Chinaunix首页 | 论坛 | 博客

分类: LINUX

2009-11-09 10:03:26

对话并行端口
并口是PC兼容机上可以经常看到的25针接口。并口(不管是单向的, 双向的,支持DMA等待)能力取决于底层芯片组.

drivers/parport/目录包含了IEEE 1284并扣口通讯的实现代码(parport). 一些连接并口的设备如打印机和扫描仪都使用并口服务. 并口代码有一个独立于CPU架构的模块"parport.ko"和一个架构相关的模块(PC架构上是"parport_pc.ko"), 它们为接口是并口的设备驱动提供编程接口.

我们先来看下并口打印机的驱动drivers/char/lp.c. 以下是打印一个文件所需的概括步骤:
1. 打印机驱动创建字符设备结点/dev/lp0一直到/dev/lpN, 每个连接打印机都有一个结点.
2. 通用UNIX打印系统(CUPS)是Linux上提供打印功能的框架. CUPS配置文件/etc/printers.conf映射打印机和它们的字符设备结点(/dev/lpX).
3. CUPS解析该文件并导流数据到对应的设备结点. 于是如果你有个打印机接到系统的第一个并口, 执行命令lpr myfile时就通过导流的/dev/lp0进入打印机驱动的写函数lp_write()(定义在drivers/char/lp.c).
4. lp_write()使用并口服务发送数据到打印机。

有一个字符驱动ppdev(drivers/char/ppdev.c)导出了可以让用户程序直接与并口通讯的/dev/parportX设备结点.

设备例子:并口LED板
关于如何利用parport提供的服务,我们先来写一个简单的驱动. 试想一个包含8个LED灯的板子连向标准25针并口. 由于PC上的8比特并口数据寄存器直接映射到并口连接器的2到9针, 这些针也就连到了板上的8个LED灯, 现在写并口数据寄存器控制这些针脚电压使得LED灯可亮可灭. 下面代码实现了该字符驱动:

#include
#include
#include
#include
#include

#define DEVICE_NAME    "led"

static dev_t dev_number;          /* Allotted device number */
static struct class *led_class;   /* Class to which this device
                                     belongs */
struct cdev led_cdev;             /* Associated cdev */
struct pardevice *pdev;           /* Parallel port device */

/* LED open */
int
led_open(struct inode *inode, struct file *file)
{
  return 0;
}

/* Write to the LED */
ssize_t
led_write(struct file *file, const char *buf,
          size_t count, loff_t *ppos)
{
  char kbuf;

  if (copy_from_user(&kbuf, buf, 1)) return -EFAULT;

  /* Claim the port */
  parport_claim_or_block(pdev);

  /* Write to the device */
  parport_write_data(pdev->port, kbuf);

  /* Release the port */
  parport_release(pdev);

  return count;
}
/* Release the device */
int
led_release(struct inode *inode, struct file *file)
{
  return 0;
}

/* File Operations */
static struct file_operations led_fops = {
  .owner = THIS_MODULE,
  .open = led_open,
  .write = led_write,
  .release = led_release,
};

static int
led_preempt(void *handle)
{
  return 1;
}

/* Parport attach method */
static void
led_attach(struct parport *port)
{
  /* Register the parallel LED device with parport */
  pdev = parport_register_device(port, DEVICE_NAME,
                                 led_preempt, NULL,
                                 NULL, 0, NULL);
  if (pdev == NULL) printk("Bad register\n");
}

/* Parport detach method */
static void
led_detach(struct parport *port)
{
  /* Do nothing */
}

