原标题:从点一个灯开始学写Linux字符设备驱动!

[导读] 前一篇文章,介绍了如何将一个hello word模块编译进内核或者编译为动态加载内核模块,本篇来介绍一下如何利用Linux驱动模型来完成一个LED灯设备驱动。点一个灯有什么好谈呢?况且Linux下有专门的leds驱动子系统。

点灯有啥好聊呢?

在很多嵌入式系统里,有可能需要实现数字开关量输出,比如:

LED状态显示

阀门/继电器控制

蜂鸣器

......

嵌入式Linux一般需求千变万化,也不可能这些需求都有现成设备驱动代码可供使用,所以如何学会完成一个开关量输出设备的驱动,一方面点个灯可以比较快了解如何具体写一个字符类设备驱动,另一方面实际项目中对于开关量输出设备就可以这样干,所以是具有较强的实用价值的。

要完成这样一个开关量输出GPIO的驱动程序,需要梳理梳理下面这些概念:

设备编号

设备挂载

关键数据结构

设备编号

字符设备是通过文件系统内的设备名称进行访问的,其本质是设备文件系统树的节点。故Linux下设备也是一个文件,Linux下字符设备在/dev目录下。可以在开发板的控制台或者编译的主Linux系统中利用ls -l /dev查看,如下图:

对于ls -l列出的属性,做一个比较细的解析:

细心的朋友或许会发现设备号属性,在有的文件夹下列出来不是这样,这就对了!普通文件夹下是这样:

差别在于一个是文件大小,一个是设备号。

再细心一点的朋友或许还会问,这些/dev下的文件时间属性为神马都相差无几?这是因为/dev设备树节点是在内核启动挂载设备驱动动态生成的,所以时间就是系统开机后按次序生成的,你如不信,不妨重启一下系统在查看一下。

常见文件类型:

d: directory 文件夹

l: link 符号链接

p: FIFO pipe 管道文件,可以用mkfifo命令生成创建

s: socket 套接字文件

c: char 字符型设备文件

b: block 块设备文件

-:常规文件

回到设备号,设备号是一个32位无符号整型数,其中:

12位用来表示主设备号,用于标识设备对应的驱动程序。

20位用来表示次设备号,用于正确确定设备文件所指的设备。

这怎么理解呢,看下串口类设备就比较清楚了:

主设备号一样证明这些设备共用了一个驱动程序,而次设备号不一样,则对应了不同的串口设备。那么怎么得到设备号呢?

/*下列定义位于./include/linux/types.h */

typedefu32 __kernel_dev_t;

typedef__kernel_dev_tdev_t;

/* 下面宏用于生成主设备号,次设备号 */

/* 下列定义位于./include/linux/Kdev_t.h */

# defineMINORBITS 20

# defineMINORMASK ((1U << MINORBITS) - 1)

# defineMAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))

# defineMINOR(dev) ((unsigned int) ((dev) & MINORMASK))

# defineMKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

使用举例:

/* 主设备号 */

MAJOR( dev_tdev);

/* 次设备号 */

MINOR( dev_tdev);

设备挂载

为简化问题,本文描述一下动态加载设备驱动模块,暂不考虑设备树。参考<>一书。可参照前文将驱动编译成模块,然后利用下面脚步动态加载模块。由前面描述,知道设备最终需要在/dev目录下生成一个设备文件,那么这个设备文件节点是怎么生成呢,看看下面的脚本:

#!/bin/sh

#-----------------------------------------------------------------------

module="led"

device="led"

mode="664"

group="staff"

# 利用insmod命令加载设备模块

insmod -f $module.ko $* || exit 1

#获取系统分配的主设备号

major=`cat /proc/devices | awk "$2=="$module" {print $1}"`

# 删除旧节点

rm -f /dev/${device}

# 创建设备文件节点

mknod /dev/${device} c $major 0

#设置设备文件节点属性

chgrp $group /dev/${device}

chmod $mode /dev/${device}

