《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对各类设备的驱动都定义了类似的数据结构和函数。

相关文章
|
2月前
|
安全 Linux 网络安全
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
99 0
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
|
3月前
|
数据采集 编解码 运维
一文讲完说懂 WowKey -- WowKey 是一款 Linux 类设备的命令行(CLT)运维工具
WowKey 是一款面向 Linux 类设备的命令行运维工具,支持自动登录、批量执行及标准化维护,适用于企业、团队或个人管理多台设备,显著提升运维效率与质量。
|
4月前
|
监控 Linux 开发者
理解Linux操作系统内核中物理设备驱动(phy driver)的功能。
综合来看,物理设备驱动在Linux系统中的作用是至关重要的,它通过与硬件设备的紧密配合,为上层应用提供稳定可靠的通信基础设施。开发一款优秀的物理设备驱动需要开发者具备深厚的硬件知识、熟练的编程技能以及对Linux内核架构的深入理解,以确保驱动程序能在不同的硬件平台和网络条件下都能提供最优的性能。
275 0
|
6月前
|
安全 Ubuntu Linux
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
219 0
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
|
7月前
|
运维 安全 Linux
试试Linux设备命令行运维工具——Wowkey
WowKey 是一款专为 Linux 设备设计的命令行运维工具,提供自动化、批量化、标准化、简单化的运维解决方案。它简单易用、高效集成且无依赖,仅需 WIS 指令剧本文件、APT 账号密码文件和 wowkey 命令即可操作。通过分离鉴权内容与执行内容,WowKey 让运维人员专注于决策,摆脱繁琐的交互与执行细节工作,大幅提升运维效率与质量。无论是健康检查、数据采集还是配置更新,WowKey 都能助您轻松应对大规模设备运维挑战。立即从官方资源了解更多信息:https://atsight.top/training。
|
7月前
|
数据采集 运维 安全
Linux设备命令行运维工具WowKey问答
WowKey 是一款用于 Linux 设备运维的工具,可通过命令行手动或自动执行指令剧本,实现批量、标准化操作,如健康检查、数据采集、配置更新等。它简单易用,只需编写 WIS 指令剧本和 APT 帐号密码表文件,学习成本极低。支持不同流派的 Linux 系统,如 RHEL、Debian、SUSE 等,只要使用通用 Shell 命令即可通吃Linux设备。
|
8月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
Linux
Linux 设备驱动程序(四)
Linux 设备驱动程序(四)
174 1
|
Linux API
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
|
Linux Python
Python全栈之路:Linux下的HelloWorld
Python全栈之路:Linux下的HelloWorld