这里所说的复杂设备驱动涉及到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");

 /*
 * This code is derived from "drivers/pci/pci.c". This means that
 * the GPL applies to this source file and credit is due to the
 * original authors (Drew Eckhardt, Frederic Potter, David
 * Mosberger-Tang)
 */
 for (bus = 0; !bus; bus++)
 {
  /* only bus 0 :-) */
  for (devfn = 0; devfn < 0x100 && pos < PAGE_SIZE / 2; devfn++)
  {
   struct pci_dev *dev = NULL;

   dev = pci_find_slot(bus, devfn);
   if (!dev)
    continue;

   /* Ok, we've found a device, copy its cfg space to the buffer*/
   for (i = 0; i < 256; i += sizeof(u32), pos += sizeof(u32))pci_read_config_dword(dev, i, (u32*)(buf + pos));
    pci_release_device(dev); /* 2.0 compatibility */
  }
 }
 *eof = 1;
 return pos;
}

  其中使用的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;
 pptr->index = index; /* 0 */
 ret = pcibios_read_config_word(bus, devfn, PCI_VENDOR_ID, &vendor);
 if (ret /* == PCIBIOS_DEVICE_NOT_FOUND or whatever error */
|| vendor==0xffff || vendor==0x0000) {
  kfree(pptr); return NULL;
 }
 printk("ok (%i, %i %x)\n", bus, devfn, vendor);
 /* fill other fields */
 pptr->bus = bus;
 pptr->devfn = devfn;
 pcibios_read_config_word(pptr->bus, pptr->devfn,PCI_VENDOR_ID, &pptr->vendor);
 pcibios_read_config_word(pptr->bus, pptr->devfn,PCI_DEVICE_ID, &pptr->device);
 return pptr;
}

  (3)根据设备的配置信息申请I/O空间及IRQ资源;

 
(4)注册设备。

  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 =
{
 name: "sample", probe: sample_probe, disconnect: sample_disconnect, id_table:
 sample_id_table,
};

  当一个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_unlink_urb(&sample->urb);
 kfree(sample);
 printk(KERN_INFO "sample: USB %s disconnected\n", sample->name);

 /*
 * here you might MOD_DEC_USE_COUNT, but only if you increment
 * the count in sample_probe() below
 */
 return;
}

  当驱动程序向子系统注册后,插入一个新的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];
 interface = &iface->altsetting[iface->act_altsetting];

 if (interface->bNumEndpoints != 1) return NULL;

 endpoint = interface->endpoint + 0;
 if (!(endpoint->bEndpointAddress & 0x80)) return NULL;
 if ((endpoint->bmAttributes & 3) != 3) return NULL;

 usb_set_protocol(udev, interface->bInterfaceNumber, 0);
 usb_set_idle(udev, interface->bInterfaceNumber, 0, 0);

 /* allocate and zero a new data structure for the new device */
 sample = kmalloc(sizeof(struct sample_device), GFP_KERNEL);
 if (!sample) return NULL; /* failure */
 memset(sample, 0, sizeof(*sample));
 sample->name = (char *)id->driver_info;

 /* fill the URB data structure using the FILL_INT_URB macro */
 {
  int pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress);
  int maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe));

  if (maxp > 8) maxp = 8; sample->maxp = maxp; /* remember for later */
  FILL_INT_URB(&sample->urb, udev, pipe, sample->data, maxp,
  sample_irq, sample, endpoint->bInterval);
 }

 /* register the URB within the USB subsystem */
 if (usb_submit_urb(&sample->urb)) {
  kfree(sample);
  return NULL;
 }
 /* announce yourself */
 printk(KERN_INFO "usbsample: probe successful for %s (maxp is %i)\n",sample->name, sample->maxp);

 /*
 * here you might MOD_INC_USE_COUNT; if you do, you'll need to unplug
 * the device or the devices before being able to unload the module
 */

 /* and return the new structure */
 return sample;
}

  在网络设备驱动的编写中,我们特别关心的就是数据的收、发及中断。网络设备驱动程序的层次如下:

  网络设备接收到报文后将其传入上层:

/*
* 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;

 /*
 * The packet has been retrieved from the transmission
 * medium. Build an skb around it, so upper layers can handle it
 */
 skb = dev_alloc_skb(len+2);
 if (!skb) {
  printk("snull rx: low on mem - packet dropped\n");
  priv->stats.rx_dropped++;
  return;
 }
 skb_reserve(skb, 2); /* align IP on 16B boundary */ 
 memcpy(skb_put(skb, len), buf, len);

 /* Write metadata, and then pass to the receive level */
 skb->dev = dev;
 skb->protocol = eth_type_trans(skb, dev);
 skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
 priv->stats.rx_packets++;
 #ifndef LINUX_20 
  priv->stats.rx_bytes += len;
 #endif 
 netif_rx(skb);
 return;
}

  在中断到来时接收报文信息:

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 */
 priv = (struct snull_priv *) dev->priv;
 spin_lock(&priv->lock);

 /* retrieve statusword: real netdevices use I/O instructions */
 statusword = priv->status;
 if (statusword & SNULL_RX_INTR) {
  /* send it to snull_rx for handling */
  snull_rx(dev, priv->rx_packetlen, priv->rx_packetdata);
 }
 if (statusword & SNULL_TX_INTR) {
  /* a transmission is over: free the skb */
  priv->stats.tx_packets++;
  priv->stats.tx_bytes += priv->tx_packetlen;
  dev_kfree_skb(priv->skb);
 }

 /* Unlock the device and we are done */
 spin_unlock(&priv->lock);
 return;
}

  而发送报文则分为两个层次,一个层次是内核调用,一个层次完成真正的硬件上的发送:

/*
* 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
 if (dev->tbusy || skb == NULL) {
  PDEBUG("tint for %p, tbusy %ld, skb %p\n", dev, dev->tbusy, skb);
  snull_tx_timeout (dev);
  if (skb == NULL)
   return 0;
 }
 #endif

 len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len;
 data = skb->data;
 dev->trans_start = jiffies; /* save the timestamp */

 /* Remember the skb, so we can free it at interrupt time */
 priv->skb = skb;

 /* actual deliver of data is device-specific, and not shown here */
 snull_hw_tx(data, len, dev);

 return 0; /* Our simple device can not fail */
}

/*
* Transmit a packet (low level interface)
*/
void snull_hw_tx(char *buf, int len, struct net_device *dev)
{
 /*
 * This function deals with hw details. This interface loops
 * back the packet to the other snull interface (if any).
 * In other words, this function implements the snull behaviour,
 * while all other procedures are rather device-independent
 */
 struct iphdr *ih;
 struct net_device *dest;
 struct snull_priv *priv;
 u32 *saddr, *daddr;

 /* I am paranoid. Ain't I? */
 if (len < sizeof(struct ethhdr) + sizeof(struct iphdr)) {
  printk("snull: Hmm... packet too short (%i octets)\n",len);
  return;
 }

 if (0) { /* enable this conditional to look at the data */
  int i;
  PDEBUG("len is %i\n" KERN_DEBUG "data:",len);
  for (i=14 ; i<len; i++)
   printk(" %02x",buf[i]&0xff);
   printk("\n");
 }
 /*
 * Ethhdr is 14 bytes, but the kernel arranges for iphdr
 * to be aligned (i.e., ethhdr is unaligned)
 */
 ih = (struct iphdr *)(buf+sizeof(struct ethhdr));
 saddr = &ih->saddr;
 daddr = &ih->daddr;

 ((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) */
 ((u8 *)daddr)[2] ^= 1;

 ih->check = 0; /* and rebuild the checksum (ip needs it) */
 ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);

 if (dev == snull_devs)
  PDEBUGG("%08x:%05i --> %08x:%05i\n",ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source),
ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest));
 else
  PDEBUGG("%08x:%05i <-- %08x:%05i\n",
  ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest),
  ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source));

  /*
  * Ok, now the packet is ready for transmission: first simulate a
  * receive interrupt on the twin device, then a
  * transmission-done on the transmitting device
  */
  dest = snull_devs + (dev==snull_devs ? 1 : 0);
  priv = (struct snull_priv *) dest->priv;
  priv->status = SNULL_RX_INTR;
  priv->rx_packetlen = len;
  priv->rx_packetdata = buf;
  snull_interrupt(0, dest, NULL);

  priv = (struct snull_priv *) dev->priv;
  priv->status = SNULL_TX_INTR;
  priv->tx_packetlen = len;
  priv->tx_packetdata = buf;
  if (lockup && ((priv->stats.tx_packets + 1) % lockup) == 0) {
   /* Simulate a dropped transmit interrupt */
   netif_stop_queue(dev);
   PDEBUG("Simulate lockup at %ld, txp %ld\n", jiffies,(unsigned long) priv->stats.tx_packets);
  }
  else
   snull_interrupt(0, dev, NULL);
 }

  块设备也以与字符设备register_chrdev、unregister_ chrdev 函数类似的方法进行设备的注册与释放。但是,register_chrdev使用一个向 file_operations 结构的指针,而register_blkdev 则使用 block_device_operations 结构的指针,其中定义的open、release 和 ioctl 方法和字符设备的对应方法相同,但未定义 read 或者 write 操作。这是因为,所有涉及到块设备的 I/O 通常由系统进行缓冲处理。

 
