网络安全 频道

Linux设备驱动编程之复杂设备驱动

这里所说的复杂设备驱动涉及到PCI、USB、网络设备、块设备等(严格意义而言,这些设备在概念上并不并列,例如与块设备并列的是字符设备,而PCI、USB设备等都可能属于字符设备),这些设备的驱动中又涉及到一些与特定设备类型相关的较为复杂的数据结构和程序结构。本文将不对这些设备驱动的细节进行过多的介绍,仅仅进行轻描淡写的叙述。

  PCI 是The Peripheral Component Interconnect -Bus的缩写,CPU使用PCI桥chipset与PCI设备通信,PCI桥chipset处理了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;
}

0
相关文章