这里所说的复杂设备驱动涉及到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. nodejs版本控制

    本方法基于https://segmentfault.com/a/1190000004855835修改 配置: 使用的nvmw的git 地址https://github.com/hakobera/nvm ...

  2. ie8 background css没有显示?——都是空格惹的祸

    ie8 background css没有显示?——都是空格惹的祸

  3. 知识备忘phpcms 简单解析一 数据表字段

    PHPCMS V9帮助中心 数据结构 phpcms v9 数据... phpcms v9 数据... PHPSSO 数据库结... phpcms v9 数据表结构 在线版 PHPCMS V9 数据结构 ...

  4. 使用Slip.js快速创建整屏滑动的手机网页

    原文  http://segmentfault.com/blog/laopopo/1190000000708417 现在滑屏网页越来越多,比如我在搜狐视频就做了好几个,举个例子,可以用手机扫描以下的二 ...

  5. TatukGIS-TGIS_LayerVector-LocateEx

    方法原型: function LocateEx(const _ptg: TGIS_Point; const _prec: Double; const _uid: Integer; var _dist: ...

  6. iOS开发-网络框架-b

    网络框架(以下称NJAFNetworking)是基于AFNetworking框架的简单封装,基本功能包括POST请求,GET请求,上传文件,下载文件,网络状态,缓存等. 为什么要使用NJAFNetwo ...

  7. xamarin fivechess

    网上的五子棋项目是java开发,先转为xamarin,有需要的请下载. FiveChess.7z

  8. oracle timestamp的转换

    select to_char(stime,'yyyy-mm-dd HH24:MI:ss.ff3') from e_bmp_log_operation t where t.sdetail like '% ...

  9. jquery的笔记

    1. 基本选择器 基本的  #id      .class     element(元素)     *(全部元素) $("#id")   $(".class") ...

  10. 带你走进EJB--MDB实现发送邮件(1)

    在实际的项目中我们有这样的需求,用户注册网站成功之后系统会自动的给注册用户发送注册成功通知邮件,而发送通知邮件的具体过程我们可以通过MDB来实现. 在用MDB来实现发送通知过程之前我们需要先了解一下J ...