world leader in high performance signal processing
Trace: » basic-block

Kernel 2.6 Block Device Drivers

The basic block device driver has undergone major changes.

The 2.6 kernel has a much simpler and cleaner interface

Here is a brief list of the new features

  • The awkward block layout definitions have been replaced by a dynamic

generic disk subsystem.

  • The block requests have been reworked
  • The Buffer Head structure has been replaced by the more elegant BIO

system.

  • Better support for large devices
  • Each block driver now has its own lock

The steps to create a 2.6 Block Device Driver are :

  • define a device structure
  • specify or get a major number
  • register a name with the major number ( no fops )
  • allocate and set up a gen disk system ( here is the fops table )
  • define a queue ( add to the gen disk system )
  • create a request service function to handle reads / writes
  • create a minimal ioctl call to allow geometry information to be exposed
  • use “add_disk” to start the system processing the device

An example of each step is shown

Define a device Structure

Define the structure and initialize it.

int num_sects = 1024;
int sect_size = 512;
 
static struct simple_device {
       unsigned long size;      //device size in bytes
       spinlock_t lock;         //mutex lock
       char * data;             //device data area
       struct gendisk *gendisk; //gendisk structure
} sbd_device;
 
   sbd_device.size = num_sects * sect_size;
   spin_lock_init(&sbd_device.lock);
   sbd_device.data = vmalloc(num_sects * sect_size);
   // Add check for NULL here return -ENOMEM if failed

Get a Major Number

   #define DEVICE_NAME "sbd"
   int maj = register_blkdev(0,DEVICE_NAME);
   // error if <=0

Define the sbd_ops table

// prototype for the ioctl function
int sbd_ioctl ( struct inode * inode, struct file * file,
                unsigned int cmd , unsigned long arg);
 
static struct block_device_operations sbd_ops = {
       .owner   = THIS_MODULE,
       .ioctl   = snd_ioctl
};

Set up a Gen Disk

Get a Gen Disk system and set it up using the sbd_ops just defined

   int num_parts = 17;  // we will get 16 partitions here
 
   sbd_device.gendisk = alloc_disk(num_parts);
   // Check for NULL and terminate if required
   sbd_device.gendisk-&gt;major = maj;
   sbd_device.gendisk-&gt;first_minor = 0;
   sbd_device.gendisk-&gt;fops = &sbd_ops;
   sbd_device.gendisk-&gt;private_data = &sbd_device;
   strcpy(sbd_device.gendisk-&gt;disk_name,DEVICE_NAME "0");
   set_capacity(sbd_device.gendisk,num_sects*(sect_size/KERNEL_SECTOR_SIZE));

Request Queue

Get a Request Queue and add it to the gen disk

   static struct request_queue * queue;
   queue = blk_init_queue(sbd_request,        \\ the service function
                          &sbd_device.lock ); \\ the lock
   // check for NULL and handle error
   blk_queue_hardsect_size(queue,sect_size);
   sbd_device.gendisk-&gt;queue =  queue;
 
   add_disk(sbd_device.gendisk);

Data Transfer

Specify the data transfer function

static void sbd_transfer ( struct simple_device * dev,
                           unsigned long sector,
                           unsigned long num_sects,
                           char * buffer,
                           int write)
{
      unsigned long offset = sector * sect_size;
      unsigned long num_bytes = num_sects * sect_size;
      // check size
      if ( (offset + num_bytes) &gt; dev-&gt;size ) {
            printk(KERN_NOTICE DEVICE_NAME ":access error\n");
            return;
      }
 
      if ( write ) {
            memcpy(dev-&gt;data+offset, buffer, num_bytes);
      } else {
            memcpy(buffer, dev-&gt;data+offset, num_bytes);
      }
}

Request Function

Specify the request function to handle entries in the request queue.

static void sbd_request(request_queue_t *q) {
   struct request * req;
 
   while((req = elv_next_request(q)) != NULL) {\
       if ( !blk_fs_request(req)) {
            end_request(req,0);        // reject request
            continue;
       }
       sbd_transfer(&sbd_device,       //our read / write routine
                    req-&gt;sector,
                    req-&gt;current_nr_sectors,
                    req-&gt;buffer,
                    rq_data_dir(req));
       end_request(req,1);            // consume request
   }
}

Basic IOCTL function

Define a basic IOCTL function.

The Kernel now handles the default IOCTL calls and only passes the ones to the driver that it cannot handle. This IOCTL is used to get the (Imaginary) geometry of the system

int sbd_ioctl ( struct inode * inode, struct file * file,
                unsigned int cmd , unsigned long arg) {
   long size;
   struct hd_geometry geom;
 
   switch (cmd) {
   // HDIO_ETGEO is all we need to supply
      case HDIO_GETGEO:
         size = sbd_device.size*(sect_size/KERNEL_SECTOR_SIZE);
         geom.cylinders = ( size & ~0x3f ) &gt;&gt; 6;
         geom.heads = 4;
         geom.sectors = 16;
         geom.start = 4;
         if( copy_to_user((void *) arg, &geom, sizeof(geom)))
               return -EFAULT;
         return 0;
   }
   return -ENOTTY;
}

Add the new Device

Start the system by adding the disk.

   add_disk(sim_device.gendisk);

Following this you can define a block device using mknod. Create the device nodes.

get maj from cat /proc/devices

mknod /dev/myblock b maj 0 
mknod /dev/myblock0 b maj 0 
mknod /dev/myblock1 b maj 1 
mknod /dev/myblock2 b maj 2 

Then make a file system on one of the nodes

mke2fs /dev/myblock1

Now mount it and add files ( they will dissapear when you reboot or unmount the partition )

mkdir -p /mnt/myblock
mount /dev/myblock1 /mnt/yblock

Clean Up

Finally the clean up code follows. This is used to remove the driver if it was inserted as a module

   static void __ exit sbd_exit(void)  {
        del_gendisk(sbd_device.gendisk);
        put_disk(sbd_device.gendisk);
        unregister_blkdev(maj,DEVICE_NAME);
        blk_cleanup_queue(queue);
        vfree(sbd_device.data);
   }