分享

字符设备基础

 wanwanstudy 2012-02-20
bash> ls -l /dev
total 0
crw-------   1 root root     5,   1 Jul 16 10:02 console
...
lrwxrwxrwx   1 root root          3 Oct 6 10:02  cdrom -> hdc
...
brw-rw----   1 root disk     3,   0 Oct 6 2007   hda
brw-rw----   1 root disk     3,   1 Oct 6 2007   hda1
...
crw-------   1 root tty      4,   1 Oct 6 10:20  tty1
crw-------   1 root tty      4,   2 Oct 6 10:02  tty2
第一个字符:c:字符设备、l:symlink、b:块设备
第五个字符5,1:主设备号,次设备号;主设备号能够寻找到驱动,而次设备号则能够定位设备。
字符设备和块设备的空间不一样,因此,可以使用同一主设备号
从编码角度看需要:(1)、初始化并注册设备。(2)、应用通过/dev下节点的文件系统调用:open()、read()、ioctl()、llseek()、write()
(3)中断处理程序、底半部、时钟处理、内核辅助线程和其他支撑架构。
从数据流角度看,字符设备需要:(1)设备对应结构体;(2)struct cdev;(3)、struct file_operation;(4)struct file。
 
Device Example:System CMOS
BIOS使用CMOS来存储诸如startup option,boot order,and the system date,您可以通过BIOS启动菜单修改。例子CMOS驱动可以像普通文件一样访问2个PC CMOS块,应用程序可以操作/dev/cmos/0和/dev/cmos/1,并使用I/O系统调用来访问两个bank里面的数据。因为BIOS给CMOS区域以位级粒度,驱动也能够以位级访问。所以read()可以读取指定的位数,并移动相应位数指针。

Table 5.1. Register Layout on the CMOS
Register NameDescription
CMOS_BANK0_INDEX_PORTSpecify the desired CMOS bank 0 offset in this register.
CMOS_BANK0_DATA_PORTRead/write data from/to the address specified in CMOS_BANK0_INDEX_PORT.
CMOS_BANK1_INDEX_PORTSpecify the desired CMOS bank 1 offset in this register.
CMOS_BANK1_DATA_PORTRead/write data from/to the address specified in CMOS_BANK1_INDEX_PORT.
Driver Initialization
 init()的责任:(1)请求分配设备主设备号;(2)为每个设备结构分配内存;(3)链接字符设备驱动cdev的入口点(open()、read()等);(4)联系主设备号和驱动的cdev;(5)在/dev和/sys下创建节点;(6)初始化硬件。以下是示例程序:
Listing 5.1. CMOS Driver Initialization

#include <linux/fs.h>

/* Per-device (per-bank) structure */
struct cmos_dev {
  unsigned short current_pointer; /* Current pointer within the
                                     bank */
  unsigned int size;              /* Size of the bank */
  int bank_number;                /* CMOS bank number */
  struct cdev cdev;               /* The cdev structure */
  char name[10];                  /* Name of I/O region */
  /* ... */                       /* Mutexes, spinlocks, wait
                                     queues, .. */
} *cmos_devp;

/* File operations structure. Defined in linux/fs.h */
static struct file_operations cmos_fops = {
  .owner    =   THIS_MODULE,      /* Owner */
  .open     =   cmos_open,        /* Open method */
  .release  =   cmos_release,     /* Release method */
  .read     =   cmos_read,        /* Read method */
  .write    =   cmos_write,       /* Write method */
  .llseek   =   cmos_llseek,      /* Seek method */
  .ioctl    =   cmos_ioctl,       /* Ioctl method */
};

static dev_t cmos_dev_number;   /* Allotted device number */
struct class *cmos_class;       /* Tie with the device model */

#define NUM_CMOS_BANKS          2
#define CMOS_BANK_SIZE          (0xFF*8)
#define DEVICE_NAME             "cmos"
#define CMOS_BANK0_INDEX_PORT   0x70
#define CMOS_BANK0_DATA_PORT    0x71
#define CMOS_BANK1_INDEX_PORT   0x72
#define CMOS_BANK1_DATA_PORT    0x73