这里要提一下/proc/devices,这是一个文件记录了字符和块设备的主设备号,以及分配到这些设备号的设备名称。比如使用cat命令来列出这个文件内容:

关键数据结构

字符设备由什么关键数据结构进行抽象的呢,来看看:

file_operations定义在./include/linux/fs.h

cdev定义在./include/linux/cdev.h

cdev中与字符设备驱动编程相关两个数据域:

const struct file_operations *ops;

dev_t dev;设备编号

文件操作符是一个庞大的数据结构,常规字符设备驱动一般需要实现下面一些函数指针:

read:用来实现从设备中读取数据

write:用于实现写入数据到设备

ioctl:实现执行设备特定命令的方法

open:用实现打开一个设备文件

release:当file结构被释放时,将调用这个接口函数点灯设备

先上代码(可左右滑动显示):

# include

# include

# include

# include /* printk */

# include

# include

# include /* everything... */

# include

# include /* copy_*_user */

/*这里具体参考不同开发板的电路 GPIOC24 */

# defineLED_CTRL (2*32+24)

staticconstunsignedintled_pad_cfg = LED_CTRL;

structt_led_dev{

structcdevcdev;

unsignedcharvalue;

};

structt_led_devled_dev;

staticdev_tled_major;

staticdev_tled_minor= 0;

staticintled_open(struct inode * inode,struct file * filp)

{

filp->private_data = &led_dev;

printk ( "led is opened!n");

return0;

}

staticintled_release(struct inode * inode,

struct file * filp)

{

return0;

}

staticssize_t led_read(struct file * file,

char__user * buf,

size_tcount,

loff_t*ppos)

{

ssize_tret= 1;

if(copy_to_user(&(led_dev.value),buf, 1))

return-EFAULT;

printk ( "led is read!n");

returnret;

}

staticssize_t led_write(struct file * filp,

constchar__user *buf,

size_tcount, loff_t*ppos)

{

unsignedcharvalue;

ssize_tretval = 0;

if(copy_from_user(&value,buf, 1))

return-EFAULT;

if(value& 0x01)

gpio_set_value(led_pad_cfg, 1);

else

gpio_set_value(led_pad_cfg, 0);

printk ( "led is written!n");

returnretval;

}

staticconststructfile_operationsled_fops= {

.owner = THIS_MODULE,

.read = led_read,

.write = led_write,

.open = led_open,

.release = led_release,

};

staticvoidled_setup_cdev(struct t_led_dev * dev, intindex)

{

/* 初始化字符设备驱动数据域 */

interr,devno = MKDEV(led_major,led_minor+index);

cdev_init(&(dev->cdev),&led_fops);

dev->cdev.owner = THIS_MODULE;

dev->cdev.ops = &led_fops;

/* 字符设备注册 */

err = cdev_add(&(dev->cdev),devno, 1);

if(err)

printk(KERN_NOTICE "Error %d adding led %d",err,index);

}

staticintled_gpio_init( void)

{

if(gpio_request(LED_CTRL, "led") < 0) {

printk( "Led request gpio failedn");

return-1;

}

printk( "Led gpio requested okn");

gpio_direction_output(LED_CTRL, 1);

gpio_set_value(LED_CTRL, 1);

return0;

}

/* 注销设备 */

voidled_cleanup( void)

{

dev_tdevno = MKDEV(led_major, led_minor);

gpio_set_value(LED_CTRL, 0);

gpio_free(LED_CTRL);

cdev_del(&led_dev.cdev);

unregister_chrdev_region(devno, 1); //注销设备号

}

/* 注册设备 */

staticintled_init( void)

{

intresult;

dev_tdev = MKDEV( led_major, 0);

/* 动态分配设备号 */

result = alloc_chrdev_region(&dev, 0, 1, "led");

if(result< 0)

returnresult;

led_major = MAJOR(dev);

memset(&led_dev, 0, sizeof(struct t_led_dev));

led_setup_cdev(&led_dev, 0);

led_gpio_init;

printk ( "led device initialised!n");

returnresult;

}

module_init(led_init);

