HyperNews Linux KHG 讨论页面

编写 parport 设备驱动程序

parport 的作用

parport 的一个目的是允许多个设备驱动程序使用同一个并行端口。 它通过位于端口硬件和并行端口设备驱动程序之间来实现这一点。 当驱动程序想要与其并行端口设备通信时,它会调用一个函数来“声明”(claim)端口,并在完成操作后“释放”(release)端口。

另一个parport的作用是从硬件提供一个抽象层,以便设备驱动程序可以实现架构独立性,即它们不需要知道它们正在使用哪种类型的并行端口(目前支持的类型包括 PC 风格、Archimedes 和 Sun Ultra/AX 架构)。

parport 的接口

查找端口

要获取指向 parport 结构体链表的指针,请使用parport_enumerate函数。 这会返回一个指向struct parport的指针,其中成员next指向列表中的下一个,或者为NULL在列表的末尾。

此结构体看起来像这样(来自 linux/include/linux/parport.h)

/* A parallel port */
struct parport {
        unsigned long base;     /* base address */
        unsigned int size;      /* IO extent */
        char *name;
        int irq;                /* interrupt (or -1 for none) */
        int dma;
        unsigned int modes;

        struct pardevice *devices;
        struct pardevice *cad;  /* port owner */
        struct pardevice *lurker;
        
        struct parport *next;
        unsigned int flags; 

        struct parport_dir pdir;
        struct parport_device_info probe_info; 

        struct parport_operations *ops;
        void *private_data;     /* for lowlevel driver */
};

设备注册

接下来要做的是在您想要使用的每个端口上注册一个设备。 这可以通过parport_register_device函数来完成,该函数返回一个指向struct pardevice的指针,您需要它才能使用该端口。

此结构体看起来像这样(同样来自 linux/include/linux/parport.h)

/* A parallel port device */
struct pardevice {
        char *name;
        struct parport *port;
        int (*preempt)(void *);
        void (*wakeup)(void *);
        void *private;
        void (*irq_func)(int, void *, struct pt_regs *);
        int flags;
        struct pardevice *next;
        struct pardevice *prev;
        struct parport_state *state;     /* saved status over preemption */
};

可以注册两种类型的驱动程序:“瞬态”(transient)和“潜伏”(lurking)。 “潜伏”驱动程序是指当没有其他驱动程序占用端口时,它希望拥有该端口的驱动程序。 PLIP 就是一个例子。 “瞬态”驱动程序是指只需要偶尔且在短时间内使用并行端口的驱动程序(打印机驱动程序和 Zip 驱动程序是很好的例子)。

声明端口

要声明端口,请使用parport_claim,并传递给它一个指向struct pardevice在设备注册时获得的指针。 如果parport_claim返回零,则表示您拥有该端口,否则您将不得不稍后重试。

一个好的方法是注册一个“唤醒”(wakeup)函数:当设备驱动程序释放端口时,在该端口上注册的其他设备驱动程序会调用它们的“唤醒”函数,并且第一个声明端口的驱动程序将获得它。 如果 parport 声明失败,您可以进入睡眠状态; 当 parport 再次空闲时,您的唤醒函数可以再次唤醒您。 例如,为设备可能位于的每个可能的端口声明一个全局等待队列

static struct wait_queue * wait_q[MAX_MY_DEVICES];

唤醒函数看起来像这样

void my_wakeup (void * my_stuff)
{
	/* this is our chance to grab the parport */

        struct wait_queue ** wait_q_pointer = (struct wait_queue **) my_stuff;

        if (!waitqueue_active (wait_q_pointer))
                return; /* parport has messed up if we get here */

        /* claim the parport */
        if (parport_claim (wait_q_pointer)))
                return; /* Shouldn't happen */

        wake_up(wait_q_pointer);
}

然后,在初始化代码中,执行类似这样的操作

struct pardevice * pd[MAX_MY_DEVICES];

