Writing PCMCIA Device Drivers
只搜寻这本书
以 PDF 格式下载本书

PC Card Driver Autoconfiguration

5

This chapter describes the support a PC Card driver must provide for driver autoconfiguration.

PC Card Driver Autoconfiguration Overview

In the autoconfiguration process, an individual instance of a PC Card driver is attached to a function on a particular PC Card in a given socket. On PC cards providing multiple functions, a unique instance of a PC Card driver is attached to each function. In the Solaris system, an instance of a PC Card driver can only access one function.
The basic steps in the autoconfiguration process are the same for PC Card drivers as for other drivers in the Solaris system; however, some details differ for PC Card drivers. For example, attach(9E) initializes the instance of the driver for a PC Card, but PC Card configuration itself does not occur until Card Services notifies the driver that the card is ready. The separation of driver autoconfiguration and card configuration allows PC Cards to be dynamically configured and unloaded as they are inserted and removed from the system.
PC Card drivers must provide the following standard set of device loading and autoconfiguration entry points and data structures.
  • The _init(9E), _info(9E), and _fini(9E) routines are required for module loading. The loadable module routines used by non-PCMCIA drivers can be used by PC Card drivers without modification.
  • The autoconfiguration entry points attach(9E), getinfo(9E), and detach(9E) are required.
  • The loadable module and driver initialization data structures modlinkage(9S), modldrv(9S), dev_ops(9S), and cb_ops(9S) are required.
The sections that follow describe autoconfiguration information specific to writing a PC Card driver. For general information on autoconfiguration in the Solaris system, see the Writing Device Drivers manual. See Chapter 9, "PCMCIA Parallel Port Driver," in this manual for a comprehensive look at the autoconfiguration routines of the pcepp.c sample driver.

Autoconfiguration Entry Points

Each PC Card driver must provide the following autoconfiguration entry points:
  • attach(9E)
  • getinfo(9E)
  • detach(9E)

attach(9E)

A driver's attach(9E) entry point initializes an instance of the driver. A PC Card attach(9E) routine should perform the common initialization tasks that all drivers require and should handle additional tasks specific to Card Services, such as driver registration and event handling setup. Typically, a PC Card driver attach(9E) routine does the following:
  • Allocates the soft state structures for the driver instance
  • Registers the driver instance with Card Services
  • Allocates any memory needed for this instance of the driver
  • Initializes per-instance mutexes and condition variables
  • Installs a Card Services event handler
  • Gets the logical socket number associated with the client handle
  • Enables the event handler
Note that system resource allocation, such as mapping the device's registers or registering device interrupts, which is typically done at attach time, is not done by a PC Card driver until the driver receives an insertion event. For information on resource allocation, see Chapter 7, "PC Card Configuration."

Driver Registration With Card Services

A driver registers with Card Services by calling csx_RegisterClient(9F).
int32_t csx_RegisterClient(client_handle_t *, client_reg_t *);

When calling csx_RegisterClient(9F), the PC Card driver must provide the following information in a client_reg_t(9S) structure:
  • Type of client
  • Event types
  • Event handler
  • Event callback data
  • Card Services version number
  • Device instance number (driver dip)
  • Driver name
The client_reg_t(9S) structure is defined in the cs.h header file as:
typedef struct client_reg_t {
    uint32_t                 Attributes;
    uint32_t                 EventMask;
    event_callback_args_t    event_callback_args;
    uint32_t                 Version;        /* CS version */
    csfunction_t             *event_handler;
    ddi_iblock_cookie_t      *iblk_cookie;  /* event iblk cookie */
    ddi_idevice_cookie_t     *idev_cookie;  /* event idev cookie */
    dev_info_t               *dip;          /* client dip */
    char                     driver_name[MODMAXNAMELEN];
} client_reg_t;

Client Types When calling csx_RegisterClient(9F), the PC Card driver must specify the type of client to be registered. Two categories of PC Card drivers are defined:
  • PC Card I/O drivers
  • PC Card memory drivers
