Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2050661
  • 博文数量: 610
  • 博客积分: 11499
  • 博客等级: 上将
  • 技术积分: 5511
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-12 19:27
文章分类

全部博文(610)

文章存档

2016年(5)

2015年(18)

2014年(12)

2013年(16)

2012年(297)

2011年(45)

2010年(37)

2009年(79)

2008年(101)

分类: LINUX

2008-04-10 23:55:55




Linux USB Driver Basics

Introduction

Drivers are software components that operating systems use to provide hardware specific services to applications. This webpage attempts to document the basics of USB drivers on Linux. The goal here is to provide you with a basic understanding of how USB device drivers on Linux work.

The File Abstraction

Linux, like other Unix derived operating systems, tries to make applications simpler by providing a common hardware abstraction, the File. Essentially, interactions with almost all hardware can be abstracted into the same interface that the operating system provides for manipulating files. Hence, you can "Open" a driver, "Read" a driver, "Write" to a driver and "Close" a driver. This abstraction extends all the way into the application, who can use the same system calls that it uses to open and manipulate files to talk to hardware.

The way this works is that the kernel creates nodes in the file system, typically located in /dev that represent a particular interface to a driver. To talk to a particular driver an application will open the /dev entry associated with that driver. The file descriptor returned by that open is passed to all future system calls (read, write, select), and is eventually passed to close.

Besides drivers, Unix also uses this file paradigm for various different kinds of IPC, and even for socket communications over a network.

It is quite surprising how many completely different kinds of hardware can be modeled with just 4 operations (open, close, read and write). That said, their is another very important system call that unix applications developers commonly use, select(). The select() system call allows applications to poll and determine whether data could be read from, or written to a file descriptor without blocking.

Sometimes a piece of hardware provides some functionality that doesn't fit well into this file centric paradigm. To allow for this, unix applications typically make use of the ioctl() system call. This call takes a numeric value that is essentially an identifier for a specific piece of functionality in the driver.

The Job of the Device Driver

Simply stated, it is the job of the driver to provide functions that the kernel can use to implement this file programming paradigm. Applications do not directly call functions in the driver, instead they call functions in libc that eventually call into the kernel (via the system call interface). The kernel implementations of these system calls call into your driver.

As you might expect, this means that you're going to be implementating open, close, read and write inside your driver. To implement select(), the kernel needs you to implement a method in your driver called poll(). Other than these, their are only a handfull of other functions that need to be implemented, and you have a driver!

Loading a Driver

Linux supports essentially two kinds of driver development models: 1) Compiled into the kernel, 2) Dynamically loadable driver modules. If configured properly, option 2 has the advantage that drivers can be loaded automatically by the kernel only when they are actually needed to service an application request. Option 1 presents a logically cleaner, and slightly faster scenario.

The choice is up to you. That said, we will now cover the basics of loading and unloading a kernel module into a running kernel.

First, understand that a Linux kernel driver cannot be dynamically linked against any librarys (not even libc, which many programmers dont even know they are linking against). The only code external to your driver that your allowed to make use of are functions that are implemented within the kernel. In fact, kernel modules are never "Linked", instead they are relocatable object files, .o's or sometimes .ko's.

The following command is used to install a driver into a running kernel:

[td@objective ~]$ insmod driver.ko

The following command is used to show what drivers are currently loaded into the kernel:

[td@objective ~]$ lsmod
Module Size Used by
md5 4033 1
ipv6 263105 22
ipt_REJECT 5441 1
ipt_state 1857 5
ip_conntrack 41369 1 ipt_state
iptable_filter 2881 1
ip_tables 19521 3 ipt_REJECT,ipt_state,iptable_filter
video 15685 0
button 4033 0
battery 9285 0
ac 4805 0
uhci_hcd 35025 0
ehci_hcd 40013 0
hw_random 5973 0
i2c_i801 8653 0
i2c_core 21313 1 i2c_i801
snd_intel8x0 34177 0
snd_ac97_codec 74937 1 snd_intel8x0
snd_pcm_oss 50673 0
snd_mixer_oss 17729 1 snd_pcm_oss
snd_pcm 98889 3 snd_intel8x0,snd_ac97_codec
snd_timer 32837 1 snd_pcm
snd 57285 6 snd_intel8x0,snd_ac97_codec
soundcore 10785 1 snd
snd_page_alloc 9669 2 snd_intel8x0,snd_pcm
e1000 102573 0
dm_snapshot 17413 0
dm_zero 2113 0
dm_mirror 25645 0
ext3 130633 2
jbd 83161 1 ext3
dm_mod 57333 6 dm_snapshot,dm_zero,dm_mirror
ata_piix 9413 0
libata 46917 1 ata_piix
sd_mod 20289 0
scsi_mod 146313 2 libata,sd_mod

