Complex device drivers for Linux device driver programming

Source: Internet
Author: User
Tags goto

The complex device drivers mentioned here involve PCI, USB, network devices, block devices, and so on (strictly speaking, these devices are conceptually not parallel, for example, a character device is tied to a block device, and a PCI, USB device, etc. may be part of a character device). Some of the more complex data structures and program structures associated with specific device types are also involved in the drivers of these devices. This article will not be too much to introduce the details of these device drivers, just an understatement of the narrative.

PCI is the abbreviation for the peripheral Component Interconnect-bus, the CPU uses the PCI bridge chipset to communicate with the PCI device, and the PCI bridge chipset handles all data interactions between the PCI subsystem and the memory subsystem, The PCI device is completely detached from the memory subsystem. Presents the principle of the PCI subsystem:


Each PCI device has a 256-byte device configuration block with the first 64 bytes as the device ID and basic configuration information, and Linux provides a set of functions to handle the PCI configuration block. Before a PCI device can be used, the Linux driver needs to determine the specific parameters of the device from the information in the PCI device configuration block, making the relevant settings so that the PCI device can operate correctly.

The General PCI device initialization function processing process is:

(1) Check whether the kernel supports pci-bios;

(2) Check whether the equipment exists, to obtain the equipment configuration information;

Examples of these two steps are as follows:

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 was derived from "DRIVERS/PCI/PCI.C". This means
* The GPL applies to this source file and credit are 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 <; 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;
}

The Pci_find_slot () function used is defined as:

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) Request I/O space and IRQ resources according to the device configuration information, (4) register the device.

USB device drivers mainly deal with probe (probing), disconnect (disconnection) functions and usb_device_id (device information) data structures, such as:

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,
};

When a USB device is unplugged from the system, the device driver's disconnect function is automatically called, and after the disconnect function is executed, all the data structures allocated for the USB device are freed:

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's might mod_dec_use_count, but only if you increment
* The Count in Sample_probe () below
*/
Return
}

Once the driver is registered with the subsystem, it is always automatically entered into the probe function after inserting a new USB device. The driver will create a new instance of the internal data structure for this new system-joined device. Typically, the probe function performs functions to detect the manufacturer and product definitions in the newly added USB device hardware and whether the class or subclass definition that the device belongs to corresponds to the driver, and, if so, compares the number of interfaces to the number of interfaces that the driver supports for the device. The USB device description is also typically parsed in the probe function to confirm that the newly added USB device will use the driver:

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-&GT;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-&GT;MAXP);

/*
* Here you might mod_inc_use_count; If you don't, you'll need to unplug
* The device or the devices before being able to unload the module
*/

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

In the development of network device driver, we are especially concerned about the data receiving, sending and interrupting. The level of network device drivers is as follows:


When a network device receives a message, it passes it to the upper layer:

/*
* 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 have been retrieved from the transmission
* Medium. Build a 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
}

Receive message information when an outage arrives:

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-&GT;SKB);
}

/* Unlock The device and we are doing */
Spin_unlock (&priv->lock);
Return
}

And the delivery of the paper is divided into two levels, one level is the kernel call, a level to complete the real hardware on the send:

/*
* 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-&GT;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 is 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 is 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-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 bytes, but the kernel arranges for IPHDR
* to was 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-&GT;IHL);

if (dev = = Snull_devs)
Pdebugg ("%08x:%05i-to-%08x:%05i\n", Ntohl (IH-&GT;SADDR), Ntohs ((struct TCPHDR *) (ih+1)),->source),
Ntohl (IH-&GT;DADDR), Ntohs ((struct TCPHDR *) (ih+1)));
Else
Pdebugg ("%08x:%05i <--%08x:%05i\n",
Ntohl (IH-&GT;DADDR), Ntohs ((struct TCPHDR *) (ih+1))->dest),
Ntohl (IH-&GT;SADDR), Ntohs ((struct TCPHDR *) (ih+1)));

/*
* Ok, now the packet are 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);
}

Block devices also register and release devices in a way similar to the character device Register_chrdev, Unregister_ chrdev function. However, Register_chrdev uses a pointer to the file_operations structure, while Register_blkdev uses a pointer to the block_device_operations structure, where the defined open, release, and The IOCTL method and the character device correspond to the same method, but no read or write operation is defined. This is because all I/O involving block devices is usually buffered by the system. The block driver must eventually provide a mechanism to complete the actual block I/O operations, in Linux, the method used for these I/O operations is called "request". During the registration process of the block device, the request queue needs to be initialized, which is done through blk_init_queue, the Blk_init_queue function establishes the queue, and the request function of the driver is associated to the queue. The Blk_cleanup_queue function should be called during the purge phase of the module. Take a look at Mtdblock's example:

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 (&AMP;MTDBLK-&GT;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 (&AMP;MTDBLK-&GT;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 are 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 (&AMP;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 (&AMP;THR_WQ);
Down (&AMP;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;
}

Complex device drivers for Linux device driver programming

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.