《Linux设备驱动开发详解 A》一一1.6 设备驱动Hello World:LED驱动

简介:

本节书摘来华章计算机出版社《Linux设备驱动开发详解 A》一书中的第1章,第1.6节,作者:宋宝华 更多章节内容可以访问云栖社区“华章计算机”公众号查看。1

1.6 设备驱动Hello World:LED驱动

1.6.1 无操作系统时的LED驱动
在嵌入式系统的设计中,LED一般直接由CPU的GPIO(通用可编程I/O)口控制。GPIO一般由两组寄存器控制,即一组控制寄存器和一组数据寄存器。控制寄存器可设置GPIO口的工作方式为输入或者输出。当引脚被设置为输出时,向数据寄存器的对应位写入1和0会分别在引脚上产生高电平和低电平;当引脚设置为输入时,读取数据寄存器的对应位可获得引脚上的电平为高或低。
在本例子中,我们屏蔽具体CPU的差异,假设在GPIO_REG_CTRL物理地址中控制寄存器处的第n位写入1可设置GPIO口为输出,在地址GPIO_REG_DATA物理地址中数据寄存器的第n位写入1或0可在引脚上产生高或低电平,则在无操作系统的情况下,设备驱动见代码清单1.3。
代码清单1.3?无操作系统时的LED驱动

1?#def?ine reg_gpio_ctrl *(volatile int *)(ToVirtual(GPIO_REG_CTRL))
 2?#def?ine reg_gpio_data *(volatile int *)(ToVirtual(GPIO_REG_DATA))
 3?/* 初始化LED */
 4?void LightInit(void)
 5?{
 6?  reg_gpio_ctrl |= (1 << n); /* 设置GPIO为输出 */
 7?}
 8?
 9?/* 点亮LED */
10?void LightOn(void)
11?{
12?  reg_gpio_data |= (1 << n); /* 在GPIO上输出高电平 */
13?}
14?
15?/* 熄灭LED */
16?void LightOff(void)
17?{
18?  reg_gpio_data &= ~(1 << n); /* 在GPIO上输出低电平 */
19?}

上述程序中的LightInit()、LightOn()、LightOff()都直接作为驱动提供给应用程序的外部接口函数。程序中ToVirtual()的作用是当系统启动了硬件MMU之后,根据物理地址和虚拟地址的映射关系,将寄存器的物理地址转化为虚拟地址。
1.6.2 Linux下的LED驱动
在Linux下,可以使用字符设备驱动的框架来编写对应于代码清单1.3的LED设备驱动(这里仅仅是为了方便讲解,内核中实际实现了一个提供sysfs节点的GPIO LED驱动,位于drivers/leds/leds-gpio.c中),操作硬件的LightInit()、LightOn()、LightOff()函数仍然需要,但是,遵循Linux编程的命名习惯,重新将其命名为light_init()、light_on()、light_off()。这些函数将被LED设备驱动中独立于设备并针对内核的接口进行调用,代码清单1.4给出了Linux下的LED驱动,此时读者并不需要能读懂这些代码。
代码清单1.4?Linux操作系统下的LED驱动