The following command is used to remove a driver from a running kernel:

[td@objective ~]$ rmmod driver.ko

Since you never link your driver, you will not know about unresolved symbols until you actually load your driver into the kernel. It is during the "insmod" process that the kernel binds all of the calls to kernel functions in your code to their actual current locations in memory. This process is called "resolving symbol dependancies".

Understanding the Universal Serial Bus

From the highest level

As I'm sure you know from your own use of USB, the USB bus, besides offering communications also offers power to devices connected to it. The USB can offer up to 500mA for the devices connected to it. Devices that need more than this can be self powered.

Besides this, your probably aware of the fact that the USB can be expanded with hubs.

How hardware is about to make your life easier

Thankfully, your driver will not have to communicate directly on the Universal Serial Bus. Instead, your driver will communicate to the USB host controller, which will communicate on the bus on your behalf. The USB host controller is a piece of hardware that acts as a focal point for all of the CPU's interaction with the USB hardware. It hides most of the complexity of dealing with USB and also protects the USB hardware from potentially badly written driver software that might otherwise affect the whole bus.

Their are two kinds of host controller hardware commonly in use, UHCI (Universal Host Controller Interface) and OHCI (Open Host Controller Interface). The good news is that both of these host controllers present the same interface to your driver, so you really dont have to care about what kind of host controller hardware is present. As you might expect, UHCI & OHCI hardware has its own driver, that thankfully you will not need to touch. It will be the job of your driver to communicate (indirectly) with the host controller driver, to configure, read from or write to devices on the USB.

Types of Communications on the USB

USB devices have the full range of different speed, latency and reliability requirements. Things like mice and keyboard transfer only small amounts of data, relatively rarely, but they care a lot about latency. No one like a slow keyboard.

On the other hand, USB webcams often transfer compressed MPEG video, which is extremely high bandwidth, but because MPEG was designed to be transmitted over lossy communications channels, it is OK if the occasional packet is lost.

To support these differing requirements, USB supports a number of communications types.

  • Control Transfers - Control transfers are used when you need reliable (the data MUST get their) communications and you are sending a very small amount of data. Essentially, these transfers are commands, and their are a few commands that every device is required to support (GET_STATUS, CLEAR_FEATURE, SET_FEATURE, SET_ADDRESS, GET_DESCRIPTOR, SET_DESCRIPTOR, GET_CONFIGURATION, SET_CONFIGURATION, GET_INTERFACE, SET_INTERFACE, SYNCH_FRAME).

  • Bulk Transfers - Bulk transfers are used when you need reliable (the data MUST get their) communications and you are sending large amounts of data.

  • Interrupt Transfers - Interrupt transfers are simmilar to bulk transfers except they are configured to occur automatically at some interval. These types of transfers are useful for devices that stream constant data of the USB.

  • Isochronous Transfers - Isochronous transfers are very fast, and have guaranteed bus bandwidth, but they have no reliability. This transfer type seems to have been designed with MPEG in mind.

The code that you write is going to have to specify to the host controller what types of communcations you want to use. Thankfully, an extremely wide range of device types fit nicely into the above types of data transfers.

The heirarchical view of the USB

If you think about what I have described so far, it should be fairly apparent that since you will be dealing only indirectly with actual USB devices and instead be communicating through a host controller, that host controller must have some kind of data model for the devices that are connected to it on the USB bus. It must present some description of the devices connected, and their capabilities.

The capabilites of a USB device are modeled heirarchically by the host controller. Your driver will communicate to a USB device by making requests to nodes of this heirarchy. To fully understand the reasoning for all of the layers of the heirarchy I am about to tell you about please consider how generic a host controller must be, given how wide ranging the functionality of USB hardware can be.

When a USB is first plugged into the bus it is detected by the host controller. The host controller sends it the GET_DESCRIPTOR command and retrieves what is called the device descriptor. This structure is itself of a heirarchical nature and describes the features and capabilities of a particular device.

This "Device Descriptor" has 1 or more "Configuration Descriptors", which in turn has 1 or more "Interfaces", which in turn has 0 to N "Endpoints". I know this is a lot to swallow, but its the genericness and flexibility of this structure that lets USB work for so many different types of devices.

