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.
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_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_PRESENTIndicates 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_CONFIGIndicates that the card is configured for use.
DEV_CONFIG_PENDINGIndicates that configuration is in progress.
DEV_SUSPENDIndicates that the card is suspended.
DEV_BUSYIndicates that an IO operation is in progress. This bit may be used as an interlock to prevent access conflicts.
DEV_STALE_CONFIGFor 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_LINKA 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.
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().
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.
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.
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.
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 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.
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.
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.
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_INFOCalls CardServices(GetCardServicesInfo, ..., &arg->servinfo).
DS_ADJUST_RESOURCE_INFOCalls CardServices(AdjustResourceInfo, ..., &arg->adjust).
DS_GET_CONFIGURATION_INFOCalls CardServices(GetConfigurationInfo, ..., &arg->config).
DS_GET_FIRST_TUPLECalls CardServices(GetFirstTuple, ..., &arg->tuple).
DS_GET_NEXT_TUPLECalls CardServices(GetNextTuple, ..., &arg->tuple).
DS_GET_TUPLE_DATACalls CardServices(GetTupleData, ..., &arg->tuple_parse.tuple).
The tuple data is returned in arg->tuple_parse.data.
DS_PARSE_TUPLECalls CardServices(ParseTuple, ..., &arg->tuple_parse.tuple, &arg->tuple_parse.parse).
DS_RESET_CARDCalls CardServices(ResetCard, ...).
DS_GET_STATUSCalls CardServices(GetStatus, ..., &arg->status).
DS_ACCESS_CONFIGURATION_REGISTERCalls CardServices(AccessConfigurationRegister, ..., &arg->conf_reg).
DS_VALIDATE_CISCalls CardServices(ValidateCIS, ..., &arg->cisinfo).
DS_SUSPEND_CARDCalls CardServices(SuspendCard, ...).
DS_RESUME_CARDCalls CardServices(ResumeCard, ...).
DS_EJECT_CARDCalls CardServices(EjectCard, ...).
DS_INSERT_CARDCalls CardServices(InsertCard, ...).
DS_GET_FIRST_REGIONCalls CardServices(GetFirstRegion, ..., &arg->region).
DS_GET_NEXT_REGIONCalls CardServices(GetNextRegion, ..., &arg->region).
DS_REPLACE_CISCalls 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_REQUESTThis 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_INFOThis call retrieves the dev_name, major, and minor
entries from the dev_link_t structure pointed to by
instance.
DS_UNBIND_REQUESTThis 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.