字符设备驱动----LED驱动程序
一. 概念介绍
一般用户在应用程序里调用的 open, read, write 函数是 c 库的函数,
这些函数会触发 swi val异常,从而引发系统调用,进入到内核空间,
内核通过VFS(virtual Filesystem)来实现调用不同的驱动函数。
例如:我们有一个函数,
int main()
{
int fd1, fd2;
int val = 1;
fd1 = open("/dev/led", O_RDWR);
write(fd1, &val, 4);
fd2 = open("hello.txt", O_RDWR);
write(fd2, &val, 4);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
函数里相同的open、write函数,引发的不同的行为,一个是操控硬件,一个是写文件。
简单的调用关系如下:
用户 –> 系统调用 –> 驱动程序
open –> sys.open –> led.open
write –> sys.write –> led.write
二. 字符设备驱动框架
实现步骤:
1. 实现驱动的 led.open, led.write, led.read 操作
2. 定义file_operations结构体, 把驱动函数填充到里面
3. 把这个结构告诉内核, 通个函数 register_chrdev(major, “first_drv”, &first_drv_fops) 来实现
4. 谁来调用注册函数 –>驱动的入口函数来调用这个注册函数, first_drv_init
5. 修饰一下这个函数入口函数,module_init(first_drv_init)
//第一步:驱动功能实现
static int first_drv_open(struct inode *inode, struct file *file)
{
//printk("first_drv_open\n");
return 0;
}
static ssize_t first_drv_write(struct file *file,
const char __user *buf,
size_t count,
loff_t * ppos)
{
//printk("first_drv_write\n");
return 0;
}
//第二步:定义结构体,并把驱动函数填充进去
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE,
.open = first_drv_open,
.write = first_drv_write,
};
//第四步:实现驱动入口函数
int major;
static int first_drv_init(void)
{
//第三步:把结构体告诉内核
major = register_chrdev(0, "first_drv", &first_drv_fops);// 注册告诉内核
return 0;
}
static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv"); // 卸载
}
//第五步:修饰入口函数,及退出函数
module_init(first_drv_init);
module_exit(first_drv_exit);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
三. 关联 [设备号] 与 [设备节点]
设备号要与设备结点关联起来,才能通过open(“/dev/xyz”)方便的操作。
1. 设置主设备号
驱动程序可以自动分配主设备号, 也可以手工指定
// 设置为 0 时是系统自动分配主设备号
major = register_chrdev(0, "first_drv", &first_drv_fops);
// 通过 [cat /proc/device] 看一下系统为我们的first_drv分配的设备号是多少
// 手动分配 111主设备号给 first_drv
register_chrdev(111, "first_drv", &first_drv_fops);
- 1
- 2
- 3
- 4
- 5
- 6
2. 设置设备节点
当应该程序 执行 open(“/dev/xyz”) 操作时,这个/dev/xyz怎么来的
2.1 手动创建
// 创建设备节点
mknod /dev/xyz c(表示是字符设备) 主设备号 次设备号
//查看设备信息:
ls -l /dev/xyz
- 1
- 2
- 3
- 4
- 5
2.2 自动创建
mdev – 根据系统信息创建设备节点
int major;
static int first_drv_init(void)
{
major = register_chrdev(0, "first_drv", &first_drv_fops);
//创建设备信息,执行后会出现 /sys/class/firstdrv
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
//创建设备节点,就是根据上面的设备信息来的
firstdrv_class_dev = class_device_create(firstdrv_class,
NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
return 0;
}
static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv");
//删除节点及信息
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
四. 完善LED驱动
完善LED驱动,也就是硬件的操作。
单片机中可以直接操作物理地址,但在驱动里只能操作虚拟地址
虚拟地址怎么来的,用 ioremap( ) 函数来映射,映射完后操作虚拟地址就像操作物理地址一样。
static int first_drv_init(void)
{
major = register_chrdev(0, "first_drv", &first_drv_fops);
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
firstdrv_class_dev = class_device_create(firstdrv_class,
NULL, MKDEV(major, 0), NULL, "xyz");
//映射 GPIO 的物理地址 0x56000050 到虚拟地址, gpfcon操作的是虚拟地址
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
映射完后,就可以操作这些地址,来控制硬件寄存器
static int first_drv_open(struct inode *inode, struct file *file)
{
//printk("first_drv_open\n");
/* 配制GPF4为输出 */
*gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
*gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
另外,当用户空间的数据要传到内核空间里,同样不能直接用,
也要通过 copy_from_user() 函数,把用户空间的值传到内核空间里。
static ssize_t first_drv_write(struct file *file,
const char __user *buf,
size_t count, loff_t * ppos)
{
int val;
//printk("first_drv_write\n");
//把用户空间的值 copy 给内核空间
copy_from_user(&val, buf, count); // copy_to_user();
if (val == 1)
{
// 点灯
*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
}
else
{
// 灭灯
*gpfdat |= (1<<4) | (1<<5) | (1<<6);
}
return 0;
}
上一篇: linux驱动之模块化编程
下一篇: gt1x TP流程