Remember, we're talking about 1 USB device here. Every device you have connected to your bus has a structure like this. Simple devices may not have multiple configurations or interfaces. Almost all devices will have at least 1 endpoint however, which means they will also have at least 1 interface and 1 configuration. The reason for this is that the endpoint is typically the part of this tree that the application interacts with.

The Importance of Endpoints

Endpoints are logically the parts of this tree that you write to or read from. If your goal is to read the mpeg coming out of a USB webcam, then you will be reading that data from an Endpoint. We talked a little about communications types above (remember Control, Bulk, Interrupt and Isochronous?) but a key to understanding USB is to understand that Endpoints support a particular type of communication. If you need to do bulk transfers to your device you'll need to find an endpoint on your device that supports bulk transfers. If you dont find it, then your out of luck. You cannot create endpoints, what you find represents the capabilities that this particular USB hardware is presenting to the host controller on your machine.

Endpoints can also be used as a secondary form of device identification. We will discuss the primary form of device identification employed shortly, but for now consider that you could identify a device by the presence of a particular heirarchy of configurations, interfaces and endpoints. In fact, the presence of the right number and types of nodes in this heirarchy is arguably the most important thing you care about. If a device identifies itself to you as being of a particular kind, that does you no good if the hardware doesn't support the endpoints you expect.

Coding details

USB Identification

All USB devices have two very important numbers that serve as our primary form of device identification. These numbers are the devices Vendor ID and Product ID. The idea is that if a company would mark all of the USB devices it creates with an id that identifies the company, it should be really easy to determine if a particular device was made by your company. Because a company might create many different kinds of USB devices, their is a second number, the Product ID. This number can be used to identify a particular product.

When a device is plugged into the USB bus, the kernel query's the installed drivers to find out which supports this particular piece of hardware. Essentially, the kernel calls a method in your driver called "probe". This function is passed the vendor and product id's, as well as a structure used to check for the availability of particular nodes in the devices USB heirarchy. If everything checks out, your driver should eventually call usb_register_dev() to let the kernel know that you do indeed handle this particular piece of hardware.

How it all begins

Now that you know some of the basic fundamentals, it's time we discuss some of the details in a little more depth. The first driver code that gets called by the kernel is your module init routine. Likewise, when your driver is unloaded (via rmmod), the last function that is called is your modules exit routine. It is the job of your init function to register a struct with the kernel called 'usb_driver'. This struct has information like your drivers name, (indirectly through another structure) the vendor and product id's of the usb hardware your driver services, and a pointer to a couple of functions called probe and disconnect (which we'll talk about shortly).

#define USB_VENDOR_ID 0x22FF
#define USB_PRODUCT_ID_1 0x1111
#define USB_PRODUCT_ID_2 0x2222
static struct usb_device_id generic_usb_id_table [] =
{
{
USB_DEVICE(USB_VENDOR_ID,USB_PRODUCT_ID_1)
},
{
USB_DEVICE(USB_VENDOR_ID,USB_PRODUCT_ID_2)
}
{
}
};
MODULE_DEVICE_TABLE (usb, generic_usb_id_table);
static struct usb_driver generic_usb_driver =
{
.owner = THIS_MODULE,
.name = "generic_usb_driver",
.id_table = generic_usb_id_table,
.probe = generic_usb_probe,
.disconnect = generic_usb_disconnect
};
static int __init generic_usb_driver_init( void )
{
return usb_register( &generic_usb_driver );
}
static void __exit generic_usb_driver_exit( void )
{
usb_deregister( &generic_usb_driver )
}
module_init (generic_usb_driver_init);
module_exit (generic_usb_driver_exit);

Their is a bit of macro magic here at the end, but the basic idea is pretty simple. We fill out a structure and we implement some standard entry points that register and unregister that structure. What we have done at this point is told the kernel what vendor and product id's we handle, and what functions it should call when devices that match those id's are added or removed from the USB bus.

Getting Probed by the kernel

When a device is plugged into the USB bus, the USB host controller hardware generates an interrupt that wakes up the host controller driver. Eventually, the kernel finds the usb driver associated with the vendor and product id of the new device. It then calls that drivers probe method. When a device is removed, that also generates an interrupt and eventually, a call to your drivers disconnect function.

The drivers probe method is very important. First, consider that most USB drivers dont really run all the time. They are called by the kernel when they have work to do (such as reading and writing). Since it is usually possible for more than 1 kind of the same USB device to be plugged in, drivers cannot use global data to store information about their state. Instead they must declare some type of structure, which they allocate and populate an instance of when they are probe()'d by the kernel and they determine that they really do handle the hardware in question. Theirfore, it is the job of your probe function to examine the passed usb_interface structure to make sure it has all of the endpoints you need to function. Once that determination has been made you should kmalloc() your instance data structure, populate it (perhaps storing endpoint addresses or pre-allocating URB's) and return it by calling usb_set_intfdata() on the passed interface object. This instance data is passed back to you when the kernel calls into your driver from then on.