A PC Card driver specifies whether it is a memory or I/O client driver by setting the Attributes field of the csx_RegisterClient(9F) request to INFO_IO_CLIENT or INFO_MEM_CLIENT. A client type must be specified.
The Card Services client registration process uses the driver category to determine the order in which events are sent to the drivers. The PC Card Standard specifies that PC Card I/O drivers must receive events before PC Card Memory drivers. However, no order is defined for events sent to multiple instances of the same driver. Aside from event sequencing, there are no other functional differences between the driver categories.
Event Types The PC Card driver specifies the types of events for which it is to receive notification using the EventMask field in the client_reg_t structure. For a list of valid event types, see "Event Types" on page 50.
Event Handler and Callback Data Each instance of a PC Card driver must register an event handler to manage events associated with the PC Card. The driver event handler is registered using the event_handler field of the client_req_t structure.
The driver may also provide client data that is passed to its event handler function; this is done using the event_callback_arg.client_data field. Typically, this argument is the driver instance's soft-state pointer.
Card Services Version Number The Version field contains the Card Services version number that the client driver expects to use. Typically, the driver will use the CS_VERSION macro to specify to Card Services which version of Card Services the client expects. The CS_VERSION macro is defined in the cs.h header file.
Return Values
csx_RegisterClient(9F) returns a unique client handle that the driver must use to make future Card Services requests. This identifies the instance of the driver.
csx_RegisterClient(9F) also returns a high-priority interrupt cookie that must be used to create a mutex to protect any data shared between the PC Card event handler (when handling a card removal event) and the rest of the driver. The driver uses the high-priority interrupt block cookie to initialize the high-level mutex lock that is used in the event handler entry point. For more information, see csx_event_handler(9E).

Note - PC Card drivers must register with Card Services. A PC Card driver can determine whether Card Services has been installed on the host machine by calling csx_GetCardServicesInfo(9F) or indirectly by calling csx_RegisterClient(9F) from attach(9E). If Card Services is not available when csx_GetCardServicesInfo(9F) is called, an error is returned.

Enabling Event Notification

In order for the event handler to start receiving events, the PC Card driver must call csx_RequestSocketMask(9F). Although csx_RegisterClient(9F) registers the driver's event handler, no events are delivered to the driver until after a call to csx_RequestSocketMask(9F) has been successful.
csx_RequestSocketMask(9F) requests that the client be notified of status changes for this socket. When calling csx_RequestSocketMask(9F), the driver provides an event mask in the sockevent_t structure to specify events that the PC Card driver is registering to receive for the socket. The socket event mask can be used to modify the global event mask registered with csx_RegisterClient(9F).
Note that for Solaris PCMCIA, there is only one PC Card driver instance per socket, and all events occur on a per-socket basis. Each Solaris PC Card driver instance gets called only for its particular socket and instance, and the driver only has access to that instance's data. Similarly, a Solaris PC Card driver only receives notification of events for its specific socket and instance; it is unaware of other PC Card drivers in the system.
Once csx_RequestSocketMask(9F) has been called, Card Services delivers a CS_EVENT_REGISTRATION_COMPLETE event if the PC Card driver has specified in its event mask that it is to receive this event.

Note - Note that PC Cards that use interrupts normally would not have to install an interrupt handler at attach time. Typically, a PC Card driver would install a high-level interrupt handler and a software interrupt handler when the card is inserted and becomes ready. The soft interrupt handler and high-level interrupt handler would then be removed when the card is removed to free these resources for use by other cards. For more information on installing an interrupt handler, see "Installing an Interrupt Handler" on page 68.

Example attach(9E) Routine

