分类: LINUX
2009-04-05 20:10:12
这几天一直在整驱动,本人也是新手,才刚刚入门,但对很多东西都是不甚了解。Bootloader用的是U-boot,内核用的是
#include
#include
#include
#include
#include
#include
#include
#include
#define DEVICE_NAME "leds" /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */
#define LED_MAJOR 231 /* 主设备号 */
/* 应用程序执行ioctl(fd, cmd, arg)时的第2个参数 */
#define IOCTL_LED_ON 0
#define IOCTL_LED_OFF 1
/* 用来指定LED所用的GPIO引脚 */
static unsigned long led_table [] = {
S
S
S
S
};
/* 用来指定GPIO引脚的功能:输出 */
static unsigned int led_cfg_table [] = {
S
S
S
S
};
/* 应用程序对设备文件/dev/leds执行open(...)时,
* 就会调用s
*/
static int s
{
int i;
for (i = 0; i < 4; i++) {
// 设置GPIO引脚的功能:本驱动中LED所涉及的GPIO引脚设为输出功能
s
}
return 0;
}
/* 应用程序对设备文件/dev/leds执行ioclt(...)时,
* 就会调用s
*/
static int s
struct inode *inode,
struct file *file,
unsigned int cmd,
unsigned long arg)
{
if (arg > 4) {
return -EINVAL;
}
switch(cmd) {
case IOCTL_LED_ON:
// 设置指定引脚的输出电平为0
s
return 0;
case IOCTL_LED_OFF:
// 设置指定引脚的输出电平为1
s
return 0;
default:
return -EINVAL;
}
}
/* 这个结构是字符设备驱动程序的核心
* 当应用程序操作设备文件时所调用的open、read、write等函数,
* 最终会调用这个结构中指定的对应函数
*/
static struct file_operations s
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = s
.ioctl = s
};
/*
* 执行“insmod s
*/
static int __init s
{
int ret;
/* 注册字符设备驱动程序
* 参数为主设备号、设备名字、file_operations结构;
* 这样,主设备号就和具体的file_operations结构联系起来了,
* 操作主设备为LED_MAJOR的设备文件时,就会调用s
* LED_MAJOR可以设为0,表示由内核自动分配主设备号
*/
ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s
if (ret < 0) {
printk(DEVICE_NAME " can't register major number\n");
return ret;
}
printk(DEVICE_NAME " initialized\n");
return 0;
}
/*
* 执行”rmmod s
*/
static void __exit s
{
/* 卸载驱动程序 */
unregister_chrdev(LED_MAJOR, DEVICE_NAME);
}
/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(s
module_exit(s
/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR(""); // 驱动程序的作者
MODULE_DESCRIPTION("S
MODULE_LICENSE("GPL");
编写好了源程序,怎么把驱动加载进内核呢?(在此要说明的是,要注意开发板运行的内核和你编译驱动程序的内核版本要一致,否则要出错。比如我虚拟机用的是
网上大概有三种办法:
第一种:把驱动程序放在内核源代码驱动程序相关目录下,如把上面的程序保存为s
obj-m += s
第二种:
写一个bash文件,放在和驱动程序同一目录下。内容如下:
#!/bin/bash
arm-linux-gcc –D__KERNEL__ -I/home/yuaf/linux-
s
运行此文件就可生成模块文件。
还需注意的是,若你的交叉编译环境路径不是对整个系统起作用的话,你可以指定你的交叉编译路径。如/usr/local/arm/gcc-
第三种:
写一个Makefile文件,放在和驱动程序同一目录下。然后make就行。内容如下:
obj-m +=s
KDIR :=/home/yuaf/linux-
PWD :=$(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
知道了怎么加载模块,接下来的是编译模块了,最开始我用的是第三种方法。Make一下,出现一大堆的错误,说这样没有定义,那样没有申明的。那肯定是驱动程序的头文件没有找到所致,我又回去看定义头文件也没有错啊,在linux内核目录下也找到了相应的文件。比较郁闷,之后我用了第一种办法,出现的效果和用第三种办法一样。没有办法,Google了一下,有人说重新把内核编译一下。我照着做了,还重新配置了一下内核,之后再编译,呵呵,还真好使,以前的错误全没了,在 driver/char/目录下就可以看到 s
有了 s
Insmod s
insmod:Couldn’t find the kernel version the module was compiled for
不能找到编译模块的内核版本!!我用 modinfo s
vermagic:
在开发板上 uname –a 查看开发板的内核版本:
Linux lyt
这两者的内核版本明显是一致的嘛!!这个郁闷的,这个问题困扰了我好几天,我几乎把google和百度上所有关于此问题的帖子、文章都看了,都没有找出解决的问题来。最后,我换了个文件系统,再加载的时候,出现
insmod: Not configured to support old kernels
google了一下,这是由于文件系统是由编译器2.95.3编译的。而模块和文件系统都是用比较高版本的编译器编译的。解决方法是:重新制作文件系统,用较高版本的编译器编译,我编译内核用的是
Freeing init memory 卡壳了。就停在那儿了,我换了个
这样进入文件系统,加载模块好使了,加载上了。
用lsmod查看输出如下:
s
最后,就是编写应用程序,源程序如下:
#include
#include
#include
#include
#define IOCTL_LED_ON 0
#define IOCTL_LED_OFF 1
void usage(char *exename)
{
printf("Usage:\n");
printf(" %s
printf(" led_no = 1, 2\n");
}
int main(int argc, char **argv)
{
unsigned int led_no;
int fd = -1;
if (argc != 3)
goto err;
fd = open("/dev/leds", 0); // 打开设备
if (fd < 0) {
printf("Can't open /dev/leds\n");
return -1;
}
led_no = strtoul(argv[1], 0, 0) - 1; // 操作哪个LED?
if (led_no > 3)
goto err;
if (!strcmp(argv[2], "on")) {
ioctl(fd, IOCTL_LED_ON, led_no); // 点亮它
} else if (!strcmp(argv[2], "off")) {
ioctl(fd, IOCTL_LED_OFF, led_no); // 熄灭它
} else {
goto err;
}
close(fd);
return 0;
err:
if (fd > 0)
close(fd);
usage(argv[0]);
return -1;
}
其中 strtoul(argv[1],0,0)表示的是把argv[1]按十进制转换,具体的说明见下面:
定义函数
unsigned long int strtoul(const char *nptr,char **endptr,int base);
函数说明
strtoul()会将参数nptr字符串根据参数base来转换成无符号的长整型数。 参数base范围从2至36,或0。参数base代表采用的进制方式,如base值为10则采用10进制,若base值为16则采用16进制数等。当 base值为0时则是采用10进制做转换,但遇到如'0x'前置字符则会使用16进制做转换。一开始strtoul()会扫描参数nptr字符串,跳过前 面的空格字符串,直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时('')结束转换,并将结果返回。若参数endptr不为NULL,则 会将遇到不合条件而终止的nptr中的字符指针由endptr返回。
返回值
返回转换后的长整型数,否则返回ERANGE并将错误代码存入errno中。
附加说明
ERANGE指定的转换字符串超出合法范围.
最后就是交叉编译应用程序,把可执行文件放在nfs文件系统里,按照说明的用法运行程序,就可以看到开发板上的Led的亮灭了。
如:led_test 1 on,led灯就亮了。
总结:第一次接触驱动,对驱动程序了解更深了一步。驱动程序是在内核态运行的,而我们的应用程序是在用户态下运行的。编写驱动程序,其实也就是填充file_operations里面的成员函数,常用的用 open,read,write,ioctl等函数,还要实现模块的初始化和卸载模块时执行的函数,之后就是把写的应用程序注册到file_operation结构体里。而应用程序调用 open,read,ioctl等函数,也就是通过主设备号和次设备号找到相应的file_operations中的open,read,ioctl函数的,并执行之。在整个编译和加载驱动程序的过程中要注意的几个要点就是:第一:交叉编译器的版本问题,不同的内核版本需要不同的交叉编译器。第二:开发板上运行的内核版本和你编译驱动模块的内核版本是否一致,不一致会出现加载不成功,会提示你找不到内核版本。第三:若前面两者都没有错误,你得考虑你的文件系统的问题,若你的文件系统用较低版本的交叉编译器编译,也有可能导致模块加载不成功。我就是出现这样的问题,花费了我好几天的时间,比较笨啊!还需要学习东西简直是太多太多啦!!