一、设备驱动程序
1、介绍
Essential Linux Device Drivers <==> 精通Linux驱动开发
2、字符设备驱动基础
字符设备驱动基础3——使用register_chrdev()函数注册字符设备
3、字符设备驱动高级篇
字符设备驱动高级篇5——静态映射表的建立过程,动态映射结构体方式操作寄存器
4、设备驱动框架
Linux设备驱动模型2——总线式设备驱动组织方式(总线、设备、驱动、类等结构体)
5、misc类设备驱动
misc类设备驱动3——misc驱动框架源码分析(核心层+具体操作层)
6、framebuffer驱动详解
framebuffer驱动详解0——framebuffer的简介
framebuffer驱动详解2——fb驱动框架分析(核心层)
framebuffer驱动详解3——fb驱动分析(具体操作层)
7、input子系统详解
8、I2C子系统详解
9、块设备驱动
10、网络设备驱动
11、实践
设备驱动,字符设备驱动、(总线)设备驱动模型、sysfs文件系统、平台设备驱动
二、字符设备驱动示例
Skipping BTF generation xxx. due to unavailability of vmlinux on Ubuntu 21.04
apt install dwarves
cp /sys/kernel/btf/vmlinux /usr/lib/modules/uname -r
/build/
Skipping BTF generation xxx. due to unavailability of vmlinux on Ubuntu 21.04
1、自动创建设备节点
Makefile
obj-m := hello.o # 要生成的模块名 # hello-objs:= a.o b.o # 生成这个模块名所需要的目标文件 KDIR := /lib/modules/`uname -r`/build # KDIR := /home/liuqz/learnLinux/linux-4.15.18 PWD := $(shell pwd) default: make -C $(KDIR) M=$(PWD) modules clean: make -C $(KDIR) M=$(PWD) clean rm -rf *.o *.o.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers rm -rf .cache.mk .*ko.cmd .*.mod.o.cmd .*.o.cmd
hello.c
// hello.c #include <linux/cdev.h> #include <linux/fs.h> #include <linux/init.h> // __init __exit #include <linux/io.h> #include <linux/ioport.h> #include <linux/leds.h> #include <linux/miscdevice.h> #include <linux/module.h> // module_init module_exit #include <linux/platform_device.h> #include <linux/proc_fs.h> #include <linux/slab.h> #define MYMAJOR 190 // 注意通过cat /proc/devices查看这个200主设备号是否被占用 #define MYCNT 1 // 表示次设备号只有一个 #define MYNAME "tc" // 表示设备或者说驱动(混在一起的)的名字 static dev_t hello_dev; static struct cdev hello_cdev; static struct class *hello_class; static const struct file_operations efi_rtc_fops = { .owner = THIS_MODULE, }; static int __init hello_init(void) { // 使用新的cdev接口来注册字符设备驱动,需要2步 // 第1步:注册主次设备号 // MYMAJOR在这里是200,这里合成了要申请的设备号,即200,0 hello_dev = MKDEV(MYMAJOR, 0); int retval = register_chrdev_region(hello_dev, MYCNT, MYNAME); // 这里申请刚才合成的设备号 if (retval) { printk(KERN_ERR "Unable to register minors for %s\n", MYNAME); return -EINVAL; } printk(KERN_INFO "major = %d, minor = %d.\n", MAJOR(hello_dev), MINOR(hello_dev)); printk(KERN_INFO "register_chrdev_region success\n"); // 第2步:注册字符设备驱动 cdev_init(&hello_cdev, &efi_rtc_fops); // 这步其实可以用其他代码来代替 retval = cdev_add(&hello_cdev, hello_dev, MYCNT); // 注册字符设备驱动 if (retval) { printk(KERN_ERR "Unable to cdev_add\n"); return -EINVAL; } printk(KERN_INFO "cdev_add success\n"); // 注册字符设备驱动完成后,添加设备类的操作,以让内核帮我们发信息!! // 给udev,让udev自动创建和删除设备文件 hello_class = class_create(THIS_MODULE, "tcls"); // 该函数将创建sys/class/tcls目录 // 此目录中会有以设备文件名命名的文件夹 if (IS_ERR(hello_class)) return -EINVAL; // 最后1个参数字符串,就是我们将来要在/dev目录下创建的设备文件的名字 // 所以我们这里要的文件名是/dev/tc device_create(hello_class, NULL, hello_dev, NULL, "tc"); return 0; } static void __exit hello_exit(void) { device_destroy(hello_class, hello_dev); class_destroy(hello_class); cdev_del(&hello_cdev); // 第二步去注销申请的主次设备号 unregister_chrdev_region(hello_dev, MYCNT); } module_init(hello_init); module_exit(hello_exit); // MODULE_xxx这种宏作用是用来添加模块描述信息 MODULE_LICENSE("GPL"); // 描述模块的许可证 // MODULE_AUTHOR("aston"); // 描述模块的作者 // MODULE_DESCRIPTION("module hello"); // 描述模块的介绍信息 // MODULE_ALIAS("alias hello1"); // 描述模块的别名信息
2、misc
驱动
// misc_t.c #include <linux/fs.h> #include <linux/init.h> // __init __exit #include <linux/io.h> #include <linux/ioport.h> #include <linux/leds.h> #include <linux/miscdevice.h> #include <linux/module.h> // module_init module_exit #include <linux/platform_device.h> #include <linux/proc_fs.h> #include <linux/slab.h> #define EFI_RTC_VERSION "0.4" #define EFI_RTC_MINOR 255 /* EFI Time services */ static const struct file_operations efi_rtc_fops = { .owner = THIS_MODULE, }; static struct miscdevice efi_rtc_dev = {EFI_RTC_MINOR, "efirtc", &efi_rtc_fops}; static int __init misc_t_init(void) { int ret; struct proc_dir_entry *dir; printk(KERN_INFO "EFI Time Services Driver v%s\n", EFI_RTC_VERSION); ret = misc_register(&efi_rtc_dev); if (ret) { printk(KERN_ERR "efirtc: can't misc_register on minor=%d\n", EFI_RTC_MINOR); return ret; } return 0; } static void __exit misc_t_exit(void) { misc_deregister(&efi_rtc_dev); } module_init(misc_t_init); module_exit(misc_t_exit); // MODULE_xxx这种宏作用是用来添加模块描述信息 MODULE_LICENSE("GPL"); // 描述模块的许可证 MODULE_AUTHOR("xjh <735503242@qq.com>"); // 描述模块的作者 MODULE_DESCRIPTION("s5pv210 led driver"); // 描述模块的介绍信息 MODULE_ALIAS("s5pv210_led"); // 描述模块的别名信息
加载模块
# /dev 下自动创建了 efirtc 设备节点 ll /dev/efirtc crw------- 1 root root 10, 123 10月 16 16:15 /dev/efirtc # /sys/class/misc 创建了 efirtc ll /sys/class/misc/efirtc/ 总计 0 drwxr-xr-x 3 root root 0 10月 16 16:15 ./ drwxr-xr-x 21 root root 0 10月 11 00:30 ../ -r--r--r-- 1 root root 4096 10月 16 16:16 dev drwxr-xr-x 2 root root 0 10月 16 16:16 power/ lrwxrwxrwx 1 root root 0 10月 16 16:16 subsystem -> ../../../../class/misc/ -rw-r--r-- 1 root root 4096 10月 16 16:15 uevent
原理
- misc_init
static int __init misc_init(void) { int err; #ifdef CONFIG_PROC_FS proc_create("misc", 0, NULL, &misc_proc_fops); #endif misc_class = class_create(THIS_MODULE, "misc"); err = PTR_ERR(misc_class); if (IS_ERR(misc_class)) goto fail_remove; err = -EIO; if (register_chrdev(MISC_MAJOR,"misc",&misc_fops)) goto fail_printk; misc_class->devnode = misc_devnode; return 0; fail_printk: printk("unable to get major %d for misc devices\n", MISC_MAJOR); class_destroy(misc_class); fail_remove: remove_proc_entry("misc", NULL); return err; } subsys_initcall(misc_init);
- 创建了/sys/class/misc
- 注册了 misc 和 misc_fops
- misc_open
static int misc_open(struct inode * inode, struct file * file) { int minor = iminor(inode); struct miscdevice *c; int err = -ENODEV; const struct file_operations *old_fops, *new_fops = NULL; mutex_lock(&misc_mtx); list_for_each_entry(c, &misc_list, list) { if (c->minor == minor) { new_fops = fops_get(c->fops); break; } } if (!new_fops) { mutex_unlock(&misc_mtx); request_module("char-major-%d-%d", MISC_MAJOR, minor); mutex_lock(&misc_mtx); list_for_each_entry(c, &misc_list, list) { if (c->minor == minor) { new_fops = fops_get(c->fops); break; } } if (!new_fops) goto fail; } err = 0; old_fops = file->f_op; file->f_op = new_fops; if (file->f_op->open) { err=file->f_op->open(inode,file); if (err) { fops_put(file->f_op); file->f_op = fops_get(old_fops); } } fops_put(old_fops); fail: mutex_unlock(&misc_mtx); return err; } static const struct file_operations misc_fops = { .owner = THIS_MODULE, .open = misc_open, };
- 打开设备节点时,先调用 misc_open
- misc_open 中遍历 misc_list, 找到 minor 相同的项,然后调用各个具体的 fops
- misc_register
int misc_register(struct miscdevice * misc) { struct miscdevice *c; dev_t dev; int err = 0; INIT_LIST_HEAD(&misc->list); mutex_lock(&misc_mtx); list_for_each_entry(c, &misc_list, list) { if (c->minor == misc->minor) { mutex_unlock(&misc_mtx); return -EBUSY; } } if (misc->minor == MISC_DYNAMIC_MINOR) { int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS); if (i >= DYNAMIC_MINORS) { mutex_unlock(&misc_mtx); return -EBUSY; } misc->minor = DYNAMIC_MINORS - i - 1; set_bit(i, misc_minors); } dev = MKDEV(MISC_MAJOR, misc->minor); misc->this_device = device_create(misc_class, misc->parent, dev, misc, "%s", misc->name); if (IS_ERR(misc->this_device)) { int i = DYNAMIC_MINORS - misc->minor - 1; if (i < DYNAMIC_MINORS && i >= 0) clear_bit(i, misc_minors); err = PTR_ERR(misc->this_device); goto out; } /* * Add it to the front, so that later devices can "override" * earlier defaults */ list_add(&misc->list, &misc_list); out: mutex_unlock(&misc_mtx); return err; }
- miscdevice 加入到 misc_list 中
- device_create 创建 /sys 目录下的文件
3、platform
(1)platform_driver_register
驱动
// led_driver.c #include <linux/fs.h> #include <linux/init.h> // __init __exit #include <linux/io.h> #include <linux/ioport.h> #include <linux/leds.h> #include <linux/module.h> // module_init module_exit #include <linux/platform_device.h> #include <linux/slab.h> #define X210_LED_OFF 1 // X210中LED是正极接电源,负极节GPIO #define X210_LED_ON 0 // 所以1是灭,0是亮 struct s5pv210_led_platdata { unsigned int gpio; unsigned int flags; char *name; char *def_trigger; }; struct s5pv210_gpio_led { struct led_classdev cdev; struct s5pv210_led_platdata *pdata; }; static inline struct s5pv210_gpio_led *pdev_to_gpio( struct platform_device *dev) { return platform_get_drvdata(dev); } static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev) { return container_of(led_cdev, struct s5pv210_gpio_led, cdev); } // 这个函数就是要去完成具体的硬件读写任务的 static void s5pv210_led_set(struct led_classdev *led_cdev, enum led_brightness value) { struct s5pv210_gpio_led *p = to_gpio(led_cdev); printk(KERN_INFO "s5pv210_led_set\n"); // 在这里根据用户设置的值来操作硬件 // 用户设置的值就是value // if (value == LED_OFF) { // // 用户给了个0,希望LED灭 // gpio_set_value(p->pdata->gpio, X210_LED_OFF); // } else { // // 用户给的是非0,希望LED亮 // gpio_set_value(p->pdata->gpio, X210_LED_ON); // } } static int s5pv210_led_probe(struct platform_device *dev) { // 用户insmod安装驱动模块时会调用该函数 // 该函数的主要任务就是去使用led驱动框架提供的设备注册函数来注册一个设备 int ret = -1; struct s5pv210_led_platdata *pdata = dev->dev.platform_data; struct s5pv210_gpio_led *led; printk(KERN_INFO "----s5pv210_led_probe---\n"); led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL); if (led == NULL) { dev_err(&dev->dev, "No memory for device\n"); return -ENOMEM; } platform_set_drvdata(dev, led); // 在这里去申请驱动用到的各种资源,当前驱动中就是GPIO资源 // if (gpio_request(pdata->gpio, pdata->name)) { // printk(KERN_ERR "gpio_request failed\n"); // } else { // // 设置为输出模式,并且默认输出1让LED灯灭 // // gpio_direction_output(pdata->gpio, 1); // } // led1 led->cdev.name = pdata->name; led->cdev.brightness = 0; led->cdev.brightness_set = s5pv210_led_set; led->pdata = pdata; ret = led_classdev_register(&dev->dev, &led->cdev); if (ret < 0) { printk(KERN_ERR "led_classdev_register failed\n"); return ret; } return 0; } static int s5pv210_led_remove(struct platform_device *dev) { struct s5pv210_gpio_led *p = pdev_to_gpio(dev); led_classdev_unregister(&p->cdev); kfree(p); // kfee放在最后一步 return 0; } static struct platform_driver s5pv210_led_driver = { .probe = s5pv210_led_probe, .remove = s5pv210_led_remove, .driver = { .name = "s5pv210_led", .owner = THIS_MODULE, }, }; static int __init s5pv210_led_init(void) { return platform_driver_register(&s5pv210_led_driver); } static void __exit s5pv210_led_exit(void) { platform_driver_unregister(&s5pv210_led_driver); } module_init(s5pv210_led_init); module_exit(s5pv210_led_exit); // MODULE_xxx这种宏作用是用来添加模块描述信息 MODULE_LICENSE("GPL"); // 描述模块的许可证 MODULE_AUTHOR("xjh <735503242@qq.com>"); // 描述模块的作者 MODULE_DESCRIPTION("s5pv210 led driver"); // 描述模块的介绍信息 MODULE_ALIAS("s5pv210_led"); // 描述模块的别名信息
加载模块
sudo insmod led_driver.ko ~$ ls /sys/bus/platform/drivers/s5pv210_led/ -l 总计 0 --w------- 1 root root 4096 10月 18 09:47 bind lrwxrwxrwx 1 root root 0 10月 18 09:47 module -> ../../../../module/led_driver --w------- 1 root root 4096 10月 18 09:47 uevent --w------- 1 root root 4096 10月 18 09:47 unbind
原理
// drivers/base/platform.c /** * platform_driver_register - register a driver for platform-level devices * @drv: platform driver structure */ int platform_driver_register(struct platform_driver *drv) { drv->driver.bus = &platform_bus_type; if (drv->probe) drv->driver.probe = platform_drv_probe; if (drv->remove) drv->driver.remove = platform_drv_remove; if (drv->shutdown) drv->driver.shutdown = platform_drv_shutdown; return driver_register(&drv->driver); }
// drivers/base/driver.c int driver_register(struct device_driver *drv) { int ret; struct device_driver *other; BUG_ON(!drv->bus->p); if ((drv->bus->probe && drv->probe) || (drv->bus->remove && drv->remove) || (drv->bus->shutdown && drv->shutdown)) printk(KERN_WARNING "Driver '%s' needs updating - please use " "bus_type methods\n", drv->name); other = driver_find(drv->name, drv->bus); if (other) { put_driver(other); printk(KERN_ERR "Error: Driver '%s' is already registered, " "aborting...\n", drv->name); return -EBUSY; } ret = bus_add_driver(drv); if (ret) return ret; ret = driver_add_groups(drv, drv->groups); if (ret) bus_remove_driver(drv); return ret; }
// drivers/leds/led-class.c /** * led_classdev_register - register a new object of led_classdev class. * @parent: The device to register. * @led_cdev: the led_classdev structure for this device. */ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev) { led_cdev->dev = device_create(leds_class, parent, 0, led_cdev, "%s", led_cdev->name); if (IS_ERR(led_cdev->dev)) return PTR_ERR(led_cdev->dev); #ifdef CONFIG_LEDS_TRIGGERS init_rwsem(&led_cdev->trigger_lock); #endif /* add to the list of leds */ down_write(&leds_list_lock); list_add_tail(&led_cdev->node, &leds_list); up_write(&leds_list_lock); if (!led_cdev->max_brightness) led_cdev->max_brightness = LED_FULL; led_update_brightness(led_cdev); #ifdef CONFIG_LEDS_TRIGGERS led_trigger_set_default(led_cdev); #endif printk(KERN_DEBUG "Registered led device: %s\n", led_cdev->name); return 0; } static int __init leds_init(void) { leds_class = class_create(THIS_MODULE, "leds"); if (IS_ERR(leds_class)) return PTR_ERR(leds_class); leds_class->suspend = led_suspend; leds_class->resume = led_resume; leds_class->dev_attrs = led_class_attrs; return 0; }
(2)platform_device_register
驱动
// led_device.c #include <linux/cdev.h> #include <linux/fs.h> #include <linux/init.h> // __init __exit #include <linux/io.h> #include <linux/ioport.h> #include <linux/leds.h> #include <linux/miscdevice.h> #include <linux/module.h> // module_init module_exit #include <linux/platform_device.h> #include <linux/proc_fs.h> #include <linux/slab.h> struct s5pv210_led_platdata { unsigned int gpio; unsigned int flags; char *name; char *def_trigger; }; /* LEDS */ static struct s5pv210_led_platdata x210_led1_pdata = { .name = "led1", // .gpio = S5PV210_GPJ0(3), // 这个要根据数据手册得知 // .flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE, .def_trigger = "heartbeat", // 上面以及这个都是私有的扩展数据 }; static struct platform_device x210_led1 = { .name = "s5pv210_led", // 注意这里的设备名字要与驱动的名字一致才能匹配 .id = 1, // 这个id最终影响为s5pv210_led.1的后缀 .dev = { .platform_data = &x210_led1_pdata, }}; static struct platform_device *ab3100_platform_devs[] = { &x210_led1, // &ab3100_power_device, &ab3100_regulators_device, // &ab3100_sim_device, &ab3100_uart_device, // &ab3100_rtc_device, &ab3100_charger_device, // &ab3100_boost_device, &ab3100_adc_device, // &ab3100_fuelgauge_device, &ab3100_vibrator_device, // &ab3100_otp_device, &ab3100_codec_device, }; static int __init hello_init(void) { /* Register the platform devices */ platform_add_devices(ab3100_platform_devs, ARRAY_SIZE(ab3100_platform_devs)); return 0; } static void __exit hello_exit(void) { int i; for (i = 0; i < ARRAY_SIZE(ab3100_platform_devs); i++) platform_device_unregister(ab3100_platform_devs[i]); } module_init(hello_init); module_exit(hello_exit); // MODULE_xxx这种宏作用是用来添加模块描述信息 MODULE_LICENSE("GPL"); // 描述模块的许可证 // MODULE_AUTHOR("aston"); // 描述模块的作者 // MODULE_DESCRIPTION("module hello"); // 描述模块的介绍信息 // MODULE_ALIAS("alias hello1"); // 描述模块的别名信息
加载模块
sudo insmod led_device.ko ~$ ls /sys/bus/platform/devices/s5pv210_led.1/ -l 总计 0 -rw-r--r-- 1 root root 4096 10月 18 10:11 driver_override -r--r--r-- 1 root root 4096 10月 18 10:11 modalias drwxr-xr-x 2 root root 0 10月 18 10:11 power lrwxrwxrwx 1 root root 0 10月 18 10:11 subsystem -> ../../../bus/platform -rw-r--r-- 1 root root 4096 10月 18 10:11 uevent
原理
// drivers/base/platform.c /** * platform_add_devices - add a numbers of platform devices * @devs: array of platform devices to add * @num: number of platform devices in array */ int platform_add_devices(struct platform_device **devs, int num) { int i, ret = 0; for (i = 0; i < num; i++) { ret = platform_device_register(devs[i]); if (ret) { while (--i >= 0) platform_device_unregister(devs[i]); break; } } return ret; }
// drivers/base/platform.c /** * platform_device_register - add a platform-level device * @pdev: platform device we're adding */ int platform_device_register(struct platform_device *pdev) { device_initialize(&pdev->dev); return platform_device_add(pdev); }
(3)驱动和设备都加载
- 只加载设备
~$ ls /sys/bus/platform/devices/s5pv210_led.1/ -l 总计 0 -rw-r--r-- 1 root root 4096 10月 18 10:19 driver_override -r--r--r-- 1 root root 4096 10月 18 10:19 modalias drwxr-xr-x 2 root root 0 10月 18 10:19 power lrwxrwxrwx 1 root root 0 10月 18 10:19 subsystem -> ../../../bus/platform -rw-r--r-- 1 root root 4096 10月 18 10:19 uevent
- 加载设备和驱动后
~$ ls /sys/bus/platform/devices/s5pv210_led.1/ -l 总计 0 lrwxrwxrwx 1 root root 0 10月 18 10:20 driver -> ../../../bus/platform/drivers/s5pv210_led -rw-r--r-- 1 root root 4096 10月 18 10:19 driver_override drwxr-xr-x 3 root root 0 10月 18 10:20 leds -r--r--r-- 1 root root 4096 10月 18 10:19 modalias drwxr-xr-x 2 root root 0 10月 18 10:19 power lrwxrwxrwx 1 root root 0 10月 18 10:19 subsystem -> ../../../bus/platform -rw-r--r-- 1 root root 4096 10月 18 10:19 uevent
相比,增加了driver 和 leds 目录。
$ ls /sys/bus/platform/devices/s5pv210_led.1/leds led1
$ ll /sys/bus/platform/devices/s5pv210_led.1/leds/led1/ 总计 0 drwxr-xr-x 3 root root 0 10月 18 10:20 ./ drwxr-xr-x 3 root root 0 10月 18 10:20 ../ -rw-r--r-- 1 root root 4096 10月 18 10:22 brightness lrwxrwxrwx 1 root root 0 10月 18 10:22 device -> ../../../s5pv210_led.1/ -r--r--r-- 1 root root 4096 10月 18 10:22 max_brightness drwxr-xr-x 2 root root 0 10月 18 10:22 power/ lrwxrwxrwx 1 root root 0 10月 18 10:22 subsystem -> ../../../../../class/leds/ -rw-r--r-- 1 root root 0 10月 18 10:22 trigger -rw-r--r-- 1 root root 4096 10月 18 10:20 uevent
(4)改生成 /dev 下节点
// led_driver.c #include <linux/cdev.h> #include <linux/fs.h> #include <linux/init.h> // __init __exit #include <linux/io.h> #include <linux/ioport.h> #include <linux/leds.h> #include <linux/module.h> // module_init module_exit #include <linux/platform_device.h> #include <linux/slab.h> #define X210_LED_OFF 1 // X210中LED是正极接电源,负极节GPIO #define X210_LED_ON 0 // 所以1是灭,0是亮 static struct class *leds_class; struct s5pv210_led_platdata { unsigned int gpio; unsigned int flags; char *name; char *def_trigger; dev_t dev_num; struct cdev led_cdev; }; struct s5pv210_gpio_led { struct led_classdev cdev; struct s5pv210_led_platdata *pdata; }; static inline struct s5pv210_gpio_led *pdev_to_gpio( struct platform_device *dev) { return platform_get_drvdata(dev); } static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev) { return container_of(led_cdev, struct s5pv210_gpio_led, cdev); } // 这个函数就是要去完成具体的硬件读写任务的 static void s5pv210_led_set(struct led_classdev *led_cdev, enum led_brightness value) { // struct s5pv210_gpio_led *p = to_gpio(led_cdev); printk(KERN_INFO "s5pv210_led_set\n"); // 在这里根据用户设置的值来操作硬件 // 用户设置的值就是value // if (value == LED_OFF) { // // 用户给了个0,希望LED灭 // gpio_set_value(p->pdata->gpio, X210_LED_OFF); // } else { // // 用户给的是非0,希望LED亮 // gpio_set_value(p->pdata->gpio, X210_LED_ON); // } } static const struct file_operations leds_fops = { .owner = THIS_MODULE, }; static int s5pv210_led_probe(struct platform_device *dev) { // 用户insmod安装驱动模块时会调用该函数 // 该函数的主要任务就是去使用led驱动框架提供的设备注册函数来注册一个设备 int ret = -1; struct s5pv210_led_platdata *pdata = dev->dev.platform_data; struct s5pv210_gpio_led *led; printk(KERN_INFO "----s5pv210_led_probe---\n"); led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL); if (led == NULL) { dev_err(&dev->dev, "No memory for device\n"); return -ENOMEM; } platform_set_drvdata(dev, led); // 在这里去申请驱动用到的各种资源,当前驱动中就是GPIO资源 // if (gpio_request(pdata->gpio, pdata->name)) { // printk(KERN_ERR "gpio_request failed\n"); // } else { // // 设置为输出模式,并且默认输出1让LED灯灭 // // gpio_direction_output(pdata->gpio, 1); // } // led1 led->cdev.name = pdata->name; led->cdev.brightness = 0; led->cdev.brightness_set = s5pv210_led_set; led->pdata = pdata; cdev_init(&led->pdata->led_cdev, &leds_fops); // 这步其实可以用其他代码来代替 ret = cdev_add(&led->pdata->led_cdev, led->pdata->dev_num, 1); // 注册字符设备驱动 if (ret) { printk(KERN_ERR "Unable to cdev_add\n"); return -EINVAL; } printk(KERN_INFO "cdev_add success\n"); // ret = led_classdev_register(&dev->dev, &led->cdev); led->cdev.dev = device_create(leds_class, &dev->dev, led->pdata->dev_num, &led->pdata->led_cdev, "%s", led->pdata->name); if (ret < 0) { printk(KERN_ERR "led_classdev_register failed\n"); return ret; } return 0; } static int s5pv210_led_remove(struct platform_device *dev) { struct s5pv210_gpio_led *p = pdev_to_gpio(dev); // led_classdev_unregister(&p->cdev); device_unregister(p->cdev.dev); kfree(p); // kfee放在最后一步 return 0; } static struct platform_driver s5pv210_led_driver = { .probe = s5pv210_led_probe, .remove = s5pv210_led_remove, .driver = { .name = "s5pv210_led", .owner = THIS_MODULE, }, }; static int __init s5pv210_led_init(void) { leds_class = class_create(THIS_MODULE, "my_leds"); return platform_driver_register(&s5pv210_led_driver); } static void __exit s5pv210_led_exit(void) { platform_driver_unregister(&s5pv210_led_driver); class_destroy(leds_class); } module_init(s5pv210_led_init); module_exit(s5pv210_led_exit); // MODULE_xxx这种宏作用是用来添加模块描述信息 MODULE_LICENSE("GPL"); // 描述模块的许可证 MODULE_AUTHOR("xjh <735503242@qq.com>"); // 描述模块的作者 MODULE_DESCRIPTION("s5pv210 led driver"); // 描述模块的介绍信息 MODULE_ALIAS("s5pv210_led"); // 描述模块的别名信息
// led_device.c #include <linux/cdev.h> #include <linux/fs.h> #include <linux/init.h> // __init __exit #include <linux/io.h> #include <linux/ioport.h> #include <linux/leds.h> #include <linux/miscdevice.h> #include <linux/module.h> // module_init module_exit #include <linux/platform_device.h> #include <linux/proc_fs.h> #include <linux/slab.h> #include <linux/cdev.h> struct s5pv210_led_platdata { unsigned int gpio; unsigned int flags; char *name; char *def_trigger; dev_t dev_num; struct cdev led_cdev; }; /* LEDS */ static struct s5pv210_led_platdata x210_led1_pdata = { .name = "led1", // .gpio = S5PV210_GPJ0(3), // 这个要根据数据手册得知 // .flags = S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE, .def_trigger = "heartbeat", // 上面以及这个都是私有的扩展数据 .dev_num = MKDEV(190, 0), }; static struct platform_device x210_led1 = { .name = "s5pv210_led", // 注意这里的设备名字要与驱动的名字一致才能匹配 .id = 1, // 这个id最终影响为s5pv210_led.1的后缀 .dev = { .platform_data = &x210_led1_pdata, }}; static struct platform_device *ab3100_platform_devs[] = { &x210_led1, // &ab3100_power_device, &ab3100_regulators_device, // &ab3100_sim_device, &ab3100_uart_device, // &ab3100_rtc_device, &ab3100_charger_device, // &ab3100_boost_device, &ab3100_adc_device, // &ab3100_fuelgauge_device, &ab3100_vibrator_device, // &ab3100_otp_device, &ab3100_codec_device, }; static int __init hello_init(void) { /* Register the platform devices */ platform_add_devices(ab3100_platform_devs, ARRAY_SIZE(ab3100_platform_devs)); return 0; } static void __exit hello_exit(void) { int i; for (i = 0; i < ARRAY_SIZE(ab3100_platform_devs); i++) platform_device_unregister(ab3100_platform_devs[i]); } module_init(hello_init); module_exit(hello_exit); // MODULE_xxx这种宏作用是用来添加模块描述信息 MODULE_LICENSE("GPL"); // 描述模块的许可证 // MODULE_AUTHOR("aston"); // 描述模块的作者 // MODULE_DESCRIPTION("module hello"); // 描述模块的介绍信息 // MODULE_ALIAS("alias hello1"); // 描述模块的别名信息
三、内核函数
浅谈ioremap,vmalloc,mmap三者之间的实现&区别