Linux设备驱动编程之复杂设备驱动
这里所说的复杂设备驱动涉及到PCI、USB、网络设备、块设备等(严格意义而言,这些设备在概念上并不并列,例如与块设备并列的是字符设备,而PCI、USB设备等都可能属于字符设备),这些设备的驱动中又涉及到一些与特定设备类型相关的较为复杂的数据结构和程序结构。本文将不对这些设备驱动的细节进行过多的介绍,仅仅进行轻描淡写的叙述。
PCI 是The Peripheral Component Interconnect -Bus的缩写,CPU使用PCI桥chipset与PCI设备通信,PCI桥chipset处理了PCI子系统与内存子系统间的所有数据交互,PCI设备完全被从内存子系统分离出来。下图呈现了PCI子系统的原理:
每个PCI设备都有一个256字节的设备配置块,其中前64字节作为设备的ID和基本配置信息,Linux中提供了一组函数来处理PCI配置块。在PCI设备能得以使用前,Linux驱动程序需要从PCI设备配置块中的信息决定设备的特定参数,进行相关设置以便能正确操作该PCI设备。
一般的PCI设备初始化函数处理流程为:
(1)检查内核是否支持PCI-Bios;
(2)检查设备是否存在,获得设备的配置信息;
1~2这两步的例子如下:
int pcidata_read_proc(char *buf, char **start, off_t offset, int len, int *eof,void *data) { int i, pos = 0; int bus, devfn; if (!pcibios_present()) return sprintf(buf, "No PCI bios present\n"); /* dev = pci_find_slot(bus, devfn); /* Ok, we've found a device, copy its cfg space to the buffer*/ |
其中使用的pci_find_slot()函数定义为:
struct pci_dev *pci_find_slot (unsigned int bus, unsigned int devfn) { struct pci_dev *pptr = kmalloc(sizeof(*pptr), GFP_KERNEL); int index = 0; unsigned short vendor; int ret; if (!pptr) return NULL; |
(3)根据设备的配置信息申请I/O空间及IRQ资源;
USB设备的驱动主要处理probe(探测)、disconnect(断开)函数及usb_device_id(设备信息)数据结构,如:
static struct usb_device_id sample_id_table[] = { { USB_INTERFACE_INFO(3, 1, 1), driver_info: (unsigned long)"keyboard" } , { USB_INTERFACE_INFO(3, 1, 2), driver_info: (unsigned long)"mouse" } , { 0, /* no more matches */ } }; static struct usb_driver sample_usb_driver = |
当一个USB 设备从系统拔掉后,设备驱动程序的disconnect 函数会自动被调用,在执行了disconnect 函数后,所有为USB 设备分配的数据结构,内存空间都会被释放:
static void sample_disconnect(struct usb_device *udev, void *clientdata) { /* the clientdata is the sample_device we passed originally */ struct sample_device *sample = clientdata; /* remove the URB, remove the input device, free memory */ /* |
当驱动程序向子系统注册后,插入一个新的USB设备后总是要自动进入probe函数。驱动程序会为这个新加入系统的设备向内部的数据结构建立一个新的实例。通常情况下,probe 函数执行一些功能来检测新加入的USB 设备硬件中的生产厂商和产品定义以及设备所属的类或子类定义是否与驱动程序相符,若相符,再比较接口的数目与本驱动程序支持设备的接口数目是否相符。一般在probe 函数中也会解析USB 设备的说明,从而确认新加入的USB 设备会使用这个驱动程序:
static void *sample_probe(struct usb_device *udev, unsigned int ifnum, const struct usb_device_id *id) { /* * The probe procedure is pretty standard. Device matching has already * been performed based on the id_table structure (defined later) */ struct usb_interface *iface; struct usb_interface_descriptor *interface; struct usb_endpoint_descriptor *endpoint; struct sample_device *sample; printk(KERN_INFO "usbsample: probe called for %s device\n",(char *)id->driver_info /* "mouse" or "keyboard" */ ); iface = &udev->actconfig->interface[ifnum]; if (interface->bNumEndpoints != 1) return NULL; endpoint = interface->endpoint + 0; usb_set_protocol(udev, interface->bInterfaceNumber, 0); /* allocate and zero a new data structure for the new device */ /* fill the URB data structure using the FILL_INT_URB macro */ if (maxp > 8) maxp = 8; sample->maxp = maxp; /* remember for later */ /* register the URB within the USB subsystem */ /* /* and return the new structure */ |
在网络设备驱动的编写中,我们特别关心的就是数据的收、发及中断。网络设备驱动程序的层次如下:
网络设备接收到报文后将其传入上层:
/* * Receive a packet: retrieve, encapsulate and pass over to upper levels */ void snull_rx(struct net_device *dev, int len, unsigned char *buf) { struct sk_buff *skb; struct snull_priv *priv = (struct snull_priv *) dev->priv; /* /* Write metadata, and then pass to the receive level */ |
在中断到来时接收报文信息:
void snull_interrupt(int irq, void *dev_id, struct pt_regs *regs) { int statusword; struct snull_priv *priv; /* * As usual, check the "device" pointer for shared handlers. * Then assign "struct device *dev" */ struct net_device *dev = (struct net_device *)dev_id; /* ... and check with hw if it's really ours */ if (!dev /*paranoid*/ ) return; /* Lock the device */ /* retrieve statusword: real netdevices use I/O instructions */ /* Unlock the device and we are done */ |
而发送报文则分为两个层次,一个层次是内核调用,一个层次完成真正的硬件上的发送:
/* * Transmit a packet (called by the kernel) */ int snull_tx(struct sk_buff *skb, struct net_device *dev) { int len; char *data; struct snull_priv *priv = (struct snull_priv *) dev->priv; #ifndef LINUX_24 len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len; /* Remember the skb, so we can free it at interrupt time */ /* actual deliver of data is device-specific, and not shown here */ return 0; /* Our simple device can not fail */ /* /* I am paranoid. Ain't I? */ if (0) { /* enable this conditional to look at the data */ ((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) */ ih->check = 0; /* and rebuild the checksum (ip needs it) */ if (dev == snull_devs) /* priv = (struct snull_priv *) dev->priv; |
块设备也以与字符设备register_chrdev、unregister_ chrdev 函数类似的方法进行设备的注册与释放。但是,register_chrdev使用一个向 file_operations 结构的指针,而register_blkdev 则使用 block_device_operations 结构的指针,其中定义的open、release 和 ioctl 方法和字符设备的对应方法相同,但未定义 read 或者 write 操作。这是因为,所有涉及到块设备的 I/O 通常由系统进行缓冲处理。
static void handle_mtdblock_request(void) { struct request *req; struct mtdblk_dev *mtdblk; unsigned int res; for (;;) { if (minor(req->rq_dev) >= MAX_MTD_DEVICES) if (!IS_REQ_CMD(req)) if ((req->sector + req->current_nr_sectors) > (mtdblk->mtd->size >> 9)) // Handle the request case READ: end_req: int __init init_mtdblock(void) spin_lock_init(&mtdblks_lock); #ifdef CONFIG_DEVFS_FS devfs_dir_handle = devfs_mk_dir(NULL, DEVICE_NAME, NULL); /* We fill it in at open() time. */ BLK_INIT_QUEUE(BLK_DEFAULT_QUEUE(MAJOR_NR), &mtdblock_request, &mtdblock_lock); kernel_thread (mtdblock_thread, NULL, CLONE_FS|CLONE_FILES|CLONE_SIGHAND); static void __exit cleanup_mtdblock(void) |
Linux设备驱动编程之复杂设备驱动的更多相关文章
- 梦织未来Windows驱动编程 第03课 驱动的编程规范
最近根据梦织未来论坛的驱动教程学习了一下Windows下的驱动编程,做个笔记备忘.这是第03课<驱动的编程规范>. 驱动部分包括基本的驱动卸载函数.驱动打开关闭读取写入操作最简单的分发例程 ...
- 梦织未来Windows驱动编程 第06课 驱动对磁盘文件的操作
代码部分: 实现一个文件C:\\text.txt,并读取写入内容到文件,然后将文件设置为只读,并隐藏文件.代码如下: //MyCreateFile.c //2016.07.22 #include &l ...
- 梦织未来Windows驱动编程 第04课 驱动相关的数据结构
- 嵌入式linux驱动开发之点亮led(驱动编程思想之初体验)
这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的 ...
- 驱动编程思想之初体验 --------------- 嵌入式linux驱动开发之点亮LED
这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的 ...
- 介绍linux设备驱动编程
目前,Linux软件工程师大致可分为两个层次: (1)Linux应用软件工程师(Application Software Engineer): 主要利用C库函数和Linux API进行应用 ...
- Linux设备驱动编程---miscdevice杂类设备的使用方法
miscdev简称杂类设备杂类设备就是对字符设备驱动做一个封装,方便简单使用杂类设备封装字符设备需要包含的头文件:#include <linux/miscdevice.h>(1)杂类设备的 ...
- Linux符设备驱动编程
加入内核源码树外 ① 建立两个文件scull.c,scull.h,以及Makefile文件 Makefile文件 ② 用make进行编译,生成scull.ko驱动程序模块 ③ 把scull.ko模块加 ...
- linux设备驱动第五篇:驱动中的并发与竟态
综述 在上一篇介绍了linux驱动的调试方法,这一篇介绍一下在驱动编程中会遇到的并发和竟态以及如何处理并发和竞争. 首先什么是并发与竟态呢?并发(concurrency)指的是多个执行单元同时.并行被 ...
随机推荐
- 字体圆润属性的使用-webkit-font-smoothing: antialiased
字体渲染和抗锯齿技术 据称该属性在window下不起作用,不知win10如何,但是在OS和ios中会有不同的展示效果,主要也是展示在webkit内核中,以及android和ios中 大概是说字体渲染的 ...
- string相关
1.find相关 string s="abcd"; size_t pos0 = s.find_first_of("dcb"); 1 //返 ...
- dedecms 常用标签调用
/*------------------单个ip调用-------------------*/ {dede:type typeid="12"} <a title=" ...
- JDK环境变量配置贺Tomcat环境搭建
一.安装JDK JDK (Java Develpmet kit) Java开发环境(工具包和运行环境jre) 是Java开发的核心,包括:编译程序的命令 javac 运行程序java命令 he jav ...
- unix 环境高级编程-读书笔记与习题解答-第二篇
第四节 输入与输出 上次的笔记中写到的 open, read, write, lseek 以及close ,都是不带缓存的IO函数,这些函数都使用文件描述符进行工作. 上一篇笔记用到的 read(ST ...
- hdfs 常用命令
(2)bin/hdfs dfs -mkdir -p /home/雨渐渐 (3)scp /media/root/DCE28B65E28B432E/download/第2周/ChinaHadoop第二讲\ ...
- js弹出新窗口居中
方式1: <script language="javascript"> var newUrl = <%=newUrl % > //window.locati ...
- 【Java】Java网络编程菜鸟进阶:TCP和套接字入门
Java网络编程菜鸟进阶:TCP和套接字入门 JDK 提供了对 TCP(Transmission Control Protocol,传输控制协议)和 UDP(User Datagram Protoco ...
- 深入浅出 Java Concurrency (2): 原子操作 part 1
转:http://www.blogjava.net/xylz/archive/2010/07/01/324988.html 从相对简单的Atomic入手(java.util.concurrent是基于 ...
- VIM default configuration
== Vim的行号.语法显示等设置(.vimrc文件的配置) ==2008年01月18日 星期五 23:01 在终端下使用vim进行编辑时,默认情况下,编辑的界面上是没有显示行号.语法高亮度显示.智能 ...