第七章 驱动开发_第一个字符型设备(LED)驱动

在要开发的工程的文件夹下简历一个sourceInsight的文件夹(用来存放sourceInsight软件生成的一些文件),然后打开sourceInsight创建一个项目,选择到我们需要编辑的工程文件夹下,sourceInsight可以使开发变得更加简单。

1.下图是驱动的基本示意图

应用程序调用open,read,write等函数,open等函数是在c库中是实现的(通过swi + value指令就会触发一个异常),这个异常进入内核空间,内核空间就会根据不同的值调用sys_open,sys_read,sys_write等函数,这些函数根据打开的不同的文件的属性找到更为底层的驱动程序。
2.对于一个最为简单的字符型驱动程序,最简单的做法就是让上层的open直接对应底层的led_open,上层的read直接对应底层的led_read等,上层open函数和底层led_open函数的对应要依赖于驱动的框架来实现。
3.怎么告诉内核
(1)定义一个file_operations,并填充。
(2)应用程序里面有什么接口,file_operations里面就有对应的成员。
(3)把这个结构告诉内核:register_chardev(major, "first_dev", &file_operations_name);
major:主设备号。
"first_dev":设备名。
&file_operations_name:定义的file_operations结构体的地址。
(4)register_chardev函数的调用者是驱动的入口函数。举例:
int first_dev_init(void)
{
    register_chrdev(major, "first_dev", &first_dev_fops);
    return 0;
}
(5)内核怎么知道自动去调用这个first_dev_init函数?
通过module_init(first_dev_init);这条语句来将这个函数进行一次修饰,module_init会定义一个结构体,这个结构体里面有一个函数指针指向first_dev_init函数,当加载一个驱动的时候,内核会自动找到这样的一个结构体,调用里面的函数指针。
4.主设备号
应用程序打开一个设备文件时,这个设备有自己的属性(比如字符型设备,可读可写可执行,主设备号次设备号等),内核根据设备类型+主设备号就找到对应的设备的file_operations结构体。可以理解为:内核中字符型有一个数组,数组存的是各个主设备号,每个主设备号下挂着相应的链表,当注册的时候就是把对应的结构体挂到相应位置的链表里面。
5.出口函数
void first_dev_exit(void)
{
    unregister_chrdev(major, "firsrt_drv");
}
出口函数也需要修饰:
module_exit(firsrt_dev_exit);
6.编译驱动程序
将第一个字符型驱动的源码放置到一个新建的文件夹,然后新建一个makefile文件:
KERN_DIR = /usr/linux-2.6.22
all:
 make -C $(KERN_DIR) M="pwd" modules
clean:
 make -C $(KERN_DIR) M="pwd" modules clean
 rm -rf modules.order
obj-m += first_dev.o
解析:
-C:后面跟一个目录,这样会转到后面的目录,用后面目录里面的makefile编译。
M="pwd":当前目录
modules:目标
然后在这个文件夹下执行:
# make
7.加载驱动
# cat /proc/devices(查看内核目前支持的设备)
# insmod first_dev.ko
# cat /proc/devices(查看是否加载成功)
8.使用一个简单的应用程序测试驱动
int main(int argc, char **argv)
{
    int fd;
    int val = 1;
    fd = open("/dev/xxx", O_RDWR);
    if(fd < 0)
        printf("Open failed!\n");
    write(fd, &val, 4);
    return 0;
}
交叉编译这个文件:
# arm-linux-gcc -o firsttest firsttest.c
# ./firsttest
到这里会提示无法打开,原因是没有对应的设备节点。
# mknod /dev/xxx c 111 0
# ./firsttest
9.主设备号的确定
可以自己给定,但是推荐使用:
major = register_chardev(0, "first_dev", &first_dev_fops);
这里参数写为0,内核就会自动找一个空的设备号,这个设备号通过返回值返回。
10.自动创建设备节点
udev机制,busybox中是mdev,注册一个驱动程序的时候,设备的信息会在sys目录下生成,而mdev会自动的根据设备信息来创建节点。
(1)定义两个全局变量
static struct class *firstdev_class;//定义一个类
static struct class_device *firstdev_class_dev;//类下面定义一个设备
在first_dev_init函数中添加:
firstdev_class = class_create(THIS_MODULE, "firstdev");
if(IS_ERR(firstdev_class))
    return PTR_ERR(firstdev_class);

firstdev_class_dev = class_device_create(firstdev_class, NULL, MKDEV(major, 0), NULL, "xyz");
if(unlikely(IS_ERR(firstdev_class_dev))
    return PTR_ERR(firstdev_class_dev);
添加卸载信息(在first_dev_exit中):
class_device_unregister(firstdev_class_dev);
class_destroy(firstdev_class);
注:这时候加载驱动可能会提醒出错。这个问题的原因是没有给代码授权,在程序末尾添加:
MODULE_LICENSE("GPL");
解析:
这样修改之后,在加载驱动的时候,mdev就会自动根据信息创建出对应的设备节点,可以通过:
# ls -l /dev/xyz
这个命令来查看,同时也可以查看设备:
# cat /proc/devices
在系统目录的class目录下有各种类,驱动加载时创建的类也在其中。
# cd class/
# cd firstdev/
# cd xyz/
# cat dev
能够自动生成设备节点,还依赖于脚本文件中的语句:
echo /sbin/mdev > /porc/sys/kernel/hotplug
内核一旦有设备加载或者卸载,那么就会根据/porc/sys/kernel/hotplug这个文件所指示的应用程序去处理。

11.LED驱动与裸机驱动的区别
裸机程序直接操作物理地址,而驱动中操作的是虚拟地址。
12.实际地址和虚拟地址的映射
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
虚拟地址的映射(建议放在first_dev_init中):
gpfcon = (volatile unsigned long *)ioremap(real_address, size);
虚拟地址去映射(建议放在first_dev_exit中):
iounmap(gpfcon);
经过映射之后,操作物理地址就可以使用类似:
*gpfcon = val;
这种方式来往对应的物理地址写值。
13.内核空间和用户空间数据传输
copy_from_user();
copy_to_user();
14.次设备号的使用
可以在open函数中使用:
int minor = MINOR(inode->i_rdev);
取出设备的次设备号,然后在接下来根据次设备号的不同设置很多种操作模式。次设备号完全是驱动程序来指定,要代表的意义由程序员控制。
15.使用系统提供的IO操作函数
s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP);
s3c2410_gpio_setpin(S3C2410_GPF4, 0);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值