块驱动程序最终必须提供完成实际块 I/O 操作的机制,在 Linux中,用于这些 I/O 操作的方法称为"request(请求)"。在块设备的注册过程中,需要初始化request队列,这一动作通过blk_init_queue来完成,blk_init_queue函数建立队列,并将该驱动程序的 request 函数关联到队列。在模块的清除阶段,应调用 blk_cleanup_queue 函数。看看mtdblock的例子:

static void handle_mtdblock_request(void)
{
 struct request *req;
 struct mtdblk_dev *mtdblk;
 unsigned int res;

 for (;;) {
  INIT_REQUEST;
  req = CURRENT;
  spin_unlock_irq(QUEUE_LOCK(QUEUE)); 
  mtdblk = mtdblks[minor(req->rq_dev)];
  res = 0;

  if (minor(req->rq_dev) >= MAX_MTD_DEVICES)
   panic("%s : minor out of bound", __FUNCTION__);

  if (!IS_REQ_CMD(req))
   goto end_req;

  if ((req->sector + req->current_nr_sectors) > (mtdblk->mtd->size >> 9))
   goto end_req;

  // Handle the request
  switch (rq_data_dir(req))
  {
   int err;

   case READ:
    down(&mtdblk->cache_sem);
    err = do_cached_read (mtdblk, req->sector << 9, req->current_nr_sectors << 9,
req->buffer);
    up(&mtdblk->cache_sem);
    if (!err)
     res = 1;
     break;
   case WRITE:
    // Read only device
    if ( !(mtdblk->mtd->flags & MTD_WRITEABLE) ) 
     break;
     // Do the write
     down(&mtdblk->cache_sem);
     err = do_cached_write (mtdblk, req->sector << 9,req->current_nr_sectors << 9, 
req->buffer);
     up(&mtdblk->cache_sem);
    if (!err)
     res = 1;
     break;
  }

  end_req:
   spin_lock_irq(QUEUE_LOCK(QUEUE)); 
   end_request(res);
 }
}

int __init init_mtdblock(void)
{
 int i;

 spin_lock_init(&mtdblks_lock);
 /* this lock is used just in kernels >= 2.5.x */
 spin_lock_init(&mtdblock_lock);

 #ifdef CONFIG_DEVFS_FS
 if (devfs_register_blkdev(MTD_BLOCK_MAJOR, DEVICE_NAME, &mtd_fops))
 {
  printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",MTD_BLOCK_MAJOR);
  return -EAGAIN;
 }

 devfs_dir_handle = devfs_mk_dir(NULL, DEVICE_NAME, NULL);
 register_mtd_user(&notifier);
 #else
 if (register_blkdev(MAJOR_NR,DEVICE_NAME,&mtd_fops)) {
  printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",MTD_BLOCK_MAJOR);
  return -EAGAIN;
 }
 #endif

 /* We fill it in at open() time. */
 for (i=0; i< MAX_MTD_DEVICES; i++) {
  mtd_sizes[i] = 0;
  mtd_blksizes[i] = BLOCK_SIZE;
 }
 init_waitqueue_head(&thr_wq);
 /* Allow the block size to default to BLOCK_SIZE. */
 blksize_size[MAJOR_NR] = mtd_blksizes;
 blk_size[MAJOR_NR] = mtd_sizes;

 BLK_INIT_QUEUE(BLK_DEFAULT_QUEUE(MAJOR_NR), &mtdblock_request, &mtdblock_lock);

 kernel_thread (mtdblock_thread, NULL, CLONE_FS|CLONE_FILES|CLONE_SIGHAND);
 return 0;
}

static void __exit cleanup_mtdblock(void)
{
 leaving = 1;
 wake_up(&thr_wq);
 down(&thread_sem);
 #ifdef CONFIG_DEVFS_FS
  unregister_mtd_user(&notifier);
  devfs_unregister(devfs_dir_handle);
  devfs_unregister_blkdev(MTD_BLOCK_MAJOR, DEVICE_NAME);
 #else
  unregister_blkdev(MAJOR_NR,DEVICE_NAME);
 #endif
 blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR));
 blksize_size[MAJOR_NR] = NULL;
 blk_size[MAJOR_NR] = NULL;
}