Code Example 5-1 shows a partial implementation of an attach(9E) routine. For a complete example of a PC Card attach routine, see "pcepp_attach()" in Chapter 9.
Code Example 5-1 PC Card attach(9E) Routine
static int
xx_attach(dev_info_t*dip, ddi_attach_cmd_tcmd)
{
   int                  instance;
   int                  ret;
   xx_state_t            *xx;
   get_status_t       get_status;
   client_reg_t       client_reg;
   sockmask_t         sockmask;
   map_log_socket_t   map_log_socket;

   instance = ddi_get_instance(dip);

   switch (cmd) {
   ...
        case DDI_ATTACH:
            break;
   ...
   }

   /* Allocate per-instance soft state */
   if (ddi_soft_state_zalloc(xx_soft_state_p,
                     instance) != DDI_SUCCESS) {
        return (DDI_FAILURE);
   }

   xx = ddi_get_soft_state(xx_soft_state_p, instance);

   /* Remember dev_info structure for xx_getinfo  */
   xx->dip = dip;
   xx->instance = instance;
   ddi_set_driver_private(dip, (caddr_t)xx);
   ...

   /* Register with Card Services */
   client_reg.Attributes = INFO_IO_CLIENT |
                         INFO_CARD_SHARE | INFO_CARD_EXCL;

client_reg.EventMask = (CS_EVENT_REGISTRATION_COMPLETE |
                 CS_EVENT_CARD_READY |
                 CS_EVENT_CARD_INSERTION |
                 CS_EVENT_CARD_REMOVAL |
                 CS_EVENT_CARD_REMOVAL_LOWP |
                 CS_EVENT_CLIENT_INFO);
client_reg.event_handler = (csfunction_t *)xx_event;
client_reg.event_callback_args.client_data = xx;
client_reg.Version = CS_VERSION;
client_reg.dip = dip;
(void) strcpy(client_reg.driver_name, XX_NAME);

ret = csx_RegisterClient(&xx->client_handle, &client_reg);

/* Get logical socket number and store in xx_state_t */
ret = csx_MapLogSocket(xx->client_handle, &map_log_socket);

xx->sn = map_log_socket.PhySocket;
...

/* Initialize the event handler mutexes and cv */
mutex_init(&xx->event_hi_mutex, "xx->event_hi_mutex",
        MUTEX_DRIVER, *(client_reg.iblk_cookie));

mutex_init(&xx->event_mutex, "xx->event_mutex",
        MUTEX_DRIVER, NULL);

cv_init(&xx->readywait_cv, "xx->readywait_cv",
        CV_DRIVER, (void *)NULL);

/* After RequestSocketMask, start receiving events. */
mutex_enter(&xx->event_mutex);
sockmask.EventMask = (CS_EVENT_CARD_INSERTION |
             CS_EVENT_CARD_REMOVAL);

ret = csx_RequestSocketMask(xx->client_handle,&sockmask);
...

/*
 * If the card is inserted and this attach is triggered by
 * an open, the open will wait until card insertion is complete
 */
 xx->card_event |= XX_CARD_WAIT_READY;
 while ((pps->card_event &
    (XX_CARD_READY | XX_CARD_ERROR)) == 0) {
    cv_wait(&xx->readywait_cv, &xx->event_mutex);

   }
   ...

   mutex_exit(&xx->event_mutex);
   ddi_report_dev(dip);
   return (DDI_SUCCESS);
   ...
}

getinfo(9E)

The getinfo(9E) entry point in a PC Card driver functions as in any other Solaris device driver with the exception of the use (and interpretation) of a driver instance number.
Since most host systems have at least two PCMCIA sockets, the driver should encode the socket number--rather than the instance number--as the minor number of the device. This allows easier identification of a PC Card with its appropriate socket. The CS_DDI_Info(9F) function can be used to retrieve the instance number, which identifies the appropriate socket number for the PC Card.
Code Example 5-2 shows the getinfo(9E) entry point. In this code example, XX_SOCKET is a driver-specific macro that retrieves the socket number from arg.
Code Example 5-2 getinfo(9E) Routine
static int
xx_getinfo(dev_info_t*dip, ddi_info_cmd_tcmd, void *arg,
                 void **result)
{
   int              rval = DDI_SUCCESS;
   xx_state_t      *xx;
   cs_ddi_info_t   cs_ddi_info;

   switch (cmd) {

   case DDI_INFO_DEVT2DEVINFO:
        cs_ddi_info.Socket = XX_SOCKET((dev_t)arg);
        cs_ddi_info.driver_name = xx_name;
        if (csx_CS_DDI_Info(&cs_ddi_info) != CS_SUCCESS) {
            return (DDI_FAILURE);
        }

    if (!(xx = ddi_get_soft_state(xx_soft_state_p,
                 cs_ddi_info.instance))) {
        *result = NULL;
    } else {
        *result = xx->dip;
    }
    break;

case DDI_INFO_DEVT2INSTANCE:
    cs_ddi_info.Socket = XX_SOCKET((dev_t)arg);
    cs_ddi_info.driver_name = xx_name;
    if (csx_CS_DDI_Info(&cs_ddi_info) != CS_SUCCESS) {
        return (DDI_FAILURE);
    }
    *result = (void *)cs_ddi_info.instance;
    break;

default:
    rval = DDI_FAILURE;
    break;
}
return (rval);

}