1?#include .../* 包含内核中的多个头文件 */

  2?/* 设备结构体 */
  3?struct light_dev {
  4?    struct cdev cdev;    /* 字符设备cdev结构体 */
  5?    unsigned char vaule;    /* LED亮时为1,熄灭时为0,用户可读写此值 */
  6?};

  7?struct light_dev *light_devp; 
  8?int light_major = LIGHT_MAJOR; 

  9?MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
 10?MODULE_LICENSE("Dual BSD/GPL");
 11?/* 打开和关闭函数 */
 12?int light_open(struct inode *inode, struct f?ile *f?ilp) 
 13?{
 14?    struct light_dev *dev; 
 15?    /* 获得设备结构体指针 */
 16?    dev = container_of(inode->i_cdev, struct light_dev, cdev); 
 17?    /* 让设备结构体作为设备的私有信息 */
 18?    f?ilp->private_data = dev; 
 19?    return 0; 
 20?}

 21?int light_release(struct inode *inode, struct f?ile *f?ilp) 
 22?{
 23?    return 0; 
 24?}

 25?/* 读写设备:可以不需要 */
 26?ssize_t light_read(struct f?ile *f?ilp, char __user *buf, size_t count, 
 27?    loff_t *f_pos) 
 28?{
 29?    struct light_dev *dev = f?ilp->private_data; /* 获得设备结构体 */
 30?    if (copy_to_user(buf, &(dev->value), 1)) 
 31?        return  -EFAULT; 

 32?    return 1; 
 33?}

 34?ssize_t light_write(struct f?ile *f?ilp, const char __user *buf, size_t count, 
 35?    loff_t *f_pos) 
 36?{
 37?    struct light_dev *dev = f?ilp->private_data; 

 38?    if (copy_from_user(&(dev->value), buf, 1)) 
 39?        return  -EFAULT; 

 40?    /* 根据写入的值点亮和熄灭LED */
 41      if (dev->value == 1) 
 42          light_on();
 43      else
 44          light_off();

 45?    return 1; 
 46?}

 47?/* ioctl函数 */
 48?int light_ioctl(struct inode *inode, struct f?ile *f?ilp, unsigned int cmd, 
 49?    unsigned long arg) 
 50?{
 51?    struct light_dev *dev = f?ilp->private_data; 

 52?    switch (cmd) {
 53?    case LIGHT_ON: 
 54?        dev->value = 1; 
 55?        light_on();
 56?        break; 
 57?    case LIGHT_OFF: 
 58?        dev->value = 0; 
 59?        light_off();
 60?        break; 
 61?    default: 
 62?        /* 不能支持的命令 */
 63?        return  -ENOTTY; 
 64?    }

 65?    return 0; 
 66?}

 67?struct f?ile_operations light_fops = {
 68?    .owner = THIS_MODULE, 
 69?    .read = light_read, 
 70?    .write = light_write, 
 71?    .ioctl = light_ioctl, 
 72?    .open = light_open, 
 73?    .release = light_release, 
 74?};

 75?/* 设置字符设备cdev结构体 */
 76?static void light_setup_cdev(struct light_dev *dev, int index) 
 77?{
 78?    int err, devno = MKDEV(light_major, index); 
 79?    cdev_init(&dev->cdev, &light_fops); 
 80?    dev->cdev.owner = THIS_MODULE; 
 81?    dev->cdev.ops = &light_fops; 
 82?    err = cdev_add(&dev->cdev, devno, 1); 
 83?    if (err) 
 84?        printk(KERN_NOTICE "Error %d adding LED%d", err, index); 
 85?}

 86?/* 模块加载函数 */
 87?int light_init(void) 
 88?{
 89?    int result; 
 90?    dev_t dev = MKDEV(light_major, 0); 
 91?    /* 申请字符设备号 */
 92?    if (light_major) 
 93?        result = register_chrdev_region(dev, 1, "LED");
 94?    else {
 95?        result = alloc_chrdev_region(&dev, 0, 1, "LED");
 96?        light_major = MAJOR(dev); 
 97?    }
 98?    if (result < 0) 
 99?        return result; 

100?    /* 分配设备结构体的内存 */
101?    light_devp = kmalloc(sizeof(struct light_dev), GFP_KERNEL); 
102?    if (!light_devp) {
103?        result =  -ENOMEM; 
104?        goto fail_malloc; 
105?    }
106?    memset(light_devp, 0, sizeof(struct light_dev)); 
107?    light_setup_cdev(light_devp, 0); 
108?    light_gpio_init();
109?    return 0; 

110?fail_malloc: 
111?    unregister_chrdev_region(dev, light_devp); 
112?    return result; 
113?}

114?/* 模块卸载函数 */
115?void light_cleanup(void) 
116?{
117?    cdev_del(&light_devp->cdev);        /* 删除字符设备结构体 */
118?    kfree(light_devp);            /* 释放在light_init中分配的内存 */
119?    unregister_chrdev_region(MKDEV(light_major, 0), 1); /* 删除字符设备 */
120?}

121?module_init(light_init); 
122?module_exit(light_cleanup);

上述代码的行数与代码清单1.3已经不能相比了,除了代码清单1.3中的硬件操作函数仍然需要外,代码清单1.4中还包含了大量暂时陌生的元素,如结构体file_operations、cdev,Linux内核模块声明用的MODULE_AUTHOR、MODULE_LICENSE、module_init、module_exit,以及用于字符设备注册、分配和注销的函数register_chrdev_region()、alloc_chrdev_region()、unregister_chrdev_region()等。我们也不能理解为什么驱动中要包含light_init ()、light_cleanup ()、light_read()、light_write()等函数。
此时,我们只需要有一个感性认识,那就是,上述暂时陌生的元素都是Linux内核为字符设备定义的,以实现驱动与内核接口而定义的。Linux对各类设备的驱动都定义了类似的数据结构和函数。

相关文章
|
3月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
125 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
4月前
|
存储 Linux 开发工具
如何进行Linux内核开发【ChatGPT】
如何进行Linux内核开发【ChatGPT】
|
5月前
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
66 6
|
5月前
|
存储 缓存 Unix
Linux 设备驱动程序(三)(上)
Linux 设备驱动程序(三)
65 3
|
5月前
|
Linux
Linux 设备驱动程序(四)
Linux 设备驱动程序(四)
42 1
|
5月前
|
存储 数据采集 缓存
Linux 设备驱动程序(三)(中)
Linux 设备驱动程序(三)
63 1
|
5月前
|
存储 前端开发 大数据
Linux 设备驱动程序(二)(中)
Linux 设备驱动程序(二)
41 1
|
5月前
|
缓存 安全 Linux
Linux 设备驱动程序(二)(上)
Linux 设备驱动程序(二)
63 1
|
4月前
|
Linux API
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
|
5月前
|
存储 缓存 安全
Linux 设备驱动程序(三)(下)
Linux 设备驱动程序(三)
51 0