int my_driver_init (void)
{
  struct parport * pp = parport_enumerate ();

  int count = 0;

  while (pp) { /* for each port */

    /* set up the wait queue */
    init_waitqueue (&wait_q[count]);

    /* register a device */
    pd[count] = parport_register_device (pp, "Me",
	/* preemption function */	 my_preempt,
	/* wakeup function */		 my_wakeup,
	/* interrupt function */	 my_interrupt,
	/* this driver is transient */	 PARPORT_DEV_TRAN,
	/* private data */		 &wait_q[count]);

    /* try initialising the device */
    if (init_my_device (count) == ERROR)
      /* failed, so unregister */
      parport_unregister_device (pd[count]);
    else if (++count == MAX_MY_DEVICES)
      /* can't handle any more devices */
      break;
  }

然后,获得端口访问权限的典型做法是

	if (parport_claim (pd[n]))
		/* someone else had it */
		sleep_on (&wait_q[n]);	/* will wake up when wakeup */
					/* function called */

	/* (do stuff with the port here) */

	/* finished with the port now */
	parport_release (pd[n]);

使用端口

可以使用 parport 接口提供的函数来执行并行端口上的操作

struct parport_operations {
        void (*write_data)(struct parport *, unsigned int);
        unsigned int (*read_data)(struct parport *);
        void (*write_control)(struct parport *, unsigned int);
        unsigned int (*read_control)(struct parport *);
        unsigned int (*frob_control)(struct parport *, unsigned int mask, unsigned int val);
        void (*write_econtrol)(struct parport *, unsigned int);
        unsigned int (*read_econtrol)(struct parport *);
        unsigned int (*frob_econtrol)(struct parport *, unsigned int mask, unsigned int val);
        void (*write_status)(struct parport *, unsigned int);
        unsigned int (*read_status)(struct parport *);
        void (*write_fifo)(struct parport *, unsigned int);
        unsigned int (*read_fifo)(struct parport *);

        void (*change_mode)(struct parport *, int);

        void (*release_resources)(struct parport *);
        int (*claim_resources)(struct parport *);

        unsigned int (*epp_write_block)(struct parport *, void *, unsigned int);
        unsigned int (*epp_read_block)(struct parport *, void *, unsigned int);

        unsigned int (*ecp_write_block)(struct parport *, void *, unsigned int, void (*fn)(struct parport *, void *, unsigned int), void *);
        unsigned int (*ecp_read_block)(struct parport *, void *, unsigned int, void (*fn)(struct parport *, void *, unsigned int), void *);

        void (*save_state)(struct parport *, struct parport_state *);
        void (*restore_state)(struct parport *, struct parport_state *);

        void (*enable_irq)(struct parport *);
        void (*disable_irq)(struct parport *);
        int (*examine_irq)(struct parport *);

        void (*inc_use_count)(void);
        void (*dec_use_count)(void);
};

但是,对于通用操作,应使用以下宏(特定于架构的 parport 实现可能会重新定义它们以避免函数调用开销)

/* Generic operations vector through the dispatch table. */
#define parport_write_data(p,x)            (p)->ops->write_data(p,x)
#define parport_read_data(p)               (p)->ops->read_data(p)
#define parport_write_control(p,x)         (p)->ops->write_control(p,x)
#define parport_read_control(p)            (p)->ops->read_control(p)
#define parport_frob_control(p,m,v)        (p)->ops->frob_control(p,m,v)
#define parport_write_econtrol(p,x)        (p)->ops->write_econtrol(p,x)
#define parport_read_econtrol(p)           (p)->ops->read_econtrol(p)
#define parport_frob_econtrol(p,m,v)       (p)->ops->frob_econtrol(p,m,v)
#define parport_write_status(p,v)          (p)->ops->write_status(p,v)
#define parport_read_status(p)             (p)->ops->read_status(p)
#define parport_write_fifo(p,v)            (p)->ops->write_fifo(p,v)
#define parport_read_fifo(p)               (p)->ops->read_fifo(p)
#define parport_change_mode(p,m)           (p)->ops->change_mode(p,m)
#define parport_release_resources(p)       (p)->ops->release_resources(p)
#define parport_claim_resources(p)         (p)->ops->claim_resources(p)

释放端口

当您完成要在端口上执行的操作序列后,请使用release_parport以允许可能存在的任何其他设备尝试使用。

注销设备

如果您决定最终不想使用该端口(也许您想与之通信的设备不存在),请使用parport_unregister_device.

需要记住的事情:中断

并行端口设备无法共享中断。 parport 代码通过调度在不同的设备之间共享一个并行端口 - 任何时候只有一个设备可以访问该端口。 如果一个设备(例如打印机)要生成中断,则它可能在其他驱动程序(如 Zip 驱动程序)拥有端口而不是打印机驱动程序时执行此操作。 这将导致完全错过中断。 因此,除非没有其他驱动程序使用该端口,否则驱动程序应轮询其设备。 要了解如何执行此操作,您可以查看打印机驱动程序。