Another critical thing you have to do in your probe function is call usb_register_dev, passing a pointer to an instance of a usb_class_driver struct. This struct (the second structure down) is populated with a number of important fields.

static struct file_operations generic_fops =
{
.owner = THIS_MODULE,
.read = generic_read,
.write = generic_write,
.poll = generic_poll,
.ioctl = generic_ioctl
};
static struct usb_class_driver generic_class =
{
.name = "generic_%d",
.fops = &generic_fops,
.mode = S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH,
.minor_base = 42
};

The first structure above contains pointers to all of the functions used to support the file paradigm for this driver (read, write, poll and ioctl). The second structure contains a pointer to the first structure. It also contains a field that is used by the kernel when it creates your device nodes under /dev. The .name field of the usb_class_driver structure is used as a format string, combined with a number, to uniquely identify all of the devices on the USB bus that are serviced by your particular driver.

write

static ssize_t generic_write( struct file* file, 
const char __user* user_buffer,
size_t count,
loff_t* ppos );

Probably the first thing you'll want to do in your write function is access the instance data for this device that you allocated in probe(). You do this by looking at the "private_data" field of the struct file* passed to the function.

Basically, an URB is the unit of communication on the USB bus. Writing or reading from a device on the USB consists of creating and submitting properly constructed URBs. Given that, the next thing you need to do is call usb_alloc_urb() to create the urb for this write operation. Next, you need to create a kernel memory buffer, attach it to the URB, copy the write data from user space into your kernel buffer, finalize the initialization of the URB and finally call usb_submit_urb() to send it.

char* buf = NULL;
struct urb* urb = usb_alloc_urb( 0, GFP_KERNEL );
buf = usb_buffer_alloc( dev->udev, count, GFP_KERNEL, &urb->transfer_dma );
if( buf )
{
copy_from_user( buf, user_buffer, count );
usb_fill_bulk_urb( urb, dev->udev,
usb_sndbulkpipe(dev->udev,dev->bulk_out_endpointAddr),
buf,
count,
generic_write_bulk_callback,
dev );
usb_submit_urb( urb, GFP_KERNEL );
usb_free_urb( urb );
}

Not the best code in the world, but shows you the steps of what your going to have to do.

read

Read is a lot simpler than write. Again, the first thing you'll probably want to do is get your instance data from the private_data field of the passed file* structure. Once thats done, you can get to the fun stuff:

      usb_bulk_msg( dev->udev,
usb_recvbulkpipe( dev->udev, dev->bulk_in_endpointAddr),
dev->bulk_in_buffer,
min(dev->bulk_in_size,count),
&count, HZ*2 );
copy_to_user( buffer, dev->bulk_in_buffer, count );

The heart of your read() implementation may be as simple as this.

ioctl

ioctl is the back door. Essentially, ioctl exists because sometimes not every piece of hardware can operate exactly like a file in all cases. Sometimes, you just need to request a specific piece of functionality be performed on your behalf in the driver. You'd also like to be able to pass parameters to such a function, and be returned some kind of indication of the success of such a call.

static int generic_reader_ioctl( struct inode* inode, 
struct file* file,
unsigned in cmd,
unsigned long arg )
{
struct usb_generic_instance_data* dev =
(struct usb_generic_instance_data*)file->private_data;
switch( cmd )
{
case GET_STATE:
return dev->global_state;
break;
default:
break;
}
return -1;
}

The above is a complete ioctl() function that implements a single logical function, GET_STATE. Any number of additional function can be added here simply by adding cases to the switch statement.

exit(0);

This article is a bit longer than I had hoped it would be. The goal was a short introduction that would give you a place to start in your understanding of the Linux USB drivers. I hope I have done at least that much!
 原文地址
阅读(2047) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~