Next Previous Contents

7. Driver Services Interface

Driver Services provides a link between Card Services client drivers and user mode utilities like the cardmgr daemon. It is a sort of Card Services ``super-client''. Driver Services uses the BindDevice function to link other client drivers with their corresponding cards. Unlike other clients, Driver Services remains permanently bound to all sockets as cards are inserted and removed.

7.1 Interface to other client drivers

Driver Services keeps track of all client drivers that are installed and ready to attach to a socket. Client drivers need to have entry points for creating and deleting device ``instances'', where one device instance is everything needed to manage one physical card.

Each client driver is identified by a unique 16-character tag that has the special type dev_info_t, defined in cs_types.h. Each device instance is described by a dev_link_t structure.

The dev_link_t structure

The dev_node_t and dev_link_t data structures are given by:

#include "ds.h"

typedef struct dev_node_t {
        char                    dev_name[DEV_NAME_LEN];
        u_char                  major, minor;
        struct dev_node_t       *next;
}

typedef struct dev_link_t {
        dev_node_t              *dev;
        u_int                   state, open;
        struct wait_queue       *pending
        struct timer_list       release
        client_handle_t         handle;
        io_req_t                io;
        irq_req_t               irq;
        config_req_t            conf;
        window_handle_t         win;
        void                    *priv;
        struct dev_link_t       *next;
} dev_link_t;

The dev field of the dev_link_t structure points to a linked list of dev_node_t structures. In dev_node_t, the dev_name field should be filled in by the driver with a device file name for accessing this device, if appropriate. For example, the serial_cs driver uses names like ``ttyS1''. The major and minor fields give major and minor device numbers for accessing this device. Driver Services relays these fields to user mode programs via the DS_GET_DEVICE_INFO ioctl.

In dev_link_t, the state field should be used to keep track of the current device state. The following flags are defined:

DEV_PRESENT

Indicates that the card is present. This bit should be set and cleared by the driver's event handler in response to card insertion and removal events.

DEV_CONFIG

Indicates that the card is configured for use.

DEV_CONFIG_PENDING

Indicates that configuration is in progress.

DEV_SUSPEND

Indicates that the card is suspended.

DEV_BUSY

Indicates that an IO operation is in progress. This bit may be used as an interlock to prevent access conflicts.

DEV_STALE_CONFIG

For some drivers, when a running card is ejected, the socket should not be unconfigured until any devices corresponding to this card are closed. This flag indicates that the socket should be unconfigured when the device is closed.

DEV_STALE_LINK

A driver instance should not be deleted until all its resources are released. This flag indicates that this driver instance should be freed as soon as the socket is unconfigured.

The open field is a usage count for this device. The device should only be freed when the open count is zero. The pending field can be used to manage a queue of processes waiting to use the device.

The release field is used to schedule device shutdown processing when a card is ejected. A card removal event needs to be handled at high priority, so a driver's event handler will typically deal with an eject by resetting the DEV_PRESENT bit in the device state, then scheduling the shutdown processing to run at a later time.

The handle, io, irq, conf, and win fields comprise all the normal data structures needed to configure an ordinary PC Card IO device

The priv field can be used for any sort of private data structure needed to manage the device. The next field can be used to build linked lists of dev_link_t structures, for drivers that can handle multiple instances.

register_pccard_driver

int register_pccard_driver(dev_info_t *dev_info,
                           dev_link_t *(*attach)(void),
                           void (*detach)(dev_link_t *));
register_pccard_driver informs Driver Services that a client driver is present and ready to be bound to sockets. When Driver Services receives a DS_BIND_REQUEST ioctl that matches this driver's dev_info string, it will call the driver's attach() entry point. When it gets a DS_UNBIND_REQUEST ioctl, it will call detach().

unregister_pccard_driver

int unregister_pccard_driver(dev_info_t *dev_info);

This informs Driver Services that it should no longer bind sockets to the specified client driver.

7.2 The CardBus client interface

