当我们着手写一个驱动的时候,并不需要从0开始,而是可以在内核源码里面找到别人成熟的类似的驱动进行修改,移植。这也是一个非常高效的过程。
首先,我们要明白自己的目标,要的是什么.
从上到下,一个软件系统可以分为:应用程序、库、操作系统(内核)、驱动程序。以一个点灯程序为例:
现在的内核都有一个VFS文件系统,会根据用户空间(应用程序)里面的设备提供的设备属性,设备号来进行填充,然后根据填充的内容注册进内核。
实现流程如下:
1,写出要实现的功能:led_open、led_write
2, 怎么告诉内核 file_operation结构体
a,定义fileoperation并填充
b,把这个结构告诉内核
register_chrdev(...)
3,谁来调用 驱动入口
int_led_drv_init(void)
4, 对驱动进行修饰
module_init(led_drv_init);
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/fs.h>
- #include <linux/init.h>
- #include <linux/delay.h>
- #include <asm/uaccess.h>
- #include <asm/irq.h>
- #include <asm/io.h>
- #include <asm/arch/regs-gpio.h>
- #include <asm/hardware.h>
- static struct class *leddrv_class;
- static struct class_device *leddrv_class_dev;
- volatile unsigned long *gpfcon = NULL;
- volatile unsigned long *gpfdat = NULL;
- static int led_drv_open(struct inode *inode, struct file *file)
- {
- printk("led_drv_open\n");
- gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)); //清零
- gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)); //输出
- return 0;
- }
- static ssize_t led_drv_write(struct file *file, const char __user *buf,size_t count, loff_t * ppos)
- {
- int val;
- copy_from_user(&val, buf, count); //copy to user
- if (val == 1)
- {
- //open_led
- gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
- }
- else
- {
- // close_led
- gpfdat |= (1<<4) | (1<<5) | (1<<6);
- }
-
- printk("led_drv_write\n");
- return 0;
- }
- static struct file_operations led_drv_fops = {
- .owner = THIS_MODULE,
- .open = led_drv_open,
- .write = led_drv_write,
- };
- int major;
- int led_drv_init(void)
- {
- major = register_chrdev(0,"led_drv",&led_drv_fops);
- leddrv_class = class_create(THIS_MODULE, "leddrv");
- if(IS_ERR(leddrv_class))
- return PTR_ERR(leddrv_class);
- leddrv_class_dev = class_device_creat(leddrv_class, NULL, MKDEV(major,0),NULL,"led");
- if(unlikely(IS_ERR(leddrv_class_dev)));
- return PTR_ERR(leddrv_class_dev);
- gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
- gpfdat = gpfcon + 1; //加1是以(volatile unsigned long *)的长度为单位,即为4字节
- return 0;
- }
- void led_drv_exit(void)
- {
- unregister_chrdev(major,"led_drv");
- class_device_unregister(leddrv_class_dev);
- class_destroy(leddrv_class);
- iounmap(gpfcon);
- return 0;
- }
- module_init(led_drv_init);
- module_exit(led_drv_exit);
当然,以上代码并不是很完善,只是为了说明一些问题,
驱动程序可以自动或手动分配主设备号,上面的代码major=0,说明是自动分配主设备号,应该程序也可以自动创建或手工创建,上面的例子中通过linux udev机制,通过两个类实现自动创建。open配置引脚,wirte返回引脚状态。file_operation里面的结构有很多,根据需要找到要用到的进行填充。还有就是驱动程序里面用到的是虚拟地址,通过ioremap/iounmap来映射/释放虚拟地址。
测试代码:
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <stdio.h>
- int main (int argc, char **argv)
- {
- int fd;
- int val = 1;
- fd = open ("/dev/led", O_RDWR);
- if (fd<0)
- printf("can't open!\n");
- if (argc != 2)
- {
- printf("Usage : \n");
- printf("%s \n", argv[0]);
- return 0;
- }
- if (strcmp(argv[1], "on") == 0)
- {
- val = 1;
- }
- else
- {
- val = 0;
- }
- write(fd, &val, 4);
- return 0;
- }
16,17行对输入的参数进行判断,如果输入的参数不等于2,则打印出正确的使用方法。把val写入驱动程序中的val就可以控制灯的亮灭。
Makefile文件:用来编译led_drv.c
- KERN_DIR = /xxx/linux-2.6.22.6
- all:
- make -C $(KERN_DIR) M=`pwd` modules
- clean:
- make -C $(KERN_DIR) M=`pwd` modules clean
- rm -rf modules.order
- obj-m += led_drv.o
第一行是存放的编译好的内核源码目录,根据自己实际情况而定。这里要注意的是内核的源码要和驱动程序版本相同,不然可能会出现一些问题。
该系列笔记均是看韦东山视频教程的记录,基本上用的是他所提供的源码。在这里也推荐大家看下他的视频教程,真的很不错的!
以上难免有疏漏之处,今后会完善。
阅读(2024) | 评论(0) | 转发(2) |