概念
U-boot:启动内核
内核:启动应用
应用:只调用open, read, write...等标准接口操作硬件,不去关心硬件相关的具体操作
驱动:以led为例,与应用接口对应包含led_open, led_read, led_write...
框架
1. 写出led_open, led_write函数。
2. 如何告诉内核?
A. 定义一个file_operations结构体,并填充它。
B. 调用register_chardev向内核注册这个结构体。
B1. 谁来调用register_chardev? -> 驱动的入口函数:led_init
B2. 为什么led_init是入口函数? -> 通过module_init来修饰led_init
B2.1 module_init是什么?一个宏,定义一个结构体,里边有个函数指针指向led_init,当加载驱动时,内核会自动找到这个结构体,然后调用这个指针指向的入口函数。
3. 卸载驱动函数:
出口函数调用unregister_chardev,用module_exit来修饰这个函数,以向内核表明这是一个出口函数。
4. 应用程序调用open如何找到相应的file_operations结构体?
根据设备类型,主设备号查找。由此,可引发对register_chardev简单实现方式的推断:
内核中有一个chardev[]数组,register_chardev根据传入的major填充这个数组中的相应元素。
5. 自动创建设备节点:udev机制,对于busybox来说是mdev。
使用mdev机制:根据系统信息自动创建设备节点。
怎么提供系统信息?
入口函数中调用:class_create , class_device_create
出口函数中调用:class_device_unregister, class_destroy
这会导致在/sys/目录下生成相应的信息,mdev即可根据这些信息自动创建设备节点。
杂项
cat /proc/devices 查看内核当前支持的设备,打印信息的第一列是主设备号,第二列是名字
mknod /dev/xxx c major minor 手工创建设备节点
驱动程序中不能直接操作物理地址,需要用ioremap映射到虚拟地址,再去操作。
问:应用如何找到对应的驱动?
答:根据打开文件的属性。以字符设备为例,设备文件属性有:字符设备、主设备号等。根据这两点,就可以在前述的chardev[]数组中找到,之后的read, write, ioctl就会使用这个数组项中的成员函数。
exec 5</dev/buttons 打开/dev/buttons定位到5,可用ls -l /proc/771/fd查看;771表示sh的PID,可用ps命令获得
exec 5<&- 关闭上面打开的/dev/buttons
源码
驱动:
/* * 引脚:底板 - PH6, PH7 * 核心板 - PB4 */ #include <linux/kernel.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/gpio.h> #include <linux/leds.h> #include <linux/of_platform.h> #include <linux/of_gpio.h> #include <linux/slab.h> #include <linux/workqueue.h> #include <linux/module.h> #include <linux/pinctrl/consumer.h> #include <linux/err.h> #include <linux/fs.h> #include <linux/kdev_t.h> #include <linux/uaccess.h> #include <asm/io.h> static int major; static struct class *leds_class; static struct device *led_device[4]; static volatile unsigned int *gpio_base = NULL; static volatile unsigned int *gpiob_dir, *gpiob_dataout; static volatile unsigned int *gpioh_dir, *gpioh_dataout; static volatile unsigned int *sys_base = NULL; static volatile unsigned int *sys_gpb_mfpl; static volatile unsigned int *sys_gph_mfpl; static int leds_open(struct inode *inode, struct file *filp) { int minor = MINOR(inode->i_rdev); switch (minor) { case 0: { *sys_gpb_mfpl &= ~(0xf << (4 * 4)); *sys_gph_mfpl &= ~((0xf << (6 * 4)) | (0xf << (7 * 4))); *gpiob_dir |= 1 << 4; *gpioh_dir |= (1 << 6) | (1 << 7); break; } case 1: { *sys_gpb_mfpl &= ~(0xf << (4 * 4)); *gpiob_dir |= 1 << 4; break; } case 2: { *sys_gph_mfpl &= ~(0xf << (6 * 4)); *gpioh_dir |= (1 << 6); break; } case 3: { *sys_gph_mfpl &= ~(0xf << (7 * 4)); *gpioh_dir |= (1 << 7); break; } default: { return -EINVAL; } } return 0; } static ssize_t leds_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { int val; int minor = MINOR(filp->f_dentry->d_inode->i_rdev); copy_from_user(&val, buf, count); switch (minor) { case 0: { if (val == 0) { *gpiob_dataout |= (1 << 4); *gpioh_dataout |= (1 << 6) | (1 << 7); } else { *gpiob_dataout &= ~(1 << 4); *gpioh_dataout &= ~((1 << 6) | (1 << 7)); } break; } case 1: { if (val == 0) { *gpiob_dataout |= (1 << 4); } else { *gpiob_dataout &= ~(1 << 4); } break; } case 2: { if (val == 0) { *gpioh_dataout |= (1 << 6); } else { *gpioh_dataout &= ~(1 << 6); } break; } case 3: { if (val == 0) { *gpioh_dataout |= (1 << 7); } else { *gpioh_dataout &= ~(1 << 7); } break; } default: { return -EINVAL; } } return 0; } static struct file_operations leds_fops = { .owner = THIS_MODULE, .open = leds_open, .write = leds_write, }; static int leds_init(void) { int i; major = register_chrdev(0, "leds", &leds_fops); leds_class = class_create(THIS_MODULE, "leds"); led_device[0] = device_create(leds_class, NULL, MKDEV(major, 0), NULL, "leds"); for (i = 1; i < 4; i++) { led_device[0] = device_create(leds_class, NULL, MKDEV(major, i), NULL, "led%d", i); } gpio_base = (volatile unsigned int *)ioremap(0xb8003000, 0x400); gpiob_dir = gpio_base + 0x40 / 4; gpiob_dataout = gpio_base + 0x44 / 4; gpioh_dir = gpio_base + 0x1c0 / 4; gpioh_dataout = gpio_base + 0x1c4 / 4; sys_base = (volatile unsigned int *)ioremap(0xb0000000, 0x200); sys_gpb_mfpl = sys_base + 0x78 / 4; sys_gph_mfpl = sys_base + 0xa8 / 4; return 0; } static void leds_exit(void) { int i; iounmap(gpio_base); iounmap(sys_base); for (i = 0; i < 4; i++) { device_destroy(leds_class, MKDEV(major, i)); } class_destroy(leds_class); unregister_chrdev(major, "leds"); } module_init(leds_init); module_exit(leds_exit); MODULE_LICENSE("GPL");
测试程序:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> /* ./ledstest </dev/led*> <on|off|blink> */ int main(int argc, char *argv[]) { int fd; int val = 0; if (argc != 3) { printf("Usage:\n"); printf("%s </dev/led*> <on|off>\n", argv[0]); return -1; } fd = open(argv[1], O_RDWR); if (fd < 0) { printf("Can't open %s\n", argv[1]); return -1; } if (strcmp(argv[2], "on") == 0) { val = 1; } else if (strcmp(argv[2], "off") == 0) { val = 0; } else if (strcmp(argv[2], "blink") == 0) { val = 1; while (1) { write(fd, &val, sizeof(val)); if (val) { usleep(50000); } else { sleep(2); } val ^= 0x1; } } else { printf("Usage:\n"); printf("%s </dev/led*> <on|off>\n", argv[0]); return -1; } write(fd, &val, sizeof(val)); return 0; }