The CardBus card interface is designed to be essentially an extension of the PCI bus. CardBus cards are typically designed using standard PCI chip sets. For simplicity in the client drivers, and maximum code sharing with regular kernel PCI drivers, we provide a sort of ``super client'' for configuring CardBus cards. This is implemented in the cb_enabler module.

The cb_enabler module is somewhat similar in philosophy to the Driver Services layer for 16-bit cards. CardBus client drivers register with it, and provide a few entry points for handling device setup and shutdown, as well as power management handling. The cb_enabler module takes care of configuring the card and fielding Card Services events. So, all CardBus-specific code is in the enabler rather than the PCI driver.

It is not mandatory for CardBus clients to use the cb_enabler interface. If a particular client requires more direct control over its CardBus configuration than is provided through the cb_enabler module, it can register directly with Card Services and perform Card Services calls directly, just like a 16-bit client.

The cb_enabler module has two entry points: register_driver and unregister_driver. At some point, these functions may migrate into the kernel: hence the generic names.

register_driver

int register_driver(struct driver_operations *ops);
The driver_operations structure is given by:
typedef struct driver_operations {
        char            *name
        dev_node_t      *(*attach) (dev_locator_t *loc);
        void            (*suspend) (dev_node_t *dev);
        void            (*resume) (dev_node_t *dev);
        void            (*detach) (dev_node_t *dev);
} driver_operations;

The name field is used by cb_enabler when registering this client with Card Services. The rest of the structure describes a set of event handlers for this client.

The function returns 0 on success, and -1 on failure.

unregister_driver

void unregister_driver(struct driver_operations *ops);

The ops parameter should be the same structure pointer passed to a prior successful call to register_driver. The client should take care to only call this function when no devices are currently being managed by this client.

The driver_operations entry points

The attach() entry point is used to configure a single device, given a ``device locator'' structure describing where to find it.

The dev_locator_t structure is given by:

typedef struct dev_locator_t {
        enum { LOC_ISA, LOC_PCI } bus;
        union {
                struct {
                        u_short         io_base_1, io_base_2;
                        u_long          mem_base;
                        u_char          irq, dma;
                } isa;
                struct {
                        u_char          bus;
                        u_char          devfn;
                } pci;
        } b;
} dev_locator_t;

The attach() function should return either NULL or a valid dev_node_t structure describing the new device. All the other entry points will use this pointer to identify the device to be manipulated. The cb_enabler module will invoke the attach() and detach() entry points in response to card insertion and removal events. The suspend() and resume() entry points will be called in response to power management events.

There is no way for a driver to refuse a suspend() or detach() event. When a detach() event is received, the driver should block any subsequent IO to the specified device, but may preserve internal data structures until the kernel device is actually closed.

7.3 Interface to user mode utilities

Driver Services creates a pseudo-device for communicating with user mode PC Card utilities. The major number of the device is chosen dynamically, and PC Card utilities should read /proc/devices to determine it. Minor device numbers correspond to socket numbers, starting with 0.

Only one process is allowed to open a socket for read/write access. Other processes can open the socket in read-only mode. A read-only connection to Driver Services can perform a subset of ioctl calls. A read/write connection can issue all ioctl calls, and can also receive Card Services event notifications.

Card Services event notifications

Driver Services implements read() and select() functions for event notification. Reading from a PC Card device returns an unsigned long value containing all the events received by Driver Services since the previous read(). If no events have been received, the call will block until the next event. A select() call can be used to monitor several sockets for new events.

The following events are monitored by Driver Services: CS_EVENT_CARD_INSERTION, CS_EVENT_CARD_REMOVAL, CS_EVENT_RESET_PHYSICAL, CS_EVENT_CARD_RESET, and CS_EVENT_RESET_COMPLETE.

Ioctl descriptions

Most Driver Services ioctl operations directly map to Card Services functions. An ioctl call has the form:

