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_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.
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_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.