Linux 字符设备驱动实例
Linux设备驱动之Ioctl控制
Linux设备驱动之Ioctl控制 ⼤部分驱动除了需要具备读写设备的能⼒之外,还需要具备对硬件控制的能⼒。
⼀、在⽤户空间,使⽤ioctl系统调⽤来控制设备,原型如下:int ioctl(int fd,unsigned long cmd,...);/*fd:⽂件描述符cmd:控制命令...:可选参数:插⼊*argp,具体内容依赖于cmd*/ ⽤户程序所作的只是通过命令码告诉驱动程序它想做什么,⾄于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。
⼆、驱动ioctl⽅法:int (*ioctl) (struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg);/*inode与filp两个指针对应于应⽤程序传递的⽂件描述符fd,这和传递open⽅法的参数⼀样。
cmd 由⽤户空间直接不经修改的传递给驱动程序arg 可选。
*/ 在驱动程序中实现的ioctl函数体内,实际上是有⼀个switch {case}结构,每⼀个case对应⼀个命令码,做出⼀些相应的操作。
怎么实现这些操作,这是每⼀个程序员⾃⼰的事情,因为设备都是特定的。
关键在于怎么样组织命令码,因为在ioctl中命令码是唯⼀联系⽤户程序命令和驱动程序⽀持的途径。
在Linux核⼼中是这样定义⼀个命令码的:____________________________________| 设备类型 | 序列号 | ⽅向 | 数据尺⼨ ||----------|--------|------|-------- || 8 bit | 8 bit | 2 bit |8~14 bit||----------|--------|------|-------- | 这样⼀来,⼀个命令就变成了⼀个整数形式的命令码。
但是命令码⾮常的不直观,所以Linux Kernel中提供了⼀些宏,这些宏可根据便于理解的字符串⽣成命令码,或者是从命令码得到⼀些⽤户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送⽅向和数据传输尺⼨。
基于rk3568的linux驱动开发——gpio知识点
基于rk3568的linux驱动开发——gpio知识点基于rk3568的Linux驱动开发——GPIO知识点一、引言GPIO(General Purpose Input/Output)通用输入/输出,是现代计算机系统中的一种常用接口,它可以根据需要配置为输入或输出。
通过GPIO 接口,我们可以与各种外设进行通信,如LED灯、按键、传感器等。
在基于Linux系统的嵌入式设备上开发驱动程序时,熟悉GPIO的使用是非常重要的一环。
本文将以RK3568芯片为例,详细介绍GPIO的相关知识点和在Linux驱动开发中的应用。
二、GPIO概述GPIO是系统中的一个基本的硬件资源,它可以通过软件的方式对其进行配置和控制。
在嵌入式设备中,通常将一部分GPIO引脚连接到外部可编程电路,以实现与外部设备的交互。
在Linux中,GPIO是以字符设备的形式存在,对应的设备驱动为"gpiolib"。
三、GPIO的驱动开发流程1. 导入头文件在驱动程序中,首先需要导入与GPIO相关的头文件。
对于基于RK3568芯片的开发,需要导入头文件"gpiolib.h"。
2. 分配GPIO资源在驱动程序中,需要使用到GPIO资源,如GPIO所在的GPIO Bank和GPIO Index等。
在RK3568芯片中,GPIO资源的分配是通过设备树(Device Tree)来进行的。
在设备树文件中,可以定义GPIO Bank和GPIO Index等信息,以及对应的GPIO方向(输入或输出)、电平(高电平或低电平)等属性。
在驱动程序中,可以通过设备树接口(Device Tree API)来获取这些GPIO资源。
3. GPIO的配置与控制在驱动程序中,首先要进行GPIO的初始化与配置。
可以通过函数"gpiod_get()"来打开指定的GPIO,并判断其是否有效。
如果成功打开GPIO,则可以使用函数"gpiod_direction_output()"或"gpiod_direction_input()"来设置GPIO的方向,分别作为输出或输入。
LinuxI2C驱动--用户态驱动简单示例
LinuxI2C驱动--⽤户态驱动简单⽰例1. Linux内核⽀持I2C通⽤设备驱动(⽤户态驱动:由应⽤层实现对硬件的控制可以称之为⽤户态驱动),实现⽂件位于drivers/i2c/i2c-dev.c,设备⽂件为/dev/i2c-02. I2C通⽤设备驱动以字符设备注册进内核的static const struct file_operations i2cdev_fops = {.owner = THIS_MODULE,.llseek = no_llseek,.read = i2cdev_read,.write = i2cdev_write,.unlocked_ioctl = i2cdev_ioctl,.open = i2cdev_open,.release = i2cdev_release,};res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);3. 对设备⽂件进⾏读写时,可以调⽤read、write或者ioctl等⽅法,他们都是通过调⽤函数i2c_transfer来实现对I2C设备的操作的int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num){int ret;/* REVISIT the fault reporting model here is weak:** - When we get an error after receiving N bytes from a slave,* there is no way to report "N".** - When we get a NAK after transmitting N bytes to a slave,* there is no way to report "N" ... or to let the master* continue executing the rest of this combined message, if* that's the appropriate response.** - When for example "num" is two and we successfully complete* the first message but get an error part way through the* second, it's unclear whether that should be reported as* one (discarding status on the second message) or errno* (discarding status on the first one).*/if (adap->algo->master_xfer) {#ifdef DEBUGfor (ret = 0; ret < num; ret++) {dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, ""len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD)'R' : 'W', msgs[ret].addr, msgs[ret].len,(msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");}#endifif (in_atomic() || irqs_disabled()) {ret = mutex_trylock(&adap->bus_lock);if (!ret)/* I2C activity is ongoing. */return -EAGAIN;} else {mutex_lock_nested(&adap->bus_lock, adap->level);}ret = adap->algo->master_xfer(adap,msgs,num);mutex_unlock(&adap->bus_lock);return ret;} else {dev_dbg(&adap->dev, "I2C level transfers not supported\n");return -EOPNOTSUPP;}}4. i2c_transfer通过代码可以看出,i2c_transfer 通过调⽤相应的 adapter 的 master_xfer ⽅法实现的,⽽ master_xfer 主要是根据 struct i2c_msg 类型的msgs来进⾏处理的。
实验二:字符设备驱动实验
实验二:字符设备驱动实验一、实验目的通过本实验的学习,了解Linux操作系统中的字符设备驱动程序结构,并能编写简单的字符设备的驱动程序以及对所编写的设备驱动程序进行测试,最终了解Linux操作系统如何管理字符设备。
二、准备知识字符设备驱动程序主要包括初始化字符设备、字符设备的I/O调用和中断服务程序。
在字符设备驱动程序的file_operations结构中,需要定义字符设备的基本入口点。
open()函数;release()函数read()函数write()函数ioctl()函数select()函数。
另外,注册字符设备驱动程序的函数为register_chrdev()。
register_chrdev() 原型如下:int register_chrdev(unsigned int major, //主设备号const char *name, //设备名称struct file_operations *ops); //指向设备操作函数指针其中major是设备驱动程序向系统申请的主设备号。
如果major为0,则系统为该驱动程序动态分配一个空闲的主设备号。
name是设备名称,ops是指向设备操作函数的指针。
注销字符设备驱动程序的函数是unregister_chrdev(),原型如下:int unregister_chrdev(unsigned int major,const char *name);字符设备注册后,必须在文件系统中为其创建一个设备文件。
该设备文件可以在/dev目录中创建,每个设备文件代表一个具体的设备。
使用mknod命令来创建设备文件。
创建设备文件时需要使用设备的主设备号和从设备号作为参数。
阅读教材相关章节知识,了解字符设备的驱动程序结构。
三、实验内容根据教材提供的实例。
编写一个简单的字符设备驱动程序。
要求该字符设备包括open()、write()、read()、ioctl()和release()五个基本操作,并编写一个测试程序来测试所编写的字符设备驱动程序。
linux字符驱动框架(用户态的read,write,poll是怎么操作驱动的)
linux字符驱动框架(⽤户态的read,write,poll是怎么操作驱动的)前⾔这篇⽂章是通过对⼀个简单字符设备驱动的操作来解释,⽤户态的读写操作是怎么映射到具体设备的。
因为针对不同版本的linux内核,驱动的接⼝函数⼀直有变化,这贴出我测试的系统信息:root@ubuntu:~/share/dev/cdev-2# cat /etc/os-release |grep -i verVERSION="16.04.5 LTS (Xenial Xerus)"VERSION_ID="16.04"VERSION_CODENAME=xenialroot@ubuntu:~/share/dev/cdev-2#root@ubuntu:~/share/dev/cdev-2# uname -r4.15.0-33-generic字符驱动这⾥给出了⼀个不怎么标准的驱动,定义了⼀个结构体 struct dev,其中buffer成员模拟驱动的寄存器。
由wr,rd作为读写指针,len作为缓存buffer的长度。
具体步骤如下:1. 定义 init 函数,exit函数,这是在 insmod,rmmod时候调⽤的。
2. 定义驱动打开函数open,这是在⽤户态打开设备时候调⽤的。
3. 定义release函数,这是在⽤户态关闭设备时候⽤到的。
4. 定义read,write,poll函数,并挂接到 file_operations结构体中,所有⽤户态的read,write,poll都会最终调到这些函数。
chardev.c/*参考:深⼊浅出linux设备驱动开发*/#include <linux/module.h>#include <linux/init.h>#include <linux/fs.h>#include <linux/uaccess.h>#include <linux/wait.h>#include <linux/semaphore.h>#include <linux/sched.h>#include <linux/cdev.h>#include <linux/types.h>#include <linux/kdev_t.h>#include <linux/device.h>#include <linux/poll.h>#define MAXNUM 100#define MAJOR_NUM 400 //主设备号 ,没有被使⽤struct dev{struct cdev devm; //字符设备struct semaphore sem;int flag;poll_table* table;wait_queue_head_t outq;//等待队列,实现阻塞操作char buffer[MAXNUM+1]; //字符缓冲区char *rd,*wr,*end; //读,写,尾指针}globalvar;static struct class *my_class;int major=MAJOR_NUM;static ssize_t globalvar_read(struct file *,char *,size_t ,loff_t *);static ssize_t globalvar_write(struct file *,const char *,size_t ,loff_t *);static int globalvar_open(struct inode *inode,struct file *filp);static int globalvar_release(struct inode *inode,struct file *filp);static unsigned int globalvar_poll(struct file* filp, poll_table* wait);/*结构体file_operations在头⽂件 linux/fs.h中定义,⽤来存储驱动内核模块提供的对设备进⾏各种操作的函数的指针。
基于linux2.6内核的字符设备驱动程序设计
, sr t t uc
o ne = TH I M ODULE; w r S
… … … … …
//获 取 字 符 设 备 号
2ce d v结构体
存 ln x2. 核 中 , 用 c e 结 构 体 描 iu 6内 使 dv 述 一 个 字 符 设 备 , d v结 构 体 定 义 如 下 : ce
.
_
的 结构 体 , 中 包 含设 备所 涉 及 到 的c e 其 d v、 私有数据及信号量等信息 。 下所 示: 如 / 设 备 结 构 体 /
sr t t uc XXX—d v t —e
— —
{, i e t l f t ) sz , of ;
_ —
f
s r t de c v; t uc c v de
s ie sz
— —
ቤተ መጻሕፍቲ ባይዱ
/ /从 设 备 中 同 步 读 取 数 据 t* ie(tutf e} h r u r (wrt) rc i .c a s s l e
—
}, i e t, l f t { ; sz of )
//该 设 备 其 他 的 私 有 数 据 和 信 号 量 的 信 息 的 定 义
… …
/ /向 设 备 发 送 数 据 u sg e n * o1 sr c i ,src n in d it(p l(tutfl ) e十 tu t
p l t bl sr c ) o l a e tu t :
—
}X —e X X d v; 模 块 加 载 和 卸 载 函数 的 形 式 如 下 : /+ 备 驱 动 模 块 加 载 函 数 / 设
・
计 算机技 术 ・
基 于 l u 26 i x . 内核 的字 符 设 备驱 动程 序设 计 n
字符设备驱动(1)驱动代码完整源码:charButtons.c
字符设备驱动(1)驱动代码完整源码:charButtons.c 内核版本:Linux3.0.8开发板:基于三星S5PV210处理器的Tiny210开发板驱动名称:charButtons.c驱动描述:按键触发中断,中断处理程序执⾏相应的简单LED点亮操作⽅案1注册字符设备使⽤新的接⼝实现(需要好⼏个函数来实现。
貌似更复杂)⽅案2注册字符设备使⽤⽼的接⼝实现(貌似⽼接⼝更简单)/*****************************************************************************简述:简单字符型驱动程序,⼿动静态分配设备号,⼿动创建设备节点******************************************************************************/#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/cdev.h>#include <linux/fs.h>#include <linux/wait.h>#include <linux/poll.h>#include <linux/sched.h>#include <linux/irq.h>#include <asm/irq.h>#include <linux/interrupt.h>#include <mach/map.h>#include <mach/gpio.h>#include <mach/regs-gpio.h>#include <plat/gpio-cfg.h>#include <linux/slab.h>#define DEVICE_NAME "buttons"struct button_desc {int gpio;int number;char *name;};struct led_desc {int gpio;int number;char *name;};static struct button_desc buttons[] = {{ S5PV210_GPH2(0), 0, "KEY0" },{ S5PV210_GPH2(1), 1, "KEY1" },{ S5PV210_GPH2(2), 2, "KEY2" },{ S5PV210_GPH2(3), 3, "KEY3" },{ S5PV210_GPH3(0), 4, "KEY4" },{ S5PV210_GPH3(1), 5, "KEY5" },{ S5PV210_GPH3(2), 6, "KEY6" },{ S5PV210_GPH3(3), 7, "KEY7" },};static struct led_desc leds[] = {{S5PV210_GPJ2(0),1,"LED1"},{S5PV210_GPJ2(1),2,"LED2"},{S5PV210_GPJ2(2),3,"LED3"},{S5PV210_GPJ2(3),4,"LED4"},};#define OK (0)#define ERROR (-1)struct gpio_chip *chip;struct cdev *gDev;struct file_operations *gFile;dev_t devNum;unsigned int subDevNum = 1;//要申请的次设备号个数int reg_major = 234;int reg_minor = 0;static irqreturn_t button_interrupt(int irq, void *dev_id){struct button_desc *bdata = (struct button_desc *)dev_id;int down;unsigned tmp;tmp = gpio_get_value(bdata->gpio);/* active low */down = !tmp;printk("KEY %d: %08x\n", bdata->number, down);if(bdata->number < 4){gpio_set_value(leds[bdata->number].gpio,0);printk("LED %d: On \n",leds[bdata->number].number);}else{gpio_set_value(leds[(bdata->number) - 4].gpio,1);printk("LED %d: OFF \n",leds[(bdata->number)-4].number); }return IRQ_HANDLED;}int butsOpen(struct inode *p, struct file *f){int irq;int i;int err = 0;printk(KERN_EMERG"butsOpen\r\n");for (i = 0; i < ARRAY_SIZE(buttons); i++) {if (!buttons[i].gpio)continue;irq = gpio_to_irq(buttons[i].gpio);// irq = IRQ_EINT(16)+i =160+i "S5PV210_GPH2(i)"//irq = IRQ_EINT(24)+i =168+i "S5PV210_GPH3(i)"err = request_irq(irq, button_interrupt, IRQ_TYPE_EDGE_BOTH,buttons[i].name, (void *)&buttons[i]);if (err)break;}for(i = 0; i<ARRAY_SIZE(leds);i++){if(!leds[i].gpio)continue;gpio_direction_output(leds[i].gpio,1);}if (err) {i--;for (; i >= 0; i--) {if (!buttons[i].gpio)continue;irq = gpio_to_irq(buttons[i].gpio);disable_irq(irq);free_irq(irq, (void *)&buttons[i]);}return -EBUSY;}return0;}int charDrvInit(void){devNum = MKDEV(reg_major, reg_minor);printk(KERN_EMERG"devNum is %d\r\n", devNum);if(OK == register_chrdev_region(devNum, subDevNum, DEVICE_NAME)) {printk(KERN_EMERG"register_chrdev_region ok\r\n");}else{printk(KERN_EMERG"register_chrdev_region error\r\n");return ERROR;}/*if(OK == alloc_chrdev_region(&devNum, subDevNum, subDevNum,"test")) {printk(KERN_EMERG"register_chrdev_region ok\r\n");}else{printk(KERN_EMERG"register_chrdev_region error\r\n");return ERROR;}*/gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL);gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL);gFile->open = butsOpen;//注册设备函数到file_operations结构体gFile//gDev->owner = THIS_MODULE;gFile->owner = THIS_MODULE;cdev_init(gDev, gFile);//在cdev结构体中添加指针指向file_operations结构体gFilecdev_add(gDev, devNum, 3);//建⽴设备号与cdev结构体联系printk(KERN_EMERG"button driver initial done...\r\n");return0;}void __exit charDrvExit(void){int i,irq;cdev_del(gDev);unregister_chrdev_region(devNum, subDevNum);for (i = 0; i < ARRAY_SIZE(buttons); i++) {if (!buttons[i].gpio)continue;irq = gpio_to_irq(buttons[i].gpio);disable_irq(irq);free_irq(irq, (void *)&buttons[i]);}return;}module_init(charDrvInit);//执⾏insmod时会执⾏此⾏代码并调⽤charDrvInit,驱动开始module_exit(charDrvExit);//执⾏rmmod时,结束MODULE_LICENSE("GPL");代码实现⽅案1/*****************************************************************************简述:简单字符型驱动程序,⼿动静态分配设备号,⼿动创建设备节点******************************************************************************/#include <linux/module.h>#include <linux/fs.h>#include <mach/gpio.h>#include <linux/irq.h>#include <linux/kdev_t.h>#include <linux/interrupt.h>#include <linux/init.h>#define DEVICE_NAME "leds"struct button_desc {int gpio;int number;char *name;};struct led_desc {int gpio;int number;char *name;};static struct button_desc buttons[] = {{ S5PV210_GPH2(0), 0, "KEY0" },{ S5PV210_GPH2(1), 1, "KEY1" },{ S5PV210_GPH2(2), 2, "KEY2" },{ S5PV210_GPH2(3), 3, "KEY3" },{ S5PV210_GPH3(0), 4, "KEY4" },{ S5PV210_GPH3(1), 5, "KEY5" },{ S5PV210_GPH3(2), 6, "KEY6" },{ S5PV210_GPH3(3), 7, "KEY7" },};static struct led_desc leds[] = {{S5PV210_GPJ2(0),1,"LED1"},{S5PV210_GPJ2(1),2,"LED2"},{S5PV210_GPJ2(2),3,"LED3"},{S5PV210_GPJ2(3),4,"LED4"},};#define OK (0)#define ERROR (-1)dev_t devNum;unsigned int subDevNum = 1;//要申请的次设备号个数int reg_major = 234;int reg_minor = 0;static irqreturn_t button_interrupt(int irq, void *dev_id){struct button_desc *bdata = (struct button_desc *)dev_id;int down;unsigned tmp;tmp = gpio_get_value(bdata->gpio);/* active low */down = !tmp;printk("KEY %d: %08x\n", bdata->number, down);if(bdata->number < 4){gpio_set_value(leds[bdata->number].gpio,0);printk("LED %d: On \n",leds[bdata->number].number);}else{gpio_set_value(leds[(bdata->number) - 4].gpio,1);printk("LED %d: OFF \n",leds[(bdata->number)-4].number); }return IRQ_HANDLED;}int butsOpen(struct inode *p, struct file *f){int irq;int i;int err = 0;printk(KERN_EMERG"butsOpen\r\n");for (i = 0; i < ARRAY_SIZE(buttons); i++) {if (!buttons[i].gpio)continue;irq = gpio_to_irq(buttons[i].gpio);// irq = IRQ_EINT(16)+i =160+i "S5PV210_GPH2(i)"//irq = IRQ_EINT(24)+i =168+i "S5PV210_GPH3(i)"err = request_irq(irq, button_interrupt, IRQ_TYPE_EDGE_FALLING, buttons[i].name, (void *)&buttons[i]);if (err)break;}for(i = 0; i<ARRAY_SIZE(leds);i++){if(!leds[i].gpio)continue;gpio_direction_output(leds[i].gpio,1);}if (err) {i--;for (; i >= 0; i--) {if (!buttons[i].gpio)continue;irq = gpio_to_irq(buttons[i].gpio);disable_irq(irq);free_irq(irq, (void *)&buttons[i]);}return -EBUSY;}return0;}static const struct file_operations gFile ={.owner = THIS_MODULE,.open = butsOpen,};int charDrvInit(void){devNum = MKDEV(reg_major, reg_minor);printk(KERN_EMERG"devNum is %d\r\n", devNum);if(OK == register_chrdev(reg_major, DEVICE_NAME, &gFile)){printk(KERN_EMERG "register_chrdev_region ok\r\n");}else{printk(KERN_EMERG"register_chrdev_region error\r\n");return ERROR;}printk(KERN_EMERG "button driver initial done...\r\n");return0;}void __exit charDrvExit(void){int i,irq;unregister_chrdev(reg_major, DEVICE_NAME);for (i = 0; i < ARRAY_SIZE(buttons); i++) {if (!buttons[i].gpio)continue;irq = gpio_to_irq(buttons[i].gpio);disable_irq(irq);free_irq(irq, (void *)&buttons[i]);}return;}module_init(charDrvInit);//执⾏insmod时会执⾏此⾏代码并调⽤charDrvInit,驱动开始module_exit(charDrvExit);//执⾏rmmod时,结束MODULE_LICENSE("GPL");MODULE_AUTHOR("LiuB");代码实现⽅案2函数修饰符__init,本质上是⼀个宏定义,在内核源代码中定义:#define __init __section(.init.text) __cold notrace作⽤就是,将被它修饰的函数放⼊.init.text段中去。
设备驱动——字符设备驱动
一、字符设备驱动重要数据结构:struct file_operations在<linux/fs.h>定义如下:struct file_operations {struct module *owner; // 拥有该结构的模块的指针,一般为THIS_MODULES loff_t (*llseek) (struct file *,loff_t ,int); ssize_t (*read) (struct file *filp, char *buff, size_t count, loff_t *offp); ssize_t (*write) (struct file *filp, const char *buff, size_t count, loff_t *offp);int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *); int (*fasync) (int, struct file *, int); int (*check_media_change) (kdev_t dev); int (*revalidate) (kdev_t dev); int (*lock) (struct file *, int, struct file_lock *); }; srtuct file数据结构定义如下:struct file { mode_t f_mode;/*标识文件是否可读或可写,FMODE_READ或FMODE_WRITE*/ dev_t f_rdev; /* 用于/dev/tty */ off_t f_pos; /* 当前文件位移 */ unsigned short f_flags; /* 文件标志,如O_RDONLY、O_NONBLOCK和O_SYNC */ unsigned short f_count; /* 打开的文件数目 */ unsigned short f_reada; struct inode *f_inode; /*指向inode的结构指针 */ struct file_operations *f_op;/* 文件索引指针 */ };1、字符设备驱动编写流程:(1)定义加载驱动模块接口 module_init(call_init);(2)定义file_operations结构变量并实现结构函数(3)编写初始化call_init()函数,在该函数中注册设备(4)定义卸载驱动模块入口module_exit(call_exit);(5)编写call_exit()函数,在该函数中注销设备;实例:dev.c#include <linux/module.h>#include <linux/fs.h>#include <linux/kernel.h>#define DEV_NAME "calldev"#define DEV_MAJOR 240loff_t call_llseek (struct file *filp,loff_t off,int whence){printk("call llseek ->off :%08x,whence :%08x \n",off,whence);return 0x23;}ssize_t call_read (struct file *filp,char *buff,size_t count,loff_t *offp){printk("call read --->buf :%08x,count :%08x \n",buff,count);return 0x33;}ssize_t call_write (struct file *filp,const char *buf,size_t count,loff_t *f_pos){printk("call write --->buf :%08x , count :%08x \n",buf,count);return 0x43;}int call_ioctl (struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg){printk("call ioctl --->cmd :%08x ,arg :%08x \n",cmd,arg);return 0x53;}int call_open(struct inode *inode ,struct file *filp){int num = MINOR(inode->i_rdev);printk("call open-->minor:%d \n",num);return 0;}int call_release(struct inode *inode ,struct file *filp){printk("callrelease\n");return 0;}struct file_operations dev_fops ={.owner = THIS_MODULE,.llseek = call_llseek,.read = call_read,.write = call_write,.ioctl = call_ioctl,.open = call_open,.release = call_release,};int call_init(void){int dev;printk("call_dev init\n");dev = register_chrdev(DEV_MAJOR,DEV_NAME,&dev_fops);if(dev < 0){printk("register_chrdev failed\n");return dev;}return 0;}void call_exit(void){printk("call_dev exit\n");unregister_chrdev(DEV_MAJOR,DEV_NAME);}module_init(call_init);module_exit(call_exit);MODULE_LICENSE("DUAL BSD?GPL");2、Makefile编写:obj-m := dev.oKDIR := /lib/modules/¥(shell uname -r)/buildPWD := ¥(shell pwd)default :¥(MAKE) -C ¥(KDIR) SUBDIRS=¥(PWD) modulesclean :rm -rf *.korm -rf *.mod.*rm -rf .*.cmdrm -rf *.o编译驱动程序:make clean;make加载驱动:insmod dev.ko卸载驱动:rmmod dev.ko查看设备的主设备号:cat /proc/devices创建设备文件结点:mknod /dev/calldev c 240 03、编写测试驱动程序,并编译运行实例:#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/ioctl.h>#include <fcntl.h>#include <unistd.h>#define DEVICE_FILENAME "/dev/calldev"int main(){int dev;char buff[128];int ret;printf("1-->device file open\n");dev = open(DEVICE_FILENAME,O_RDWR | O_NDELAY);if (dev > 0){printf("2-->seek function call\n");ret = lseek(dev,0x20,SEEK_SET);printf("ret = %08x \n",ret);printf("3-->read function call\n");ret = read(dev,0x30,0x31);printf("ret = %08x \n",ret);printf("4-->write function call\n");ret = write(dev,0x40,0x41);printf("ret = %08x \n",ret);printf("5-->ioctl function call\n");ret = ioctl(dev,0x50,0x51);printf("ret = %08x \n",ret);printf("6-->close function call\n");ret = close(dev);printf("ret = %08x \n",ret);}return 0;}。
I2C实例解析
实例解析linux内核I2C体系结构(1)一、概述谈到在linux系统下编写I2C驱动,目前主要有两种方式,一种是把I2C设备当作一个普通的字符设备来处理,另一种是利用linux I2C驱动体系结构来完成。
下面比较下这两种驱动。
第一种方法的好处(对应第二种方法的劣势)有:●思路比较直接,不需要花时间去了解linux内核中复杂的I2C子系统的操作方法。
第一种方法问题(对应第二种方法的好处)有:●要求工程师不仅要对I2C设备的操作熟悉,而且要熟悉I2C的适配器操作;●要求工程师对I2C的设备器及I2C的设备操作方法都比较熟悉,最重要的是写出的程序可移植性差;●对内核的资源无法直接使用。
因为内核提供的所有I2C设备器及设备驱动都是基于I2C子系统的格式。
I2C适配器的操作简单还好,如果遇到复杂的I2C适配器(如:基于PCI的I2C适配器),工作量就会大很多。
本文针对的对象是熟悉I2C协议,并且想使用linux内核子系统的开发人员。
网络和一些书籍上有介绍I2C子系统的源码结构。
但发现很多开发人员看了这些文章后,还是不清楚自己究竟该做些什么。
究其原因还是没弄清楚I2C子系统为我们做了些什么,以及我们怎样利用I2C子系统。
本文首先要解决是如何利用现有内核支持的I2C适配器,完成对I2C设备的操作,然后再过度到适配器代码的编写。
本文主要从解决问题的角度去写,不会涉及特别详细的代码跟踪。
二、I2C设备驱动程序编写首先要明确适配器驱动的作用是让我们能够通过它发出符合I2C标准协议的时序。
在Linux内核源代码中的drivers/i2c/busses目录下包含着一些适配器的驱动。
如S3C2410的驱动i2c-s3c2410.c。
当适配器加载到内核后,接下来的工作就要针对具体的设备编写设备驱动了。
编写I2C设备驱动也有两种方法。
一种是利用系统给我们提供的i2c-dev.c来实现一个i2c 适配器的设备文件。
然后通过在应用层操作i2c适配器来控制i2c设备。
精通Linux设备驱动程序开发-第7章-输入设备驱动
第7章 输入设备驱动内核的输入子系统是为了对分散的、多种不同类别的输入设备(如键盘、鼠标、跟踪球、操纵杆、辊轮、触摸屏、加速计和手写板)进行统一处理的驱动。
输入子系统带来了如下好处:•统一了物理形态各异的相似的输入设备的处理功能。
例如,各种鼠标,不论PS/2、USB,还是蓝牙,都被同样处理。
•提供了用于分发输入报告给用户应用程序的简单的事件(event)接口。
你的驱动不必创建、管理/dev节点以及相关的访问方法。
因此它能很方便的调用输入API以发送鼠标移动、键盘按键,或触摸事件给用户空间。
X Windows这样的应用程序能够无缝地运行于输入子系统提供的event接口之上。
•抽取出了输入驱动的通用部分,简化了驱动,并提供了一致性。
例如,输入子系统提供了一个底层驱动(成为serio)的集合,支持对串口和键盘控制器等硬件输入设备的访问。
图7.1展示了输入子系统的操作。
此子系统包括一前一后运行的两类驱动:事件驱动和设备驱动。
事件驱动负责和应用程序的接口,而设备驱动负责和底层输入设备的通信。
鼠标事件产生者mousedev,是前者的实例;而PS/2鼠标驱动是后者的实例。
事件驱动和设备驱动都可以利用输入子系统的高效、可重用的核心提供的服务。
图 7.1. 输入子系统事件驱动是标准的,对所有的输入类都是可用的,所以你更可能的是实现设备驱动而不是事件驱动。
你的设备驱动可以利用一个已经存在的、合适的事件驱动通过输入核心和用户应用程序接口。
需要注意的是本章使用的名辞“设备驱动”指的是输入设备驱动,而不是输入事件驱动。
输入事件驱动输入子系统提供的事件接口已经发展成为很多图形窗口系统理解的标准。
事件驱动提供一个硬件无关的抽象,以和输入设备交互;如同帧缓冲接口(在第12章《视频设备驱动》中讨论)提供一个通用的机制以和显示设备通信一样。
事件驱动和帧缓冲驱动一起,将图形用户接口(GUI)和各种各样的底层硬件隔离开来。
Evdev接口Evdev是一个通用的输入事件驱动。
Linux设备管理(二)_从cdev_add说起
Linux设备管理(⼆)_从cdev_add说起我在⼀⽂中已经简单的介绍了字符设备驱动的基本的编程框架,这⾥我们来探讨⼀下Linux内核(以4.8.5内核为例)是怎么管理字符设备的,即当我们获得了设备号,分配了cdev结构,注册了驱动的操作⽅法集,最后进⾏cdev_add()的时候,究竟是将哪些内容告诉了内核,内核⼜是怎么管理我的cdev结构的,这就是本⽂要讨论的内容。
我们知道,Linux 内核对设备的管理是基于kobject的(参见),这点从我们的cdev结构中就可以看出,所以,接下来,你将看到"fs/char_dev.c"中实现的操作字符设备的函数都是基于"lib/kobject.c"以及"drivers/base/map.c"中对kobject操作的函数。
好,现在我们从cdev_add()开始⼀层层的扒。
cdev_map对象//fs/char_dev.c27 static struct kobj_map *cdev_map;内核中关于字符设备的操作函数的实现放在"fs/char_dev.c"中,打开这个⽂件,⾸先注意到就是这个在内核中不常见的静态全局变量cdev_map(27),我们知道,为了提⾼软件的内聚性,Linux内核在设计的时候尽量避免使⽤全局变量作为函数间数据传递的⽅式,⽽建议多使⽤形参列表,⽽这个结构体变量在这个⽂件中到处被使⽤,所以它应该是描述了系统中所有字符设备的某种信息,带着这样的想法,我们可以在"drivers/base/map.c"中找到kobj_map结构的定义://drivers/base/map.c19 struct kobj_map {20 struct probe {21 struct probe *next;22 dev_t dev;23 unsigned long range;24 struct module *owner;25 kobj_probe_t *get;26 int (*lock)(dev_t, void *);27 void *data;28 } *probes[255];29 struct mutex *lock;30 };从中可以看出,kobj_map的核⼼就是⼀个struct probe类型、⼤⼩为255的数组,⽽在这个probe结构中,第⼀个成员next(21)显然是将这些probe结构通过链表的形式连接起来,dev_t类型的成员dev显然是设备号,get(25)和lock(26)分别是两个函数接⼝,最后的重点来了,void作为C语⾔中的万⾦油类型,在这⾥就是我们cdev结构(通过后⾯的分析可以看出),所以,这个cdev_map是⼀个struct kobj_map类型的指针,其中包含着⼀个struct probe*类型、⼤⼩为255的数组,数组的每个元素指向的⼀个probe结构封装了⼀个设备号和相应的设备对象(这⾥就是cdev),下图中体现两种常见的对设备号和cdev管理的⽅式,其⼀是⼀个cdev对象对应这⼀个/多个设备号的情况, 在cdev_map中, ⼀个probes对象就对应⼀个主设备号,多个设备号对应⼀个cdev时,其实只是次设备号在变,主设备号还是⼀样的,所以是同⼀个probes对象;其⼆是当主设备号超过255时,会进⾏probe复⽤,此时probe->next就派上了⽤场,⽐如probe[200],可以表⽰设备号200,455...3895等所有对255取余是200的数字, 参见下⽂的kobj_map--58--。
Linux字符设备驱动开发方法与应用实例
 ̄l a0 re) led 和w i ( : r t 调用等 ; 还包含对用户发出的控制请求
的响 应 , 比如 启动 或 关 闭 设备 等 。由于用 户发 出 的I 功 / O 能 调 用 和控 制请 求 是在 确 定 的 时间 内发 生 , 以它是 同 所 步 事 件 , 执 行 的先 后 顺 序上 看 , 在 中断 程 序 的后 面 从 它
P  ̄ 的并 口控制卡经常使用 的2 / 并 口基地 址 cJ L 个I O 是0 3 8 x 7 , x 7 和0 2 8 在并 口通信 中使 用的电平信号 是标
准的T L T 电平 : 伏和5 我们要把驱动程序 的下部分 0 伏。
被执行, 所以把它称为驱动程序的下半部 。
FI NANCI AL coM PUTE
驱动 程序的代码按照执 行时 间是 否确定, 可将其分为
同步 执 行 和 异 步 执 行 的 代码 。 Ln x 在 iu 内核 中把 同步 执
行 的代码 称为驱动程序 的下半部 , 把异步执 行的代码
个十字路 口的交通灯。
由微 机 接 口技 术 可知 , 口的 最 小 配 置 由一 些 8 并 位 的 端 口组 成 。 到输 出 端 口的 数 据 , 现 为 2 脚 D 插 写 表 5 型
一
捅到时间队列, 这将使用Ln x i 的定时器报时, u 即每秒中
断 l0 。 0次
些总结 , 也编 写 了一个 应 用 实例 , 细 讨 论 了与 操 作 详
系统的软件 接口部分, 为那些想将其他 系统下的驱动程 序移植 ̄ Ln x J l iu下的人员提供参 考。 该文参 ̄Ln x24 i 一. u 的内核源代码提供有关数据结构和函数。
ch341a在linux系统中的驱动编写
在Linux系统中编写ch341a驱动,首先需要了解ch341a的硬件接口和通信协议。
然后,可以使用Linux内核提供的设备驱动框架进行开发。
以下是一个简单的示例:1. 首先,创建一个名为`ch341a.c`的文件,用于编写驱动代码:```c#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/uaccess.h>#include <linux/init.h>#include <linux/platform_device.h>#include <linux/gpio.h>#include <linux/interrupt.h>#include <linux/delay.h>#define CH341A_GPIO_PIN 4 // 根据实际情况修改引脚号static int ch341a_probe(struct platform_device *pdev){int ret;ret = gpio_request(CH341A_GPIO_PIN, "ch341a");if (ret) {dev_err(&pdev->dev, "Failed to request GPIO pin %d", CH341A_GPIO_PIN);return ret;}// 初始化ch341a设备,例如设置波特率、数据位等// ...return 0;}static int ch341a_remove(struct platform_device *pdev){gpio_free(CH341A_GPIO_PIN);// 释放ch341a设备资源,例如关闭串口等// ...return 0;}static const struct of_device_id ch341a_of_match[] = { { .compatible = "ch341a", },{ /* sentinel */ }};MODULE_DEVICE_TABLE(of, ch341a_of_match);static struct platform_driver ch341a_driver = {.probe = ch341a_probe,.remove = ch341a_remove,.driver = {.name = "ch341a",.of_match_table = ch341a_of_match,},};module_platform_driver(ch341a_driver);MODULE_LICENSE("GPL");MODULE_DESCRIPTION("Driver for ch341a USB-Serial adapter"); MODULE_AUTHOR("Your Name");```2. 编译驱动模块:```bashmake -C /lib/modules/$(uname -r)/build M=$(pwd) modules```3. 加载驱动模块:```bashsudo insmod ch341a.ko```4. 卸载驱动模块:```bashsudo rmmod ch341a```。
设备驱动程序-案例一
c:字符设备 主设备号 b:块设备 1 root root 1, 1 root root 1, 1 root root 1, 1 root root 1, 1 root root 1, 1 root root 1, 1 root root 1, 1 root root 1, 1 root root 5, 1 root root 5, 1 root root 5, 1 root root 2 root root 1 root root 1 root root 1 root root 1 root root 4, 1 root root 4, 1 root root 4, 1 root root 4, 1 root root 4, 数字集成电路设计与系统应用专业 1 root root
file_operations是一个结构体类型,定义在include/linux/fs.h中。 上述代码定义了一个file_operations类型的结构体spioc_fops,并将 其中的一些成员赋了初值。由于spioc_fops是一个静态变量,所以其他成员 的初值是“零”。
结构体spioc_fops将作为一个参数在注册一个设备驱动程序时传递给内核。 内核使用设备链表维护各种注册的设备。不同类型的设备使用不同的链表。
ioctl()函数用于控制驱动程序本身的一些特性和参数,如设定驱动 程序使用的缓冲区的大小,设定串行通讯的速率等。 数字集成电路设计与系统应用专业
11
static int spioc_open(struct inode *inode, struct file *filp) { /* 这里是open函数的代码 */ return ret; } static int spioc_close(struct inode *inode, struct file *filp) { /* 这里是close函数的代码 */ return ret; }
高级字符驱动(llseek以及ioctl函数)
一、ioctl函数大部分设备除了读写能力,还可进行超出简单的数据传输之外的操作,所以设备驱动也必须具备进行各种硬件控制操作的能力. 这些操作常常通过 ioctl 方法来支持,它有和用户空间版本不同的原型:int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);需要注意的是:不管可选的参数arg是否由用户给定为一个整数或一个指针,它都以一个unsigned long的形式传递。
如果调用程序不传递arg参数, 被驱动收到的 arg 值是未定义的。
因为在arg参数上的类型检查被关闭了,所以若一个非法参数传递给 ioctl,编译器是无法报警的,且任何关联的错误难以查找.二、定位设备(llseek函数)llseek是修改文件中的当前读写位置的系统调用,内核中的缺省的实现进行移位通过修改 filp->f_pos, 这是文件中的当前读写位置。
对于 lseek 系统调用要正确工作,读和写方法必须通过更新它们收到的偏移量来配合。
如果设备是不允许移位的,你不能只制止声明 llseek 操作,因为缺省的方法允许移位。
应当在你的 open 方法中,通过调用 nonseekable_open 通知内核你的设备不支持 llseek :三、实例代码*************************************************** @author: Jaguar.Yuan* @date: 2010-8-7* @describion: 加入锁机制的高级字符设备驱动,同时实现对llseek以及ioctl的测试,并通过使用module_param()实现对关键数据的自定义输入。
* @filename: scull2.c***************************************************/#include <linux/kernel.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/errno.h>#include <linux/sched.h>#include <linux/init.h>#include <linux/cdev.h>#include <asm/io.h>#include <asm/system.h>#include <asm/uaccess.h>#include "scull01.h"#define SCULL2_SIZE 0x1000 /*定义全局内存最大4K字节*/ #define MEM_CLEAR 0X1 /*清0全局内存*/#define SCULL2_MAJOR 0 /*定义为动态分配主设备号*/static int scull2_major = SCULL2_MAJOR; int scull2_minor = 0; int scull2_nr_devs = SCULL_NR_DEVS;//设备数量int scull2_quantum = SCULL_QUANTUM; int scull2_qset = SCULL_QSET;/*scull2结构体定义*/ struct scull2_qset { void **data; struct scull2_qset *next; };struct scull2_dev { struct scull2_qset *data; /* 指向quantum set */ int quantum; /* 当前quantum大小*/ int qset; /* 当前结构中数组大小*/ unsigned long size; /* 存储数据量大小*/ unsigned int access_key; /* 被sculluid 和 scullpriv调用 */ struct semaphore sem; /* 信号量声明*/ struct cdev cdev; /* 字符设备结构体 */ };/*struct semaphore {spinlock_t lock;//自旋锁类型unsigned int count;//信号量计数struct list_head wait_list;//双向链表结构等待队列,即当需要等待信号量时,调用进程把自己加入到等待队列中,然后进入睡眠状态.};*/struct scull2_dev *scull2_devices;//设备结构体指针//清空scull2设备内容;必须调用与信号设备。
Linux设备驱动程序DF
Linux设备驱动程序
04/05/2006 应忍冬
内容
• • • • • • • • • 设备分类 设备驱动程序的框架 字符型设备 网络设备 文件系统
– User Spacuffer例子和使用 Debug原理和Debug方法 常用设备/fb/ram/loopback/zero
设备驱动程序内访问设备地址
• 设备驱动程序可以通过指针访问设备地址 • 设备驱动程序接触到的还是虚拟地址,但 对于外界设备有固定的设备地址映射(设 备的地址在移植Linux时候确定) 设备驱动程序
虚拟地址映射
设备地址映射
设备驱动程序
虚拟地址映射
设备地址映射
物理内存地址空间
设备地址空间
直接访问IO端口 vs 设备驱动程序
设备驱动程序的任务
• • • • 设备初始化 硬件操作和管理 外部硬件和内核空间的数据传递 内核空间和用户空间的数据传递
设备驱动程序的功能
用 户 空 间 内 核 空 间
用户程序
程序
用户态程序 vs 内核态程序
用户程序 • 权限受限 • 虚拟运行环境
–逻辑地址 –关键资源访问受监管
内核程序 • 最高权限 • 实际的运行环境
生成o文件
设备装载和设备文件建立
• chmod +x /tmp/LED.o • /sbin/insmod -f ./LED.o • cat /proc/devices得到装入内核的主 设备号 • mknod /dev/Lamp c Num1 Num2 Num1为主设备号 Num2为次设备号 强制安装,忽略版本检查
Linux设备驱动开发详解-第6章字符设备驱动(一)-globalmem
Linux设备驱动开发详解-第6章字符设备驱动(⼀)-globalmem1 驱动程序设计之前奏 (2)1.1 应⽤程序、库、内核、驱动程序的关系 (2)1.2 设备类型 (2)1.3 设备⽂件 (2)1.4 主设备号和从设备号 (2)1.5 驱动程序与应⽤程序的区别 (3)1.6 ⽤户态与内核态 (3)1.7 Linux驱动程序功能 (3)2 字符设备驱动程序框架 (3)2.1 file_operations结构体 (4)2.2 驱动程序初始化和退出 (5)2.3 将驱动程序模块注册到内核 (5)2.4 应⽤字符设备驱动程序 (5)3 globalmem虚拟设备实例描述 (6)3.1 头⽂件、宏及设备结构体 (6)3.2 加载与卸载设备驱动 (6)3.3 读写函数 (8)3.4 seek()函数 (9)3.5 ioctl()函数 (10)3.6 globalmem完整实例 (12)4 测试应⽤程序 (17)4.1 应⽤程序接⼝函数 (17)4.2 应⽤程序 (18)5 实验步骤 (19)5.1 编译加载globalmem 模块 (19)5.2 编译测试应⽤程序 (20)6 扩展 (21)1 驱动程序设计之前奏㈠应⽤程序、库、内核、驱动程序的关系㈡设备类型㈢设备⽂件㈣主设备号与从设备号㈤驱动程序与应⽤程序的区别㈥⽤户态与内核态㈦Linux驱动程序功能1.1 应⽤程序、库、内核、驱动程序的关系■应⽤程序调⽤应⽤程序函数库完成功能■应⽤程序以⽂件形式访问各种资源■应⽤程序函数库部分函数直接完成功能部分函数通过系统调⽤由内核完成■内核处理系统调⽤,调⽤设备驱动程序■设备驱动直接与硬件通信1.2 设备类型■字符设备对字符设备发出读/写请求时,实际的硬件I/O操作⼀般紧接着发⽣■块设备块设备与之相反,它利⽤系统内存作为缓冲区■⽹络设备⽹络设备是⼀类特殊的设备,它不像字符设备或块设备那样通过对应的设备⽂件节点访问,也不能直接通过read或write进⾏数据访问请求1.3 设备⽂件■设备类型、主从设备号是内核与设备驱动程序通信时使⽤的■应⽤程序使⽤设备⽂件节点访问对应设备■每个主从设备号确定的设备都对应⼀个⽂件节点■每个设备⽂件都有其⽂件属性(c或者b)■每个设备⽂件都有2个设备号(后⾯详述)主设备号:⽤于标识驱动程序从设备号:⽤于标识同⼀驱动程序的不同硬件■设备⽂件的主设备号必须与设备驱动程序在登记时申请的主设备号⼀致■系统调⽤是内核与应⽤程序之间的接⼝■设备驱动程序是内核与硬件之间的接⼝1.4 主设备号和从设备号■在设备管理中,除了设备类型外,内核还需要⼀对被称为主从设备号的参数,才能唯⼀标识⼀个设备■主设备号相同的设备使⽤相同的驱动程序■从设备号⽤于区分具体设备的实例例:PC的IDE设备,主设备号⽤于标识该硬盘,从设备号⽤于标识每个分区■在/dev⽬录下使⽤ll命令(ls -l)可以查看各个设备的设备类型、主从设备号等■cat /proc/devices可以查看系统中所有设备对应的主设备号1.5 驱动程序与应⽤程序的区别■应⽤程序以main开始■驱动程序没有main,它以⼀个模块初始化函数作为⼊⼝■应⽤程序从头到尾执⾏⼀个任务■驱动程序完成初始化之后不再运⾏,等待系统调⽤■应⽤程序可以使⽤GLIBC等标准C函数库■驱动程序不能使⽤标准C库1.6 ⽤户态与内核态■驱动程序是内核的⼀部分,⼯作在内核态■应⽤程序⼯作在⽤户态■数据空间访问问题★⽆法通过指针直接将⼆者的数据地址进⾏传递★系统提供⼀系列函数帮助完成数据空间转换get_userput_usercopy_from_usercopy_to_user1.7 Linux驱动程序功能■对设备初始化和释放■把数据从内核传送到硬件和从硬件读取数据■读取应⽤程序传送给设备⽂件的数据和回送应⽤程序请求的数据■检测和处理设备出现的错误2 字符设备驱动程序框架①Linux各种设备驱动程序都是以模块的形式存在的,驱动程序同样遵循模块编程的各项原则②字符设备是最基本、最常⽤的设备,其本质就是将千差万别的各种硬件设备采⽤⼀个统⼀的接⼝封装起来,屏蔽了不同设备之间使⽤上的差异性,简化了应⽤层对硬件的操作③字符设备将各底层硬件设备封装成统⼀的结构体,并采⽤相同的函数操作,如下等:open/close/read/write/ioctl④添加⼀个字符设备驱动程序,实际上是给上述操作添加对应的代码⑤Linux对所有的硬件操作统⼀做以下抽象抽象file_operations结构体规定了驱动程序向应⽤程序提供的操作接⼝struct file_operations ext2_file_operations ={.llseek = generic_file_llseek,.read = generic_file_read,.write = generic_file_write,.aio_read = generic_file_aio_read,.aio_write = generic_file_aio_write,.ioctl = ext2_ioctl,.mmap = generic_file_mmap,.open = generic_file_open,.release = ext2_release_file,.fsync = ext2_sync_file,.readv = generic_file_readv,.writev = generic_file_writev,.sendfile = generic_file_sendfile,};⑥⽤户态与内核态数据的交互⽤户应⽤程序与驱动程序分属于不同的进程空间,因此⼆者之间的数据应当采⽤以下函数进⾏交换long copy_to_user(kernel_buffer, user_buffer,n)//从内核空间拷贝n字节数据到⽤户空间copy_from_user(kernel_buffer, user_buffer,n)//从⽤户空间拷贝n字节数据到内核空间put_user(kernel_value, user_buffer)//从内核空间拷贝⼀数据变量到⽤户空间get_user(kernel_value, user_buffer)//从⽤户空间拷贝⼀数据变量到内核空间(内核空间数据可是任意类型)2.1 file_operations结构体⑴write函数■从应⽤程序接收数据送到硬件ssize_t (*write)(struct file*, const char __user *, size_t, loff_t*);⑵read函数■从硬件读取数据并交给应⽤程序ssize_t (*read)(struct file *, char __user *, size_t, loff_t*); /// 从设备中同步读取数据⑶ioctl函数■为应⽤程序提供对硬件⾏为的相关配置int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned long);⑷open函数■当应⽤程序打开设备时对设备进⾏初始化■使⽤MOD_INC_USE_COUNT增加驱动程序的使⽤次数,当模块使⽤次数不为0时,禁⽌卸载模块Int (*open)(struct inode *, struct file*);⑸release函数■当应⽤程序关闭设备时处理设备的关闭操作■使⽤MOD_DEC_USE_COUNT减少驱动程序的使⽤次数,配合open使⽤,来对模块使⽤次数进⾏计数int (*release)(struct inode *, struct file*);⑹⑻⑻⑼⑽2.2 驱动程序初始化和退出①驱动程序初始化函数■Linux在加载内核模块时会调⽤初始化函数■在初始化函数中⾸先进⾏资源申请等⼯作■使⽤register_chrdev向内核注册驱动程序②驱动程序退出函数■Linux在卸载内核模块时会调⽤退出函数■释放驱动程序使⽤的资源■使⽤unregister_chrdev从内核中卸载驱动程序2.3 将驱动程序模块注册到内核内核需要知道模块的初始化函数和退出函数,才能将模块放⼊⾃⼰的管理队列中①module_init()向内核声明当前模块的初始化函数②module_exit()向内核声明当前模块的退出函数2.4 应⽤字符设备驱动程序㈠加载驱动程序■insmod 内核模块⽂件名■cat /proc/devices 查看当前系统中所有设备驱动程序及其主设备号㈡⼿动建⽴设备⽂件■设备⽂件⼀般建⽴/dev⽬录下■mknod ⽂件路径c [主设备号] [从设备号]㈢应⽤程序接⼝函数■编写应⽤层测试程序■可以使⽤标准C的⽂件操作函数来完成①int open(const char *path, int oflag,…);★打开名为path的⽂件或设备★成功打开后返回⽂件句柄★常⽤oflag:O_RDONLY, O_WRONLY, O_RDWR②int close(int fd);★关闭之前被打开的⽂件或设备★成功关闭返回0,否则返回错误代号③ssize_t read(int fd, void *buffer, size_t count)★从已经打开的⽂件或设备中读取数据★buffer表⽰应⽤程序缓冲区★count表⽰应⽤程序希望读取的数据长度★成功读取后返回读取的字节数,否则返回-1④ssize_t write(int fd, void *buffer, size_t count);★向已经打开的⽂件或设备中写⼊数据★buffer表⽰应⽤程序缓冲区★count表⽰应⽤程序希望写⼊的数据长度★成功写⼊后返回写⼊的字节数,否则返回-1④int ioctl(int fd, unsigned int cmd, unsigned long arg);★向驱动程序发送控制命令★cmd:⽤来定义⽤户向驱动分配的命令例如G PF驱动中:设置指定管脚的⾼低电平、输⼊输出特性等为了规范化及错误检查常⽤_IO宏合成该命令:_IO(MAGIC, num) ★arg:配置命令参数配合cmd命令完成指定功能3 globalmem虚拟设备实例描述3.1 头⽂件、宏及设备结构体在globalmem字符设备驱动中,应包含它要使⽤的头⽂件,并定义globalmem设备结构体及相关宏。
精选嵌入式LINUX设备驱动程序课件
设备的控制操作
对设备的控制操作可通过文件操作数据结构中的ioctl()函数来完成。控制操作与具体的设备有密切关系,需要根据设备实际情况进行具体分析。
设备的轮询和中断处理
轮询方式对于不支持中断的硬件设备,读写时需要轮流查询设备的状态,以便决定随后的数据操作。如果轮询处理方式的驱动程序被链接到内核,则意味着查询过程中,内核一直处于闲置状态。解决办法是使用内核定时器,进行定期查询。
主设备号与次设备号
次设备号用于标识使用同一设备驱动程序的不同硬件,并仅由设备驱动程序解释 当应用程序操作某个设备文件时,Linux内核根据其主设备号调用相应的驱动程序,并从用户态进入内核态驱动程序判断次设备号,并完成相应的硬件操作。
用户空间和内核空间
Linux运行在2种模式下内核模式用户模式内核模式对应内核空间,而用户模式对应用户空间。驱动程序作为内核的一部分,它对应内核空间,应用程序不能直接访问其数据,
帧缓冲设备驱动程序
LCD分类
LCD可由为液晶照明的方式有两种:传送式和反射式传送式屏幕要使用外加光源照明,称为背光(backlight),照明光源要安装在LCD的背后。传送式LCD在正常光线及暗光线下,显示效果都很好,但在户外,尤其在日光下,很难辩清显示内容。 反射式屏幕,则不需要外加照明电源,使用周围环境的光线(或在某些笔记本中,使用前部照明系统的光线)。这样,反射式屏幕就没有背光,所以,此种屏幕在户外或光线充足的室内,才会有出色的显示效果,但在一般室内光线下,这种显示屏的显示效果就不及背光传送式的。
文件操作结构体的主要函数
open: 用于打开文件设备release: 在关闭文件的调用read: 用于从设备中读取数据write: 向设备发送数据poll: 查询设备是否可读或可写ioctl: 提供执行设备特定命令的方法fasync: 用于设备的异步通知操作
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
main()
{
int fd, num;
fd=open("/dev/globalvar", O_RDWR, S_IRUSR|S_IWUSR); 式打开设备文件
//可读写方
if(fd!=-1)
{
变量
read(fd, &num, sizeof(int));
printf("The globalvar is %d\n", num);
{
my_dev->global_var=0; 0
//设备变量初始化为
cdev_init(&my_dev->cdev, &globalvar_fops); ev 结构
//初始化设备中的 cd
my_dev->cdev.owner=THIS_MODULE; 所有者字段
//初始化 cdev 中的
err=cdev_add(&my_dev->cdev, devno, 1); ev 结构的信息
//读取设备
printf("Please input the num written to globalvar\n");
scanf("%d", &num);
write(fd, &num, sizeof(int)); 量
//写设备变
read(fd, &num, sizeof(int)); 刚才写的值
printf("The globalvar is %d\n", num);
{ owner: THIS_MODULE,
//指向拥有该模块结构的指针
open: globalvar_open,
release: globalvar_release,
read: globalvar_read,
write: globalvar_write,
};
struct globalvar_dev {
//关闭设备文件系统调用对应的操作 int globalvar_release(struct inode *inode, struct file *filp) {
return 0; }
//读设备文件系统调用对应的操作 ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff _t *off) {
//再次读取
文件
close(fd);
}
else
{
printf("Device open failure\n");
//关闭设备
} }
//获取指向已分配数据的指针 struct globalvar_dev *dev=filp->private_data; //将设备变量值复制到用户空间 if(copy_to_user(buf, &dev->global_var, sizeof(int))) {
return -EFAULT; } return sizeof(int); //返回读取数据的大小 }
//向内核添加这个 cd
if(err<0)
错误消息
printk("add device failure\n");
//如果添加失败打印
}
return ret;
}
//打开设备文件系统调用对应的操作
int globalvar_open(struct inode *inode, struct file *filp) {
//#globalvar.c
#include <linux/module.h> //模块所需的大量符号和函数定义
#include <linux/init.h>
//指定初始化和清楚函数
#include <linux/fs.h>
//文件系统相关的函数和头文件
#include <linux/cdev.h>
struct globalvar_dev *dev; //根据 inode 结构的 cdev 字段,获得整个设备结构的指针 dev=container_of(inode->i_cdev, struct globalvar_dev, cdev); //将 file 结构中的 private_data 字段指向已分配的设备结构 filp->private_data=dev; return 0; }
//用来表示我们定义设备的结构
int global_var; struct cdev cdev; };
//这个变量代表要操作的设备 //内核中表示字符设备的结构
struct globalvar_dev *my_dev;
//设备结构的指针
static void __exit globalvar_exit(void)
ssize_t globalvar_write(struct file *, const char *, size_t, loff_ t *);
int dev_major = 50;
//指定主设备号
int dev_minor = 0;
//指定次设备号
struct file_operations globalvar_fops= //将文件操作与分配的设备号相连
接下来运行如下代码,将驱动加入内核。 insmod globalvar.ko 此时可以用 dmesg 或 lsmod 命令检查一下模块加载是否成功。如果没有问题,就可以使用 mk nod 构造一个设备文件:
mknod /dev/globalvar c
最后,按照下面内容编写一个简单的测试文件并用 gcc 编译。 //#test.c #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <fcntl.h>
//写设备文件系统调用对应的操作 ssize_t globalvar_write(struct file *filp, const char *buf, size_t le n, loff_t *off)
{ //获取指向已分配数据的指针 struct globalvar_dev *dev=filp->private_data; //从用户空间复制数据到内核中的设备变量 if(copy_from_user(&dev->global_var, buf, sizeof(int))) { return -EFAULT; } return sizeof(int); //返回写数据的大小
my_dev=kmalloc(sizeof(struct globalvar_dev), GFP_KERNEL);
if(!my_dev)
{
ret=-ENOMEM;
//如果分配失败返回错误信息
printk("create device failed\n");
}
else
//如果分配成功就可以完成设
备的初始化
//动态分配设备号,次设备号已经指定
ret=alloc_chrdev_region(&devno, dev_minor, 1, "globalvar");
//保存动态分配的主设备号
dev_major=MAJOR(devno);
//根据期望值分配设备号
//ret=register_chrdev_region(devno, 1, "globalvar");
//退出模块时的操作
{
dev_t devno=MKDEV(dev_major, dev_minor); 号的结构
//dev_t 是用来表示设备编
cdev_del(&my_dev->cdev); 备
//从系统中移除一个字符设
kfree(my_dev);
//释放自定义的设备结构
unregister_chrdev_region(devno, 1);
}
module_init(globalvar_init); module_exit(globalvar_exit);
//模块被装载时调用 globalvar_init //模块被卸载时调用 globalvar_exit
//按如下内容编写一个 Makefile 文件,然后输入 make 就可以开始自动编译了。编译之后得 到了一个名为 globalvar.ko 的模块文件,这就是我们需要的设备驱动文件。
//#Makefile ifneq ($(KERNELRELEASE), )
obj-m := globalvar.o else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean endif
Linux 字符设备驱动实例 1
看了《Linux 设备驱动程序》的前几章,我结合这篇教程中给出的一个 2.4 版内核的字符驱动, 自己编写了一个 2.6 版内核的驱动程序,并且加上了详细的注释。这个程序很简单,但是对初 学者把握 2.6 版内核的字符驱动的脉络应该有一定的帮助,也可以算作我对《Linux 设备驱动 程序》前几章学习的一个小结。
//cdev 结构的头文件
#include <asm/uaccess.h> //在内核和用户空间中移动数据的函数
MODULE_LICENSE("GPL");