The HyperNews Linux KHG Discussion Pages

Writing a parport Device Driver

What parport does

One purpose of parport is to allow multiple device drivers to use the same parallel port. It does this by sitting in-between the port hardware and the parallel port device drivers. When a driver wants to talk to its parallel port device, it calls a function to "claim" the port, and "releases" the port when it is done.

Another thing that parport does is provide a layer of abstraction from the hardware, so that device drivers can be architecture-independent in that they don't need to know which style of parallel port they are using (those currently supported are PC-style, Archimedes, and Sun Ultra/AX architecture).

Interface to parport

Finding a port

To obtain a pointer to a linked list of parport structures, use the parport_enumerate function. This returns a pointer to a struct parport, in which the member next points to the next one in the list, or is NULL at the end of the list.

This structure looks like (from 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 PARPORT_IRQ_NONE 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 */
};

Device registration

The next thing to do is to register a device on each port that you want to use. This is done with the parport_register_device function, which returns a pointer to a struct pardevice, which is essentially a ``handle'' that you will need in order to use the port.

The device-registering function has the following prototype:

struct pardevice *parport_register_device(struct parport *port, 
			  const char *name,
			  int (*pf)(void *), void (*kf)(void *),
			  void (*irq_func)(int, void *, struct pt_regs *), 
			  int flags, void *handle);

Obviously, the port is the one that you got from the list returned by parport_enumerate, and name is whatever you want to call your driver.

flags will be zero. There was a time when a distinction was made between so-called ``transient'' drivers and ``lurking'' drivers, but no longer.

The handle is just a pointer to whatever driver-specific structure you want to associate with your device on that port.

Each of the remaining functions, irq_func, pf and kf, are called under certain conditions. They may be NULL to indicate that no callback is needed. irq_func is, predictably, an interrupt-handler.

pf is a ``preempt'' function, and is called whenever your driver has the port but another device has tried to claim it. The ``preempt'' function should return zero if it is willing to give up the port (but should not actually call parport_release), or non-zero if now is a bad time to release the port. In this way, a driver can ``bear in mind'' that somebody else would like to use the port soon.

When a driver releases a port, the ``wakeup'' function, kf, is called of each other registered driver on that port in turn, until one of the drivers claims the port (or all of the drivers have been tried).

Each of these three functions takes a void * as one of the arguments. This is the driver-specific handle.

Claiming the port

To claim the port, use parport_claim, passing it a pointer to the struct pardevice obtained at device registration. If parport_claim returns zero, the port is yours, otherwise (if it returns -EAGAIN) you will have to try again later (the parport_claim function does not block).

A blocking version of that is parport_claim_or_block. If the port cannot be claimed, it sleeps. The return value is 1 if it slept, 0 if the port was available immediately, and negative on error.

Parport provides scheduling between devices in a co-operative fashion. parport_yield checks to see if any other drivers are waiting for the port. If they are not, or your timeslice has not yet been exceeded, it returns 0. Otherwise, it releases the port and reclaims it using parport_claim, and returns the result.

A blocking version of that is parport_yield_blocking. The only difference is that to reclaim the port it uses parport_claim_or_block.

This allows for code like:

/* We are now at a point where we could release the port
 * if someone else wants it, but it would be nice if they
 * didn't. */
if (parport_yield_blocking (pd) == 0 && need_resched)
	/* Didn't sleep but need to */
	schedule ();

Using the port

Operations on the parallel port can be carried out using functions provided by the parport interface:

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);
};

(NB. Some of these, specifically the block operations, have not been implemented yet!)

However, for generic operations, the following macros should be used (architecture-specific parport implementations may redefine them to avoid function call overheads):

/* 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)

Releasing the port

When you have finished the sequence of operations on the port that you wanted to do, use release_parport to let any other devices that there may be have a go.

Unregistering the device

If you decide that you don't want to use the port after all (perhaps the device that you wanted to talk to isn't there), or your driver module is being unloaded, use parport_unregister_device.

Something to bear in mind: interrupts

It is only safe to rely on interrupts from your device when you have the port claimed. If another driver has the port, it will also get the interrupt (or the interrupt will be lost if the other driver has no interrupt function registered).

Copyright © 1998 Tim Waugh tim@cyberelk.demon.co.uk


to: "Writing a parport Device Driver"

Subscribe Membership Admin Mode Show Frames Help for HyperNews 1.9.4