int ioctl(int fd, int cmd, ds_ioctl_arg_t *arg);
The ds_ioctl_arg_t structure is given by:
typedef union ds_ioctl_arg_t {
        servinfo_t      servinfo;
        adjust_t        adjust;
        config_info_t   config;
        tuple_t         tuple;
        tuple_parse_t   tuple_parse;
        client_req_t    client_req;
        status_t        status;
        conf_reg_t      conf_reg;
        cisinfo_t       cisinfo;
        region_info_t   region;
        bind_info_t     bind_info;
        mtd_info_t      mtd_info;
        cisdump_t       cisdump;
} ds_ioctl_arg_t;

The following ioctl commands execute the corresponding Card Services function:

DS_GET_CARD_SERVICES_INFO

Calls CardServices(GetCardServicesInfo, ..., &arg->servinfo).

DS_ADJUST_RESOURCE_INFO

Calls CardServices(AdjustResourceInfo, ..., &arg->adjust).

DS_GET_CONFIGURATION_INFO

Calls CardServices(GetConfigurationInfo, ..., &arg->config).

DS_GET_FIRST_TUPLE

Calls CardServices(GetFirstTuple, ..., &arg->tuple).

DS_GET_NEXT_TUPLE

Calls CardServices(GetNextTuple, ..., &arg->tuple).

DS_GET_TUPLE_DATA

Calls CardServices(GetTupleData, ..., &arg->tuple_parse.tuple). The tuple data is returned in arg->tuple_parse.data.

DS_PARSE_TUPLE

Calls CardServices(ParseTuple, ..., &arg->tuple_parse.tuple, &arg->tuple_parse.parse).

DS_RESET_CARD

Calls CardServices(ResetCard, ...).

DS_GET_STATUS

Calls CardServices(GetStatus, ..., &arg->status).

DS_ACCESS_CONFIGURATION_REGISTER

Calls CardServices(AccessConfigurationRegister, ..., &arg->conf_reg).

DS_VALIDATE_CIS

Calls CardServices(ValidateCIS, ..., &arg->cisinfo).

DS_SUSPEND_CARD

Calls CardServices(SuspendCard, ...).

DS_RESUME_CARD

Calls CardServices(ResumeCard, ...).

DS_EJECT_CARD

Calls CardServices(EjectCard, ...).

DS_INSERT_CARD

Calls CardServices(InsertCard, ...).

DS_GET_FIRST_REGION

Calls CardServices(GetFirstRegion, ..., &arg->region).

DS_GET_NEXT_REGION

Calls CardServices(GetNextRegion, ..., &arg->region).

DS_REPLACE_CIS

Calls CardServices(ReplaceCIS, ..., &arg->cisdump).

The following ioctl commands invoke special Driver Services functions. They act on bind_info_t structures:

typedef struct bind_info_t {
        dev_info_t              dev_info;
        u_char                  function;
        struct dev_info_t       *instance;
        char                    name[DEV_NAME_LEN];
        u_char                  major, minor;
        void                    *next;
} bind_info_t;
DS_BIND_REQUEST

This call connects a socket to a client driver. The specified device ID dev_info is looked up in the list of registered drivers. If this is a multifunction card, the function field identifies which card function is being bound. If found, the driver is bound to this socket and function using the BindDevice call. Then, Driver Services calls the client driver's attach() entry point to create a device instance. The new dev_link_t pointer is returned in instance.

DS_GET_DEVICE_INFO

This call retrieves the dev_name, major, and minor entries from the dev_link_t structure pointed to by instance.

DS_UNBIND_REQUEST

This call calls the detach() function for the specified driver and instance, shutting down this device.

Finally, the DS_BIND_MTD request takes an argument of type mtd_info_t:

typedef struct mtd_info_t {
        dev_info_t      dev_info;
        u_int           Attributes;
        u_int           CardOffset;
} mtd_info_t;

This call associates an MTD identified by dev_info with a memory region described by Attributes and CardOffset, which have the same meanings as in the Card Services BindMTD call.


Next Previous Contents