module_exit(led_cleanup);

MODULE_DEION( "Led device demo");

MODULE_AUTHOR( "embinn");

MODULE_LICENSE( "GPL");

来总结一下要点:

init函数,需要用module_init宏包起来,本例中即为led_init,module_init宏的作用就是选编译为模块或进内核的底层实现,建议刚开始不必深究。一般而言主要实现:

申请分配主设备号alloc_chrdev_region

为特定设备相关数据结构分配内存

将入口函数(open read write等)与字符设备驱动的cdev抽象数据结构关联

将主设备与驱动程序cdev相关联

申请硬件资源,初始化硬件

调用cdev_add注册设备

exit函数,一样需要用module_exit包起来,主要负责:

释放硬件资源

调用cdev_del删除设备

调用unregister_chrdev_region注销设备号

用户空间与驱动数据交换

copy_to_user,如其名一样,将内核空间数据信息传递到用户空间

copy_from_user,如其名一样,从用户空间拷贝数据进内核空间

善用printk进行驱动调试,这是内核打印函数。

gpio相关操作函数,这里就不一一列举其作用了,比较容易理解。测试驱动# include

# include

# include

# include

# defineREAD_SIZE 10

intmain( intargc, char**argv){

intfd,count;

floatvalue;

unsignedcharbuf[READ_SIZE+ 1];

printf( "Cmd argv[0]:%s,argv[1]:%s,argv[2]:%sn",argv[ 0],argv[ 1],argv[ 2] );

if( argc< 2){

printf( "[Usage: test device_name ]n");

exit( 0);

}

if( strlen(argv[ 2]!= 1)

printf( "Invalid parametern");

if(( fd = open(argv[ 1],O_WRONLY ))< 0){

printf( "Error:can not open the device: %sn",argv[ 1] );

exit( 1);

}

if(argv[ 2][ 0] == '1')

buf[ 0] = 1;

elseif(argv[ 2][ 0] == '0')

buf[ 0] = 0;

else

printf( "Invalid parametern");

printf( "write: %dn",buf[ 0]);

if( (count = write( fd, buf , 1))< 0){

perror( "write error.n");

exit( 1);

}

close(fd);

printf( "close device %sn",argv[ 1] );

return0;

}

编译成可执行文件,调用前面的脚本加载设备后,在/dev下就可以看到led设备了。 比如测试代码编译成ledTest执行文件,则使用下面命令运行测试程序就可以看到led控制效果了:

/*打开led 具体取决电路是高有效还是低有效*/

./ledTest /dev/led 1

./ledTest /dev/led 0

这样就实现了用户空间驱动底层设备了,实际应用代码就可以这样去访问底层的字符型设备。

总结一下

本文总结了简单字符设备的驱动开发的一些要点,以及如何动态加载,在设备文件系统树上创建设备节点,并演示了驱动以及驱动使用的基本要点。

免责声明:本文系网络转载,版权归原作者所有。如涉及作品版权问题,请与我们联系,我们将根据您提供的版权证明材料确认版权并支付稿酬或者删除内容。返回搜狐,查看更多

责任编辑:

linux字符设备驱动在哪里设置,从点一个灯开始学写Linux字符设备驱动!相关推荐

  1. WS2812驱动 SPI+DMA 无需降频 一个灯占用9Byte的RAM

    WS2812驱动 STM32F103 SPI+DMA 无需降频 一个灯占用9Byte的RAM 对于WS2812不了解的朋友可以先看一下这些 ​[STM32]WS2812介绍.使用SPI+DMA发送数据 ...

  2. 驱动华为_再补齐一个短板,华为正式宣布进军屏幕驱动行业

    作为全球屏幕生产与出口的大国,京东方生产的屏幕远销全球,但用于驱动屏幕的IC芯片,却基本依靠进口.数据显示,仅在2019年京东方生产屏幕用于采购芯片的总金额超过了60亿元,但国产芯片占比却不到5%. ...

  3. linux pmap命令,Linux进程管理之pmap命令解析【每日一个知识点第130期-Linux】

    使用Linux系统必备的技能之一就是Linux进程管理,系统运行的过程正是无数进程在运行的过程.这些进程的运行需要占用系统的内存等资源,做好系统进程的管理,对于我们合理分配.使用系统资源有非常大的意义 ...

  4. vue 怎么在字符串中指定位置插入字符_vue 我想在一个字符串中间加入一个/br 该怎么写?...

    let validatePass = (rule, value, callback) => { console.log("AAAAA", rule) console.log( ...

  5. crontab 每周五_/etc/crontab 怎么设置每月最后一个周六任务

    你的位置: 问答吧 -> Linux -> 问题详情 /etc/crontab 怎么设置每月最后一个周六任务 /etc/crontab 怎么设置每月最后一个周五任务? 我需要在每月定时做备 ...

  6. 手把手教你写Linux I2C设备驱动

    手把手教你写Linux I2C设备驱动 标签:Linux 设备 驱动 详解 i2c 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http:/ ...

  7. linux探测i2c设备连接状态,手把手教你写Linux I2C设备驱动

    Linux I2C驱动是嵌入式Linux驱动开发人员经常需要编写的一种驱动,因为凡是系统中使用到的I2C设备,几乎都需要编写相应的I2C驱动去配置和控制它,例如 RTC实时时钟芯片.音视频采集芯片.音 ...

  8. 手把手教你写Linux I2C设备驱动 tvp5158

    Linux I2C驱动是嵌入式Linux驱动开发人员经常需要编写的一种驱动,因为凡是系统中使用到的I2C设备,几乎都需要编写相应的I2C驱动去配置和控制它,例如 RTC实时时钟芯片.音视频采集芯片.音 ...

  9. linux i2c detect函数,手把手教你写Linux I2C设备驱动

    Linux I2C驱动是嵌入式Linux驱动开发人员经常需要编写的一种驱动,因为凡是系统中使用到的I2C设备,几乎都需要编写相应的I2C驱动去配置和控制它,例如 RTC实时时钟芯片.音视频采集芯片.音 ...

最新文章

  1. 《转》八大算法详细讲解
  2. mysql两张表一起计数_mysql-同一张表上的多个联接,其中一个查询计数
  3. SAP 电商云 Accelerator 和 Spartacus UI 的工作机制差异
  4. extjs弹出窗口查看文本内容-new Ext.Window
  5. 023 -uniApp
  6. [iphone-游戏]游戏中常用的数据组织方式和解析
  7. 如何解决GBK的编码的文件中的中文转换成为UTF-8编码的文件而且不乱码
  8. 固态硬盘用软件测试掉速严重,分析固态硬盘掉速原因及解决方法
  9. 零至二岁宝宝故事(一)
  10. echarts 南丁格尔玫瑰图属性设置
  11. Oblog博客系统存在SQL注入漏洞http://www.anqn.com
  12. 华三交换机配置vrrp_VRRP原理与配置 华为、华三交换机,路由器
  13. idle最好记的常用快捷键大全
  14. 我精心整理的 136 页 Excel 数据透视表 PDF 文件!【附获取方式】
  15. jooq 执行sql_jOOQ星期二:Markus Winand执行现代SQL任务
  16. 软件工程的10个知识领域
  17. 微信小程序:云开发开通
  18. Javaweb开发一般步骤
  19. 天下文章一大抄 mysql远程连接
  20. ilove中文_iLovePDF中文版

热门文章

  1. vs中四点画矩形的算法_中考热点,初高中衔接之倒角利器四点共圆
  2. 字符串固定长度 易语言_易语言字符串操作源码
  3. python删除列表一段元素_Python基础--list列表删除元素
  4. 语句拼接_Linux 进阶语句
  5. Git 基础 —— 常用命令
  6. IDEA生成可运行jar包
  7. Python 核心编程(第二版)——条件和循环
  8. 《Java设计模式》之桥接模式
  9. Redis配置和常用命令
  10. C++primer习题--第3章