文章目录
- 一、设备驱动程序
- 二、字符设备驱动示例
- 1、自动创建设备节点
- Makefile
- hello.c
- 2、misc
- 驱动
- 加载模块
- 原理
- 3、platform
- (1)platform_driver_register
- 驱动
- 加载模块
- 原理
- (2)platform_device_register
- 驱动
- 加载模块
- 原理
- (3)驱动和设备都加载
- (4)改生成 /dev 下节点
- 三、内核函数
一、设备驱动程序
1、介绍
设备驱动程序的简介
2、字符设备驱动基础
字符设备驱动基础1——简单的驱动源代码分析
字符设备驱动基础2——用开发板来调试驱动的步骤
字符设备驱动基础3——使用register_chrdev()函数注册字符设备
字符设备驱动基础4——读写接口的操作实践
字符设备驱动基础5——驱动如何操控硬件
3、字符设备驱动高级篇
字符设备驱动高级篇1——注册字符设备驱动的新接口
字符设备驱动高级篇2——注册字符设备驱动的函数代码分析
字符设备驱动高级篇3——自动创建设备文件
字符设备驱动高级篇4——自动创建设备文件的函数代码分析
字符设备驱动高级篇5——静态映射表的建立过程,动态映射结构体方式操作寄存器
字符设备驱动高级篇6——内核提供的读写寄存器接口
4、设备驱动框架
设备驱动框架1——LED驱动框架的分析(核心层)
设备驱动框架2——基于驱动框架写LED驱动(具体操作层)
设备驱动框架3——使用gpiolib完成LED驱动
设备驱动框架4——将驱动集成到内核中
Linux设备驱动模型1——设备驱动模型的简介与底层架构
Linux设备驱动模型2——总线式设备驱动组织方式(总线、设备、驱动、类等结构体)
Linux设备驱动模型3——平台总线的工作原理
Linux设备驱动模型4——基于平台总线的LED驱动实践
5、misc类设备驱动
misc类设备驱动1——板载蜂鸣器驱动测试
misc类设备驱动2——misc类设备的简介
misc类设备驱动3——misc驱动框架源码分析(核心层+具体操作层)
6、framebuffer驱动详解
framebuffer驱动详解0——framebuffer的简介
framebuffer驱动详解2——fb驱动框架分析(核心层)
framebuffer驱动详解3——fb驱动分析(具体操作层)
7、input子系统详解
input子系统详解1——input子系统简介
input子系统详解2——应用层代码实践
input子系统详解3——input子系统框架核心层分析
input子系统详解4——输入事件驱动层源码分析
input子系统详解5——参考驱动模板编写按键驱动
8、I2C子系统详解
I2C子系统详解1——I2C总线设备的驱动框架
I2C子系统详解2——I2C核心层源码分析
I2C子系统详解3——I2C总线驱动层代码分析
I2C子系统详解4——I2C设备驱动层代码分析
二、字符设备驱动示例
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.04linux UIO驱动实践
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 <[email protected]>"); // 描述模块的作者
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 <[email protected]>"); // 描述模块的作者
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
Linux设备驱动模型4——基于平台总线的LED驱动实践平台设备与平台驱动的注册
(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 <[email protected]>"); // 描述模块的作者
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三者之间的实现&区别
内存之ioremap内存映射
remap_pfn_range使用详解
Linux内核机制总结内存管理之内存映射(十一)
IORESOURCE_IO和IORESOURCE_MEM
标签:led,s5pv210,misc,platform,device,驱动,root,畅想,设备 From: https://blog.51cto.com/u_15930680/8031033