detach(9E)

The detach(9E) entry point removes a PC Card from the system. detach(9E) releases resources allocated with attach(9E) or with card insertion, releases the driver socket mask, deregisters with Card Services, and frees the various mutex and condition variables. Code Example 5-3 shows an example of a detach(9E) entry point.
Code Example 5-3 detach(9E) Routine
static int
xx_detach(dev_info_t*dip, ddi_detach_cmd_tcmd)
{
   int                     instance;
   int                     ret;
   xx_state_t            *xx;
   release_socket_mask_trsm;
   error2text_t          cft;

   instance = ddi_get_instance(dip);

   switch (cmd) {
   case DDI_SUSPEND:
        /*
         * DDI_SUSPEND/DDI_RESUME should be implemented
         *  to always succeed.
         */
        return (DDI_SUCCESS);

   case DDI_DETACH:
        break;

   default:
        return (DDI_FAILURE);
   }

   xx = ddi_get_soft_state(xx_soft_state_p, instance);
   if (xx == NULL) {
        cmn_err(CE_NOTE, "xx%d: no soft state\n", instance);
        return (DDI_FAILURE);
   }

   /* Call xx_card_removal to do any final card cleanup */
   if (xx->card_event & XX_CARD_READY) {
        mutex_enter(&xx->event_mutex);
        (void) xx_card_removal(xx);
        mutex_exit(&xx->event_mutex);
   }

   /* Release driver socket mask */
   ret = csx_ReleaseSocketMask(xx->client_handle,&rsm);

   /* Deregister with Card Services to stop getting events.*/
   ret = csx_DeregisterClient(xx->client_handle);

   /* Free the various mutex and condition variables */
   mutex_destroy(&xx->event_hi_mutex);
   mutex_destroy(&xx->event_mutex);
   cv_destroy(&xx->readywait_cv);

   ddi_soft_state_free(xx_soft_state_p, instance);
   return (DDI_SUCCESS);
}

PC Card Power Management Suspend and Resume

Card Services manages PC Card driver suspend and resume requests in a manner consistent with the PC Card standard. When power is suspended, the driver receives a suspend event and card removal event. The driver should handle the suspend as a card removal event because the driver has no way of knowing when the system is powered off whether the PC Card has been removed. Similarly, when power is restored, if the PC Card is present when this occurs, the driver will receive a resume event followed by a card insertion event. The driver should handle the resume event as a card insertion, as the driver will not know whether the card was removed while power was suspended.
A PC Card driver should be written to always return DDI_FAILURE in response to a DDI_SUSPEND or DDI_RESUME command, as shown in Code Example 5-4. Because PC Card suspend and resume events are handled as card removal and card insertion events, they do not need to be implemented in attach(9E) or detach(9E).
For more information on power management in the Solaris system, see the Writing Device Drivers manual.
Code Example 5-4 Power Management Suspend and Resume
static int
xx_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
   switch (cmd) {
   case DDI_RESUME:
        return (DDI_FAILURE);
   ...
   }
}

static int
xx_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
   switch (cmd) {
   case DDI_SUSPEND:
        return (DDI_FAILURE);
   ...
   }
}