/* Parport driver operations */
static struct parport_driver led_driver = {
  .name   = "led",
  .attach = led_attach,
  .detach = led_detach,
};
/* Driver Initialization */
int __init
led_init(void)
{
  /* Request dynamic allocation of a device major number */
  if (alloc_chrdev_region(&dev_number, 0, 1, DEVICE_NAME)
                          < 0) {
    printk(KERN_DEBUG "Can't register device\n");
    return -1;
  }

  /* Create the led class */
  led_class = class_create(THIS_MODULE, DEVICE_NAME);
  if (IS_ERR(led_class)) printk("Bad class create\n");

  /* Connect the file operations with the cdev */
  cdev_init(&led_cdev, &led_fops);

  led_cdev.owner = THIS_MODULE;

  /* Connect the major/minor number to the cdev */
  if (cdev_add(&led_cdev, dev_number, 1)) {
    printk("Bad cdev add\n");
    return 1;
  }

  class_device_create(led_class, NULL, dev_number,
                                 NULL, DEVICE_NAME);

  /* Register this driver with parport */
  if (parport_register_driver(&led_driver)) {
    printk(KERN_ERR "Bad Parport Register\n");
    return -EIO;
  }

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

/* Driver Exit */
void __exit
led_cleanup(void)
{
  unregister_chrdev_region(MAJOR(dev_number), 1);
  class_device_destroy(led_class, MKDEV(MAJOR(dev_number), 0));
  class_destroy(led_class);
  return;
}

module_init(led_init);
module_exit(led_cleanup);

MODULE_LICENSE("GPL");

led_init()和前面代码中的cmos_init()很类似,但有两点不同:
1. 新的设备模型可以区分驱动和设备. led_init(0)通过parport注册LED驱动,使用的是调用parport_register_driver()函数. 当内核在led_attach()中找到LED板后就调用parport_register_device()注册设备.
2. led_init()创建设备结点/dev/led, 你可以用它来控制单个LED灯的状态.
编译并添加设备模块到内核中:
bash> make –C /path/to/kerneltree/ M=$PWD modules
bash> insmod ./led.ko
LED Driver Initialized
要选择性的驱动并口针来控制对应的LED灯, 只需echo适当数据给/dev/led即可:
bash> echo 1 > /dev/led
由于1的ASCII值是31(00110001), 因此第一,第五,第六个LED灯会被点亮.

前面的命令触发了led_write(),这个驱动函数首先通过copy_from_user()来拷贝用户内存数据(31)到内核缓冲区,接着申请得到并口,写数据最后释放使用parport接口的并口.

sysfs是比/dev更好控制设备状态的地方, 所以委托sysfs文件来控制LED是个好主意. 下面代码包含了该想法的驱动实现. 这些对sysfs操作代码也可以作为模板实现驱动中对其他设备的控制.
使用sysfs控制并口LED板:

#include
#include
#include
#include
#include

static dev_t dev_number;         /* Allotted Device Number */
static struct class *led_class;  /* Class Device Model */
struct cdev led_cdev;            /* Character dev struct */
struct pardevice *pdev;          /* Parallel Port device */

struct kobject kobj;             /* Sysfs directory object */

/* Sysfs attribute of the leds */
struct led_attr {
  struct attribute attr;
  ssize_t (*show)(char *);
  ssize_t (*store)(const char *, size_t count);
};

#define glow_show_led(number)                             \
static ssize_t                                            \
glow_led_##number(const char *buffer, size_t count)       \
{                                                         \
  unsigned char buf;                                      \
  int value;                                              \
                                                          \
  sscanf(buffer, "%d", &value);                           \
                                                          \
  parport_claim_or_block(pdev);                           \
  buf = parport_read_data(pdev->port);                    \
  if (value) {                                            \
    parport_write_data(pdev->port, buf | (1<
  } else {                                                \
    parport_write_data(pdev->port, buf & ~(1<
  }                                                       \
  parport_release(pdev);                                  \
  return count;                                           \
}                                                         \
                                                          \
static ssize_t                                            \
show_led_##number(char *buffer)                           \
{                                                         \
  unsigned char buf;                                      \
                                                          \
  parport_claim_or_block(pdev);                           \
                                                          \
  buf = parport_read_data(pdev->port);                    \
  parport_release(pdev);                                  \
                                                          \
  if (buf & (1 << number)) {                              \
    return sprintf(buffer, "ON\n");                       \
  } else {                                                \
    return sprintf(buffer, "OFF\n");                      \
  }                                                       \
}                                                         \
                                                          \
static struct led_attr led##number =                      \
__ATTR(led##number, 0644, show_led_##number, glow_led_##number);

glow_show_led(0); glow_show_led(1); glow_show_led(2);
glow_show_led(3); glow_show_led(4); glow_show_led(5);
glow_show_led(6); glow_show_led(7);

#define DEVICE_NAME "led"

static int
led_preempt(void *handle)
{
  return 1;
}

/* Parport attach method */
static void
led_attach(struct parport *port)
{
  pdev = parport_register_device(port, DEVICE_NAME,
                                       led_preempt, NULL, NULL, 0,
                                       NULL);
  if (pdev == NULL) printk("Bad register\n");
}
/* Parent sysfs show() method. Calls the show() method
   corresponding to the individual sysfs file */
static ssize_t
l_show(struct kobject *kobj, struct attribute *a, char *buf)
{
  int ret;
  struct led_attr *lattr = container_of(a, struct led_attr,attr);

  ret = lattr->show ? lattr->show(buf) : -EIO;
  return ret;
}


/* Sysfs store() method. Calls the store() method
   corresponding to the individual sysfs file */
static ssize_t
l_store(struct kobject *kobj, struct attribute *a,
        const char *buf, size_t count)
{
  int ret;
  struct led_attr *lattr = container_of(a, struct led_attr, attr);

  ret = lattr->store ? lattr->store(buf, count) : -EIO;
  return ret;
}

/* Sysfs operations structure */
static struct sysfs_ops sysfs_ops = {
  .show  = l_show,
  .store = l_store,
};

/* Attributes of the /sys/class/pardevice/led/control/ kobject.
   Each file in this directory corresponds to one LED. Control
   each LED by writing or reading the associated sysfs file */
static struct attribute *led_attrs[] = {
  &led0.attr,
  &led1.attr,
  &led2.attr,
  &led3.attr,
  &led4.attr,
  &led5.attr,
  &led6.attr,
  &led7.attr,
  NULL
};

/* This describes the kobject. The kobject has 8 files, one
   corresponding to each LED. This representation is called the
   ktype of the kobject */
static struct kobj_type ktype_led = {
  .sysfs_ops = &sysfs_ops,
  .default_attrs = led_attrs,
};

/* Parport methods. We don't have a detach method */
static struct parport_driver led_driver = {
  .name = "led",
  .attach = led_attach,
};

/* Driver Initialization */
int __init
led_init(void)
{
  struct class_device *c_d;

  /* Create the pardevice class - /sys/class/pardevice */
  led_class = class_create(THIS_MODULE, "pardevice");
  if (IS_ERR(led_class)) printk("Bad class create\n");

  /* Create the led class device - /sys/class/pardevice/led/ */
  c_d = class_device_create(led_class, NULL, dev_number,
                                   NULL, DEVICE_NAME);

  /* Register this driver with parport */
  if (parport_register_driver(&led_driver)) {
    printk(KERN_ERR "Bad Parport Register\n");
    return -EIO;
  }

  /* Instantiate a kobject to control each LED
     on the board */

  /* Parent is /sys/class/pardevice/led/ */
  kobj.parent = &c_d->kobj;
  /* The sysfs file corresponding to kobj is
     /sys/class/pardevice/led/control/ */
  strlcpy(kobj.name, "control", KOBJ_NAME_LEN);

  /* Description of the kobject. Specifies the list of attribute
     files in /sys/class/pardevice/led/control/ */
  kobj.ktype = &ktype_led;

  /* Register the kobject */
  kobject_register(&kobj);

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

/* Driver Exit */
void
led_cleanup(void)
{
  /* Unregister kobject corresponding to
     /sys/class/pardevice/led/control */
  kobject_unregister(&kobj);

  /* Destroy class device corresponding to
     /sys/class/pardevice/led/ */
  class_device_destroy(led_class, MKDEV(MAJOR(dev_number), 0));

  /* Destroy /sys/class/pardevice */
  class_destroy(led_class);

  return;
}

module_init(led_init);
module_exit(led_cleanup);

MODULE_LICENSE("GPL");

宏定义glow_show_led()使用了内核代码中一种流行技巧来紧凑定义一些类似函数,为板上每个LED定义产生附加到8个/sys文件的read()和write()函数(在sysfs术语里面称为show()和store()). 因此glow_show_led(0)将glow_led_0()和show_led_0()附加到对应于第一个LED的/sys文件. 这些函数分别负责第一个LED的发光/熄灭和读状态. ##是粘一个值给字符串,当编译器处理语句glow_show_led(0)时,glow_led_##number就转换为glow_led_0().

sysfs版本的驱动使用kobject来表示模拟软件把手控制LED的“控制”抽象. 每个kobject通过sysfs里面一个文件夹名字代表,所以kobject_register()会创建/sys/class/pardevice/led/control/文件夹.

ktype描述了一个kobject. "控制"kobject通过ktype_led结构体描述, 这个结构体包含指向属性数组led_attrs[]的指针, 这个数组包含了每个LED设备属性的地址. 每个LED的属性通过下面语句捆绑在一起:
static struct led_attr led##number =
__ATTR(led##number, 0644, show_led_##number, glow_led_##number);
这会为每个LED产生控制文件/sys/class/pardevice/control/ledX, 其中X是LED编号. 要改变ledX的状态,echo一个1(或0)到对应的控制文件即可.

在模块退出阶段,驱动使用kobject_unregister(), class_device_destroy()和class_destroy()反注册kobject和class.

写一个字符驱动不再像2.4内核里面那样简单了, 为开发上述简单的LED驱动,我们使用了6个抽象: cdev, kobject,class,class device和parport. 但抽象也带给我们很多优点如无bug构建块, 代码复用和优雅设计.

阅读(1284) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~