Linux设备驱动编程之复杂设备驱动的更多相关文章

  1. 梦织未来Windows驱动编程 第03课 驱动的编程规范

    最近根据梦织未来论坛的驱动教程学习了一下Windows下的驱动编程,做个笔记备忘.这是第03课<驱动的编程规范>. 驱动部分包括基本的驱动卸载函数.驱动打开关闭读取写入操作最简单的分发例程 ...

  2. 梦织未来Windows驱动编程 第06课 驱动对磁盘文件的操作

    代码部分: 实现一个文件C:\\text.txt,并读取写入内容到文件,然后将文件设置为只读,并隐藏文件.代码如下: //MyCreateFile.c //2016.07.22 #include &l ...

  3. 梦织未来Windows驱动编程 第04课 驱动相关的数据结构

  4. 嵌入式linux驱动开发之点亮led(驱动编程思想之初体验)

    这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的 ...

  5. 驱动编程思想之初体验 --------------- 嵌入式linux驱动开发之点亮LED

    这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的 ...

  6. 介绍linux设备驱动编程

    目前,Linux软件工程师大致可分为两个层次: (1)Linux应用软件工程师(Application Software Engineer):       主要利用C库函数和Linux API进行应用 ...

  7. Linux设备驱动编程---miscdevice杂类设备的使用方法

    miscdev简称杂类设备杂类设备就是对字符设备驱动做一个封装,方便简单使用杂类设备封装字符设备需要包含的头文件:#include <linux/miscdevice.h>(1)杂类设备的 ...

  8. Linux符设备驱动编程

    加入内核源码树外 ① 建立两个文件scull.c,scull.h,以及Makefile文件 Makefile文件 ② 用make进行编译,生成scull.ko驱动程序模块 ③ 把scull.ko模块加 ...

  9. linux设备驱动第五篇:驱动中的并发与竟态

    综述 在上一篇介绍了linux驱动的调试方法,这一篇介绍一下在驱动编程中会遇到的并发和竟态以及如何处理并发和竞争. 首先什么是并发与竟态呢?并发(concurrency)指的是多个执行单元同时.并行被 ...

随机推荐

  1. php数组基础

    一.php数组的声明      1.数组中可以有任意类型的数据      2.长度可以变长      3.数组的分类:           a.索引数组:数组是以从0开始的帧数作为索引值       ...

  2. JS生成二维码,支持中文字符

    一.使用jquery-qrcode生成二维码 先简单说一下jquery-qrcode,这个开源的三方库(可以从https://github.com/jeromeetienne/jquery-qrcod ...

  3. 关于Look and Say序列的感想

    今天无意间翻到了<PHP经典实例>中字符串章节中关于Look and Say序列的那个程序: <?php function lookandsay($s) { //将保存返回值的变量初 ...

  4. Android中的Selector的用法

    转自: Android中的Selector主要是用来改变ListView和Button控件的默认背景.其使用方法可以按一下步骤来设计: (以在mylist_view.xml为例) 1.创建mylist ...

  5. 常用 Linux 命令

    Check page size: getconf PAGESIZE Check memory information: cat /proc/meminfo Check number of hugepa ...

  6. Solr4.8.0源码分析(22)之SolrCloud的Recovery策略(三)

    Solr4.8.0源码分析(22)之SolrCloud的Recovery策略(三) 本文是SolrCloud的Recovery策略系列的第三篇文章,前面两篇主要介绍了Recovery的总体流程,以及P ...

  7. 转:php的memcache和memcached扩展区别

    原文来自于:http://www.cnblogs.com/yjf512/p/3778287.html 作者:叶剑峰 老生长谈的问题了.我这里就整理一下. memcache的文档在:http://pec ...

  8. javascript下用ActiveXObject控件替换word书签,将内容导出到word后打印第1/2页

    由于时间比较紧,没多的时候去学习研究上述工具包,现在用javascript操作ActiveXObject控件,用替换word模板中的书签方式解决. 最近有需求将数据导出到word里,然后编辑打印. 想 ...

  9. Extjs打开window窗口自动加载html网页

    Window inherits the autoLoad config option from Panel. Note that I included all config options below ...

  10. ExtJS简单的动画效果2(ext js淡入淡出特效)

    Ext 开发小组则提供了 Fx 类集中处理了大部分常用的 js 动画特效,减少了我们自己手写代码的复杂度. 面我给出一个简单的实例代码,其中囊括了大部分的 Ext 动画效果: (注意导入js和css文 ...