unsigned char addrports[NUM_CMOS_BANKS] = {CMOS_BANK0_INDEX_PORT,
                                           CMOS_BANK1_INDEX_PORT,};

unsigned char dataports[NUM_CMOS_BANKS] = {CMOS_BANK0_DATA_PORT,
                                           CMOS_BANK1_DATA_PORT,};

/*
 * Driver Initialization
 */
int __init
cmos_init(void)
{
  int i;

  /* Request dynamic allocation of a device major number */
  if (alloc_chrdev_region(&cmos_dev_number, 0,
                          NUM_CMOS_BANKS, DEVICE_NAME) < 0) {
    printk(KERN_DEBUG "Can't register device\n"); return -1;
  }

  /* Populate sysfs entries */
  cmos_class = class_create(THIS_MODULE, DEVICE_NAME);

  for (i=0; i<NUM_CMOS_BANKS; i++) {
    /* Allocate memory for the per-device structure */
    cmos_devp = kmalloc(sizeof(struct cmos_dev), GFP_KERNEL);
    if (!cmos_devp) {
      printk("Bad Kmalloc\n"); return 1;
    }

    /* Request I/O region */
    sprintf(cmos_devp->name, "cmos%d", i);
    if (!(request_region(addrports[i], 2, cmos_devp->name)) {
      printk("cmos: I/O port 0x%x is not free.\n", addrports[i]);
      return –EIO;
    }
    /* Fill in the bank number to correlate this device
       with the corresponding CMOS bank */
    cmos_devp->bank_number = i;

    /* Connect the file operations with the cdev */
    cdev_init(&cmos_devp->cdev, &cmos_fops);
    cmos_devp->cdev.owner = THIS_MODULE;

    /* Connect the major/minor number to the cdev */
    if (cdev_add(&cmos_devp->cdev, (dev_number + i), 1)) {
      printk("Bad cdev\n");
      return 1;
    }

    /* Send uevents to udev, so it'll create /dev nodes */
    class_device_create(cmos_class, NULL, (dev_number + i),
                        NULL, "cmos%d", i);
  }

  printk("CMOS Driver Initialized.\n");
  return 0;
}

/* Driver Exit */
void __exit
cmos_cleanup(void)
{
  int i;

  /* Remove the cdev */
  cdev_del(&cmos_devp->cdev);

  /* Release the major number */
  unregister_chrdev_region(MAJOR(dev_number), NUM_CMOS_BANKS);

  /* Release I/O region */
  for (i=0; i<NUM_CMOS_BANKS; i++) {
    class_device_destroy(cmos_class, MKDEV(MAJOR(dev_number), i));
    release_region(addrports[i], 2);
  }
  /* Destroy cmos_class */
  class_destroy(cmos_class);
  return();
}

module_init(cmos_init);
module_exit(cmos_cleanup);

cmos_init()使用alloc_chrdev_region()来动态请求未使用的主设备号,dev_number包含了得到的主设备号,第二个和第三个参数用于指定起始次设备号和支持的次设备数目,最后一个参数是设备名,可用于在/proc/devices中指定CMOS:
bash> cat /proc/devices | grep cmos
253 cmos
在2.6以前,无法动态分配主设备号,只有通过register_chrdev()来注册指定的主设备号。
cdev_init()把文件操作cmos_fopscdev联系起来,cdev_add()alloc_chrdev_region()分配的主/次设备号给cdev
class_create()为该设备产生一个sysfs入口,class_device_create()则导致产生两个uevent:cmos0和cmos1,udevd会侦听uevent并在查询/etc/udev/rules.d里的规则库后产生/dev/cmos/0和/dev/cmos/1对应的设备节点。要在/etc/udev/rules.d/里面加入:
KERNEL="cmos[0-1]*", NAME="cmos/%n"
设备驱动需要操作一个I/O地址范围,因此使用request_region()来请求该地址,该技术会使其他请求该I/O地址空间的请求者失败,直到拥有者释放空间release_region().request_region()通常由总线申请,如PCI、ISA。request_region()的最后一个参数是/proc/ioports使用的标记,因此:
bash>  grep cmos /proc/ioports
0070-0071  :  cmos0
0072-0073  :  cmos1
Open and Release
当应用程序打开节点时,就会激发驱动程序的open()。您可以通过bash> cat /dev/cmos/0来激发cmos_open()的执行,当cat关闭/dev/cmos/0的文件描述符时,会导致cmos_release()的执行。
Listing 5.2. Open and Release

/*
 * Open CMOS bank
 */
int
cmos_open(struct inode *inode, struct file *file)
{
  struct cmos_dev *cmos_devp;

  /* Get the per-device structure that contains this cdev */
  cmos_devp = container_of(inode->i_cdev, struct cmos_dev, cdev);

  /* Easy access to cmos_devp from rest of the entry points */
  file->private_data = cmos_devp;

  /* Initialize some fields */
  cmos_devp->size = CMOS_BANK_SIZE;
  cmos_devp->current_pointer = 0;

  return 0;
}

/*
 * Release CMOS bank
 */
int
cmos_release(struct inode *inode, struct file *file)
{
  struct cmos_dev *cmos_devp = file->private_data;

  /* Reset file pointer */
  cmos_devp->current_pointer = 0;

  return 0;
}

Exchanging Data

read()和write()是用户空间和设备交换的基本函数,其他的还有fsync(),aio_read(),aio_write()和mmap()

COMS是一个简单的memory设备,因此CMOS数据访问程序不必是sleep-wait 设备IO完成,但其他的一些字符设备都要支持blocking和nonblocking操作。除非设备文件以nonblocking(O_NONBLOCK)方式打开,否则read()和write()允许calling进程睡眠直到相应操作完成;(2)CMOS驱动以完全同步方式而不是依赖于中断。
内核空间和用户空间的交互使用函数copy_to_user()和copy_from_user(),由于这两个函数会陷入睡眠,所以调用时不能持有spinlock锁。读取或写入不同大小的数据使用一些架构无关的函数in[b|w|l|sb|sl]()和out[b|w|l|sb|sl]().
Listing 5.3. Read and Write

/*
 * Read from a CMOS Bank at bit-level granularity
 */
ssize_t
cmos_read(struct file *file, char *buf,
          size_t count, loff_t *ppos)
{
  struct cmos_dev *cmos_devp = file->private_data;
  char data[CMOS_BANK_SIZE];
  unsigned char mask;
  int xferred = 0, i = 0, l, zero_out;
  int start_byte = cmos_devp->current_pointer/8;
  int start_bit  = cmos_devp->current_pointer%8;

  if (cmos_devp->current_pointer >= cmos_devp->size) {
    return 0; /*EOF*/
  }

  /* Adjust count if it edges past the end of the CMOS bank */
  if (cmos_devp->current_pointer + count > cmos_devp->size) {
    count = cmos_devp->size - cmos_devp->current_pointer;
  }

  /* Get the specified number of bits from the CMOS */
  while (xferred < count) {
    data[i] = port_data_in(start_byte, cmos_devp->bank_number)
              >> start_bit;
    xferred += (8 - start_bit);
    if ((start_bit) && (count + start_bit > 8)) {
      data[i] |= (port_data_in (start_byte + 1,
                  cmos_devp->bank_number) << (8 - start_bit));
      xferred += start_bit;
    }
    start_byte++;
    i++;
  }
  if (xferred > count) {
    /* Zero out (xferred-count) bits from the MSB
       of the last data byte */
    zero_out = xferred - count;
    mask = 1 << (8 - zero_out);
    for (l=0; l < zero_out; l++) {
      data[i-1] &= ~mask; mask <<= 1;
    }
    xferred = count;
  }

  if (!xferred) return -EIO;

  /* Copy the read bits to the user buffer */
  if (copy_to_user(buf, (void *)data, ((xferred/8)+1)) != 0) {
    return -EIO;
  }

  /* Increment the file pointer by the number of xferred bits */
  cmos_devp->current_pointer += xferred;
  return xferred; /* Number of bits read */
}

/*
 * Write to a CMOS bank at bit-level granularity. 'count' holds the
 * number of bits to be written.
 */
ssize_t
cmos_write(struct file *file, const char *buf,
           size_t count, loff_t *ppos)
{
  struct cmos_dev *cmos_devp = file->private_data;
  int xferred = 0, i = 0, l, end_l, start_l;
  char *kbuf, tmp_kbuf;
  unsigned char tmp_data = 0, mask;
  int start_byte = cmos_devp->current_pointer/8;
  int start_bit  = cmos_devp->current_pointer%8;

  if (cmos_devp->current_pointer >= cmos_devp->size) {
    return 0; /* EOF */
  }
  /* Adjust count if it edges past the end of the CMOS bank */
  if (cmos_devp->current_pointer + count > cmos_devp->size) {
    count = cmos_devp->size - cmos_devp->current_pointer;
  }

  kbuf = kmalloc((count/8)+1,GFP_KERNEL);
  if (kbuf==NULL)
    return -ENOMEM;

  /* Get the bits from the user buffer */
  if (copy_from_user(kbuf,buf,(count/8)+1)) {
    kfree(kbuf);
    return -EFAULT;
  }

  /* Write the specified number of bits to the CMOS bank */
  while (xferred < count) {
    tmp_data = port_data_in(start_byte, cmos_devp->bank_number);
    mask = 1 << start_bit;
    end_l = 8;
    if ((count-xferred) < (8 - start_bit)) {
      end_l = (count - xferred) + start_bit;
    }

    for (l = start_bit; l < end_l; l++) {
      tmp_data &= ~mask; mask <<= 1;
    }
    tmp_kbuf = kbuf[i];
    mask = 1 << end_l;
    for (l = end_l; l < 8; l++) {
      tmp_kbuf &= ~mask;
      mask <<= 1;
    }

    port_data_out(start_byte,
                  tmp_data |(tmp_kbuf << start_bit),
                  cmos_devp->bank_number);
    xferred += (end_l - start_bit);

    if ((xferred < count) && (start_bit) &&
        (count + start_bit > 8)) {
      tmp_data = port_data_in(start_byte+1,
                              cmos_devp->bank_number);
      start_l = ((start_bit + count) % 8);
      mask = 1 << start_l;
      for (l=0; l < start_l; l++) {
        mask >>= 1;
        tmp_data &= ~mask;
      }
      port_data_out((start_byte+1),
                    tmp_data |(kbuf[i] >> (8 - start_bit)),
                    cmos_devp->bank_number);
      xferred += start_l;
    }

    start_byte++;
    i++;
  }

  if (!xferred) return -EIO;

  /* Push the offset pointer forward */
  cmos_devp->current_pointer += xferred;
  return xferred; /* Return the number of written bits */
}

/*
 * Read data from specified CMOS bank
 */
unsigned char
port_data_in(unsigned char offset, int bank)
{
  unsigned char data;

  if (unlikely(bank >= NUM_CMOS_BANKS)) {
    printk("Unknown CMOS Bank\n");
    return 0;
  } else {
    outb(offset, addrports[bank]); /* Read a byte */
    data = inb(dataports[bank]);
  }
  return data;

}
/*
 * Write data to specified CMOS bank
 */
void
port_data_out(unsigned char offset, unsigned char data,
                int bank)
{
  if (unlikely(bank >= NUM_CMOS_BANKS)) {
    printk("Unknown CMOS Bank\n");
    return;
  } else {
    outb(offset, addrports[bank]); /* Output a byte */
    outb(data, dataports[bank]);
  }
  return;
}

当write()成功返回时,说明应用程序已经将数据成功传给了驱动,但并不保证数据写到了设备里面,如果需要这个保证,那么需要调用fsync()。当应用程序的数据来源自不同的分散的buffers时,需要使用readv和writev(),从2.6.19以后这两个函数被folded into Linux AIO层里面了,the vector driver相关的原型包括:

ssize_t aio_read(struct kiocb *iocb, const struct iovec *vector,
                 unsigned long count, loff_t offset);
ssize_t aio_write(struct kiocb *iocb, const struct iovec *vector,
                  unsigned long count, loff_t offset);
第一个参数描述了AIO操作,第二个参数则是iovecs的数组,记录了buffers的地址和长度,可查看iovecs的定义和/drivers/net/tun.c下对vectors drivers的实现。另外一个数据访问方法是mmap(),它将设备内存和用户虚拟内存联系起来。应用可以调用同名系统调用mmap(),并在返回的内存区域上直接操作设备驻留内存。可查看/drivers/char/mem.c查看实例。
Seek
内核使用内部指针来跟踪文件内访问位置,应用程序调用lseek(),进而调用相应的驱动llseek()。这里的CMOS驱动是按bit移动的。先看一下lseek()使用的几个参数:(1)SEEK_SET,从文件头计算;(2)SEEK_CUR,从当前位置计算;(3)SEEK_END,从文件尾部计算。
Listing 5.4. Seek

/*
 * Seek to a bit offset within a CMOS bank
 */
static loff_t
cmos_llseek(struct file *file, loff_t offset,
            int orig)
{
  struct cmos_dev *cmos_devp = file->private_data;

  switch (orig) {
    case 0: /* SEEK_SET */
      if (offset >= cmos_devp->size) {
        return -EINVAL;
      }
      cmos_devp->current_pointer = offset; /* Bit Offset */
      break;

    case 1: /* SEEK_CURR */
      if ((cmos_devp->current_pointer + offset) >=
           cmos_devp->size) {
        return -EINVAL;
      }
      cmos_devp->current_pointer = offset; /* Bit Offset */
      break;

    case 2: /* SEEK_END - Not supported */
      return -EINVAL;

    default:
      return -EINVAL;
  }

  return(cmos_devp->current_pointer);
}

 Control
ioctl用于接收用户空间命令并请求特殊操作,CMOS memory由cyclic redundancy check(CRC)算法保护。为检测数据是否崩溃,CMOS驱动支持两个ioctl命令:(1)Adjust checksum,用于当CMOS内容改变后重新计算CRC。计算出的checksum放在实现定义好的CMOS bank1的某处。(2)Verify checksum,用于检查CMOS的内容是否healthy,通过比较当前内容的CRC和预存的CRC来判断。应用程序想进行校验操作时,就将这些命令通过ioctl()系统调用发给驱动。
Listing 5.5. I/O Control

#define CMOS_ADJUST_CHECKSUM 1
#define CMOS_VERIFY_CHECKSUM 2

#define CMOS_BANK1_CRC_OFFSET 0x1E

/*
 * Ioctls to adjust and verify CRC16s.
 */
static int
cmos_ioctl(struct inode *inode, struct file *file,
           unsigned int cmd, unsigned long arg)
{
  unsigned short crc = 0;
  unsigned char buf;

  switch (cmd) {
    case CMOS_ADJUST_CHECKSUM:
      /* Calculate the CRC of bank0 using a seed of 0 */
      crc = adjust_cmos_crc(0, 0);

      /* Seed bank1 with CRC of bank0 */
      crc = adjust_cmos_crc(1, crc);

      /* Store calculated CRC */
      port_data_out(CMOS_BANK1_CRC_OFFSET,
                    (unsigned char)(crc & 0xFF), 1);
      port_data_out((CMOS_BANK1_CRC_OFFSET + 1),
                    (unsigned char) (crc >> 8), 1);
      break;

    case CMOS_VERIFY_CHECKSUM:
     /* Calculate the CRC of bank0 using a seed of 0 */
      crc = adjust_cmos_crc(0, 0);

     /* Seed bank1 with CRC of bank0 */
     crc = adjust_cmos_crc(1, crc);

     /* Compare the calculated CRC with the stored CRC */
     buf = port_data_in(CMOS_BANK1_CRC_OFFSET, 1);
     if (buf != (unsigned char) (crc & 0xFF)) return -EINVAL;

     buf = port_data_in((CMOS_BANK1_CRC_OFFSET+1), 1);
     if (buf != (unsigned char)(crc >> 8)) return -EINVAL;
     break;
     default:
       return -EIO;
  }

  return 0;
}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多