Writing PCMCIA Device Drivers
  Искать только в названиях книг
Загрузить это руководство в формате PDF

PC Card Configuration

7

This chapter discusses allocation of system resources to a PC Card and provides example code illustrating PC Card configuration.

PC Card Configuration Overview

When a PC Card has been inserted and is ready for further configuration (Card Services delivers a CS_EVENT_CARD_READY event), the PC Card driver determines the card's capabilities and negotiates with Card Services for the resources it needs.
For cards requiring I/O or IRQ resources, the driver examines the card's CIS structure to determine the needed I/O address space and interrupts, and then requests the resources through Card Services. When all resources have been obtained, the driver configures the card to use the resources. For cards requiring memory resources, the driver may need to examine the CIS structure to determine the characteristics of the PC Card's memory address space before mapping the card's memory into a block of system address space.
In general, the basic tasks in PC Card configuration are:
  • Selecting a configuration option appropriate for the card and the available system resources
  • Requesting resources
  • Configuring the card, if necessary
The following sections discuss these tasks in more detail.

Selecting a Configuration Option

To select a configuration option, PC Card drivers examine the card's CIS tuple list to determine the card's characteristics and possible configurations of registers and interrupts. The driver and Card Services use this information to select the best configuration, given the card's capabilities and the available system resources.
The PCMCIA framework usually presents drivers with a list of possible configuration choices. A PC Card driver should extract all possible configurations and then sort those configurations into a preferred order. The PC Card driver then requests the set of configuration parameters it wants, and Card Services informs the driver if those resources are available. If they are not, the driver tries another combination. It isn't always possible to acquire the exact resources desired; however, different configurations might be found to work.
In general, configurations that specify resources that are typically used by non-PCMCIA devices in the system should have the lowest preference. Configurations that specify the minimum number of address lines should be given the highest preference. For example, if a serial card has five configurations where the first four specify the I/O addresses for COM1 through COM4, but the last configuration allows the I/O address to be relocated anywhere, the last one is the most preferred. If the card is essentially the same as a standard PC device (serial cards usually fit this criteria), then a configuration should be tried with the preference going to the value least likely to exist in a system. For serial cards, the order would be COM4 through COM1, for example.
PC Card configuration requirements vary depending on the type of card being configured. In general, PC Card memory drivers have simple configuration requirements (which may all be contained, for example, in the Device Information Tuple), while I/O PC Card drivers may experience considerably more difficulty matching resources to PC Card requirements.

Processing the CIS Tuples

To examine a PC Card's CIS, the driver uses the Card Services functions csx_GetFirstTuple(9F) and csx_GetNextTuple(9F) to step through the tuple data structures and locate the desired tuple. The csx_GetFirstTuple(9F) and csx_GetNextTuple(9F) functions enable a client to traverse the CIS without being aware of how tuple links are evaluated.
The linked list of tuples may be inspected one by one, or the driver may narrow the search by requesting only tuples of a particular type. The driver requests a specific tuple by filling in the tuple(9S) structure with the DesiredTuple and other necessary information. The tuple(9S) data structure is defined as:
typedef struct tuple_t {
   uint32_t    Socket;           /* socket number */
   uint32_t    Attributes;       /* tuple attributes */
   cisdata_t   DesiredTuple;     /* tuple to search for */
   cisdata_t   TupleOffset;      /* tuple data offset */
   cisdata_t   TupleDataMax;     /* max tuple data size */
   cisdata_t   TupleDataLen;     /* actual tuple data length */
   cisdata_t   TupleData[CIS_MAX_TUPLE_DATA_LEN]; /* body tuple */
   cisdata_t   TupleCode;        /* tuple type code */
   cisdata_t   TupleLink;        /* tuple link */
} tuple_t;

Once a tuple has been located, the PC Card driver can inspect the tuple data using the tuple parsing functions, such as csx_ParseTuple(9F) and csx_Parse_CISTPL_DEVICE(9F). For a list of the tuple parsing functions, see "Tuple Parsing Functions" on page 27. Note that for tuples for which no tuple parsing function is provided, the PC Card driver can retrieve the raw tuple data using csx_GetTupleData(9F). This might be the case for nonstandard, vendor-specific tuples, which the driver would need to parse itself.
Code Example 7-1 shows how the tuple data for the CISTPL_VERS_1 tuple can be obtained. For a complete example of tuple parsing, see "pcepp_parse_cis()" on page 125.
Code Example 7-1 Parsing CISTPL_VERS_1 Tuple Data
static int
xx_parse_cis(xx_state_t *xx, xx_cftable_t **cftable)
{

int                   i;
int                   ret;
...
tuple_t               tuple;
...
cistpl_vers_1_t       cistpl_vers_1;

...
/* Clear the CIS saving information structure */
bzero(cis_vars, sizeof (xx_cis_vars_t));

/* CISTPL_VERS_1 processing */
bzero(&tuple, sizeof (tuple));
tuple.DesiredTuple = CISTPL_VERS_1;
ret = csx_GetFirstTuple(xx->client_handle, &tuple);

bzero(&cistpl_vers_1, sizeof (struct cistpl_vers_1_t));

ret = csx_Parse_CISTPL_VERS_1(xx->client_handle,
        &tuple, &cistpl_vers_1);
    cis_vars->major_revision = cistpl_vers_1.major;
    cis_vars->minor_revision = cistpl_vers_1.minor;
    cis_vars->nstring = cistpl_vers_1.ns;
    for (i = 0; i < cistpl_vers_1.ns; i++) {
        strcpy(cis_vars->prod_strings[i],
                 cistpl_vers_1.pi[i]);
    }
}
....

Card Configuration for I/O Cards

I/O PC Cards typically require the use of system I/O space and interrupts. The process of card configuration for cards requiring I/O resources normally includes these tasks:
  • Requesting I/O registers with csx_RequestIO(9F)
  • Installing an interrupt handler with csx_RequestIRQ(9F)
  • Configuring the card with csx_RequestConfiguration(9F)
These tasks are described in the sections that follow. Note that some I/O cards may require memory windows; for information on allocating memory resources, see "Card Configuration for Memory Cards" on page 77.

Requesting I/O Resources

If a PC Card requires I/O resources, the driver requests the resources from Card Services by calling csx_RequestIO(9F).
int32_t csx_RequestIO(client_handle_t ch, io_req_t *ir);

The csx_RequestIO(9F) io_req_t structure describes the requested resources. It specifies one or two ranges of addresses, the logical socket number, and the number of address lines decoded by the PC Card in the specified socket. Each address range is specified by the:
  • Base port address
  • Number of contiguous ports
  • Address range attributes, which specify the data path width, and byte-ordering and data-ordering characteristics
The io_req_t structure contains the following members:
typedef struct io_req_t {
   uint32_t         Socket;                /* logical socket number */
   uint32_t         Baseport1.base;        /* base port address */
   acc_handle_t     Baseport1.handle;      /* access handle */
   uint32_t         NumPorts1;             /* 1st set contiguous ports */
   uint32_t         Attributes1;           /* 1st  attributes */
   uint32_t         Baseport2.base;        /* base port address */
   acc_handle_t     Baseport2.handle;      /* access handle */
   uint32_t         NumPorts2;             /* 2nd set contiguous ports */
   uint32_t         Attributes2;           /* 2nd attributes */
   uint32_t         IOAddrLines;/* number of address lines decoded */
} io_req_t;

If the requested base port is set to zero, Card Services returns an I/O resource based on the available I/O resources and the number of contiguous ports requested. In this case, Card Services aligns the returned resource in the host system's I/O address space on a boundary that is a multiple of the number of contiguous ports requested, rounded up to the nearest power of two. For example, if a client requests two I/O ports, the resource returned will be a multiple of two. If a client requests five contiguous I/O ports, the resource returned will be a multiple of eight.
csx_RequestIO(9F) returns an access handle corresponding to the first byte of the allocated I/O window. The PC Card driver must use this handle to access locations within the requested I/O port through the Card Services Common Access functions.
On some systems, the driver will have to make multiple calls to csx_RequestIO(9F) with different resource requests to find an acceptable combination of parameters that can be used by Card Services to allocate I/O resources. Note that the card is not configured until the driver calls csx_RequestConfiguration(9F); csx_RequestIO(9F) does not configure the card.

Notes on I/O Resource Allocation

  • It is important for drivers to use the minimum amount of I/O resources necessary. One way to do this is for the driver to parse the CIS of the PC Card and call csx_RequestIO(9F) with the minimum number of address lines necessary to decode the I/O space on the PC Card.
  • The driver must take care not to choose a configuration that would cause system resource conflicts. On x86 machines, for example, 0x3F8 is generally known as the I/O port address of the COM1 serial port. Drivers should avoid using this address (or address range).
  • For cards that support relocatable I/O addresses, the preferred way of requesting an I/O address is to request an address of 0. This results in an I/O address being allocated on the appropriate boundary for the size.

Installing an Interrupt Handler

If the PC Card requires an interrupt line, the driver installs the interrupt and registers an interrupt handler using csx_RequestIRQ(9F).
int32_t csx_RequestIRQ(client_handle_t, irq_req_t *);

In the irq_req_t structure, the driver must set the irq_handler field to the address of the interrupt handler, and set the IRQ_TYPE_EXCLUSIVE attribute, indicating that the system IRQ is dedicated to the PC Card. The irq_handler_arg field is normally set to the address of the driver's per-instance soft-state structure. csx_RequestIRQ(9F) returns an iblk_cookie that must be used to set up the mutex used in the driver interrupt handler. The irq_req_t structure is defined as:
typedef struct irq_req_t {
   uint32_t                  Socket;
   uint32_t                  Attributes;  /* IRQ attribute flags */
   csfunction_t              *irq_handler;
   void                      *irq_handler_arg;

   ddi_iblock_cookie_t       *iblk_cookie;/* IRQ iblk cookie */
   ddi_idevice_cookie_t      *idev_cookie;/* IRQ idev cookie */
} irq_req_t;

High-Level Interrupts

Because PCMCIA interrupts are always shared, PC Card driver interrupt handlers always run above lock level and may not allocate memory or use most other system services. In addition to installing a high-level interrupt handler, the driver must install a soft interrupt handler with ddi_add_softintr(9F) and request a soft interrupt cookie that must be used to set up the mutex used for the soft interrupt handler. See ddi_intr_hilevel(9F) for more information about the restrictions imposed on high-level interrupt handlers.
Two mutexes are needed to manage a high-level interrupt. One mutex protects the high-level interrupt handler and must be initialized with the interrupt cookie returned by csx_RequestIRQ(9F). The other mutex protects the soft interrupt handler and must be initialized with the interrupt cookie returned by ddi_get_soft_iblock_cookie(9F). This second mutex is also used throughout the driver to protect data from parallel access by an interrupt.
Code Example 7-2 installs a high-level interrupt handler and a soft interrupt handler.
Code Example 7-2 Installing High-Level and Soft Interrupt Handlers
static int
xx_card_configuration(xx_state_t *xx)
...

   xx_t     *xx;
                         /* soft state */
   u_int   xx_intr(caddr_t);/* high-level interrupt handler */
   u_int   xx_softintr(caddr_t);/* software interrupt handler */

   ...
   /* Allocate an IRQ */
   irq_req.Attributes = IRQ_TYPE_EXCLUSIVE;
   irq_req.irq_handler = (csfunction_t *)xx_intr;
   irq_req.irq_handler_arg = (caddr_t)xx;

   ret = csx_RequestIRQ(xx->client_handle, &irq_req);

/*
 * Initialize the interrupt mutex for protecting
 *  the card registers.
 */
mutex_init(&xx->high_mutex, "xx->high_mutex", MUTEX_DRIVER,
                 *(irq_req.iblk_cookie));

/*
 * Add soft interrupt handler, which is triggered from
 * high-level interrupt handler to service the interrupt.
 */
ret = ddi_get_soft_iblock_cookie(xx->xx_dip, DDI_SOFTINT_LOW,
          &xx->softint_cookie);

mutex_init(&xx->softint_mutex, "softint mutex", MUTEX_DRIVER,
        xx->softint_cookie);

ret = ddi_add_softintr(xx->dip, DDI_SOFTINT_LOW,
        &xx->softint_id, &xx->softint_cookie,
        (ddi_idevice_cookie_t *)NULL, xx_softintr,
        (caddr_t)xx);
...

The protocol between the high-level interrupt handler and the software interrupt must be carefully arranged. The high-level interrupt handler must only acquire the mutex initialized with the cookie returned by csx_RequestIRQ(9F). This interrupt handler must then determine if the device is in fact interrupting, arrange to begin servicing the device, trigger the software interrupt to continue servicing the interrupt by calling ddi_trigger_softintr(9F), and finally, return DDI_INTR_CLAIMED. If the device is not generating the interrupt, the interrupt handler should return DDI_INTR_UNCLAIMED.

Example High-Level Interrupt Handler

The high-level interrupt handler determines whether this instance of the device is the interrupting device, services the device, and triggers the software interrupt.
Note that the high-level interrupt handler must not acquire any locks other than the lock initialized with the high-level interrupt cookie, and it must drop the high-level mutex before triggering the software interrupt.
Code Example 7-3 High-Level Interrupt Handler
static u_int
xx_intr(caddr_t arg)
{
   xx_state_t *xx = (xx_state_t *)arg;

   /*
    * If the card isn't inserted or fully initialized yet,
    * this isn't an interrupt for us.  We must do this before
    * grabbing the mutex, since with shared interrupts, we
    * may get interrupts from other sources before we are fully
    * prepared for them.  We also need to stop accessing the
    * card promptly when the card gets yanked out
    * from under us.  The high-level card removal processing
    * clears the Card Inserted bit.
    */
   if (XX_CARD_IS_READY(xx)) {
        return (DDI_INTR_UNCLAIMED);
   }

   mutex_enter(&xx->high_mutex);

   /* If we are already interrupting, not for us */
   if (xx->is_interrupting == 1) {
        mutex_exit(&xx->high_mutex);
        return (DDI_INTR_UNCLAIMED);
   }

   if ( /* the device is interrupting */ ) {
        /* service device and disable interrupts */
        xx->is_interrupting = 1;
   }
   mutex_exit(&xx->high_mutex);

   if (xx->is_interrupting == 1) {
        ddi_trigger_softintr(xx->softint_id);
        return (DDI_INTR_CLAIMED);
   }

   return (DDI_INTR_UNCLAIMED);
}

Example Soft Interrupt Handler

The software interrupt routine is started by the high-level interrupt handler. It completes the task of processing the data.
Note that the software interrupt must first acquire the mutex initialized with the cookie returned by ddi_get_soft_iblock_cookie(9F), then acquire the high-level mutex before continuing on to service the device interrupt.
Code Example 7-4 Soft Interrupt Handler
static u_int
xx_softintr(caddr_t arg)
{
   xx_t    *xx = (xx_t *)arg;

   if (!XX_CARD_IS_READY(xx)) {
        return (DDI_INTR_UNCLAIMED);
   }

   mutex_enter(&xx->softint_mutex);
   mutex_enter(&xx->high_mutex);

   if (xx->is_interrupting == 0) {
        mutex_exit(&xx->high_mutex);
        mutex_exit(&xx->softint_mutex);
        return (DDI_INTR_UNCLAIMED);
   }

   mutex_exit(&xx->high_mutex);
   /* service the interrupt */

   mutex_enter(&xx->high_mutex);
   /* re-enable device interrupts here */

   xx->is_interrupting = 0;
   mutex_exit(&xx->high_mutex);

   mutex_exit(&xx->softint_mutex);

   return (DDI_INTR_CLAIMED);
}

Removing an Interupt Handler and Soft Interrupt Handler

Code Example 7-5 releases allocated IRQ resources. This code might be included in a card removal routine.
Code Example 7-5 Releasing IRQ Resources
...
/* Unregister the softinterrupt handler */
ddi_remove_softintr(xx->softint_id);

/* Release allocated IRQ resources. */
ret = csx_ReleaseIRQ(xx->client_handle, &irq_req);

/* Destroy mutexes */
mutex_destroy(&xx->high_mutex);
mutex_destroy(&xx->softint_mutex);
...

Configuring the PC Card and Socket

Once suitable IO and IRQ resources are found, the driver must call csx_RequestConfiguration(9F) to apply power to the socket and make the I/O and IRQ resources active. PC Card drivers can read or write to the allocated I/O port after calling csx_RequestConfiguration(9F).
int32_t csx_RequestConfiguration(client_handle_t, config_req_t *);

If the configuration attribute CONF_ENABLE_IRQ_STEERING is set in the csx_RequestConfiguration(9F) structure config_req_t Attributes field, csx_RequestConfiguration(9F) connects the PC Card interrupt line to a system interrupt previously selected by a call to csx_RequestIRQ(9F). When csx_RequestConfiguration(9F) returns successfully, the driver will receive IRQ callbacks at the interrupt handler established in the call to csx_RequestIRQ(9F).

Note - By default, PC Cards are in memory-only mode. The I/O address range in the PC Card is not enabled until csx_RequestConfiguration(9F) is called to configure the card.

csx_AccessConfigurationRegister(9F) provides access to configuration registers in attribute memory space. This function accesses the requested configuration register directly and does not return an access handle; consequently, it does not require the PC Card driver to use the Card Services Common Access functions to access these registers.

Releasing I/O and IRQ Resources

To release the current PC Card and socket configuration, the driver must call csxReleaseConfiguration(9F). After a call to csxReleaseConfiguration(9F), any I/O or IRQ resources that are no longer needed should be returned to Card Services. To return I/O resources to Card Services, use csx_ReleaseIO(9F). To return IRQ resources to Card Services, use csx_ReleaseIRQ(9F).

I/O Card Configuration Example

Code Example 7-6 shows an example of a card configuration routine for an I/O device, with emphasis on allocating I/O resources and configuring the card.
Code Example 7-6 Card Configuration for an I/O Device
static int
xx_card_configuration(xx_state_t *xx)
{
   int                       ret;
   char                      devname[16];
   char                      *dname;
   io_req_t                  io_req;
   irq_req_t                 irq_req;
   get_status_t              get_status;
   sockevent_t               sockevent;
   config_req_t              config_req;
   make_device_node_t        make_device_node;
   devnode_desc_t            *dnd;
   xx_cftable_t              *cftable = NULL;
   xx_cftable_t              *cft;
   xx_cis_vars_t             *cis_vars = &xx->cis_vars;

   ASSERT(mutex_owned(&xx->event_mutex));

   /* Get card state status */

/* Get PC Card CIS information */
ret = xx_parse_cis(xx, &cftable);

/* Try to allocate IO resources; if fail then exit */
cft = cftable;
while (cft) {
    io_req.BasePort1.base = cft->p.card_base1;
    io_req.NumPorts1 = cft->p.length1 + 1;
    io_req.Attributes1 = (IO_DATA_PATH_WIDTH_8 |
                 WIN_ACC_NEVER_SWAP |
                 WIN_ACC_STRICT_ORDER);
    io_req.BasePort2.base = 0;
    io_req.NumPorts2 = 0;
    io_req.Attributes2 = 0;
    io_req.IOAddrLines = cft->p.addr_lines;

    ret = csx_RequestIO(xx->client_handle, &io_req);
        cis_vars->card_base1 = cft->p.card_base1;
        cis_vars->length1 = cft->p.length1;
        cis_vars->card_base2 = cft->p.card_base2;
        cis_vars->length2 = cft->p.length2;

        cis_vars->addr_lines = cft->p.addr_lines;
        cis_vars->card_vcc = cft->p.card_vcc;
        cis_vars->card_vpp1 = cft->p.card_vpp1;
        cis_vars->card_vpp2 = cft->p.card_vpp2;
        cis_vars->pin = cft->p.pin;
        cis_vars->config_index = cft->p.config_index;
        break;

    cft = cft->next;
}
...

/* Normal eight contiguous registers */
xx->card_handle = io_req.BasePort1.handle;

xx->flags |= XX_REQUESTIO;

/* Allocate an IRQ */
irq_req.Attributes = IRQ_TYPE_EXCLUSIVE;
irq_req.irq_handler = (csfunction_t *)xx_intr;
irq_req.irq_handler_arg = (caddr_t)xx;

ret = csx_RequestIRQ(xx->client_handle, &irq_req);

/* Initialize the interrupt mutex */
mutex_init(&xx->irq_mutex, "xx->irq_mutex", MUTEX_DRIVER,
                 *(irq_req.iblk_cookie));
xx->flags |= XX_REQUESTIRQ;

/* Set up the client event mask */
sockevent.Attributes = CONF_EVENT_MASK_CLIENT;
ret = csx_GetEventMask(xx->client_handle,&sockevent);

sockevent.EventMask |= CS_EVENT_CARD_READY;
ret = csx_SetEventMask(xx->client_handle, &sockevent);

/* Configure the PC Card */
config_req.Attributes = 0;
config_req.Vcc = cis_vars->card_vcc;
config_req.Vpp1 = cis_vars->card_vpp1;
config_req.Vpp2 = cis_vars->card_vpp2;
config_req.IntType = SOCKET_INTERFACE_MEMORY_AND_IO;
config_req.ConfigBase = cis_vars->config_base;
config_req.Status = 0;
config_req.Pin = cis_vars->pin;
config_req.Copy = 0;
config_req.ConfigIndex = cis_vars->config_index;
config_req.Present = cis_vars->present;

ret = csx_RequestConfiguration(xx->client_handle,&config_req);

xx->flags |= XX_REQUESTCONFIG;

/*
 *  Create the minor devices for this instance
 *  The minor number is the socket number
 */
dname = devname;
make_device_node.Action = CREATE_DEVICE_NODE;
make_device_node.NumDevNodes = 1;

make_device_node.devnode_desc =
    kmem_zalloc(sizeof (struct devnode_desc) *
        make_device_node.NumDevNodes, KM_SLEEP);

dnd = &make_device_node.devnode_desc[0];
dnd->name = dname;
dnd->spec_type = S_IFCHR;
dnd->minor_num = xx->sn;
dnd->node_type = XX_NT_PARALLEL;

   ret = csx_MakeDeviceNode(xx->client_handle,&make_device_node);
   ...
}

Card Configuration for Memory Cards

The memory allocation process for memory PC Cards involves mapping the memory address range within the PC Card driver's address space in system address space into PC Card memory. Although memory PC Cards typically don't require I/O space or IRQs, they may request I/O resources if necessary. In addition, nonmemory cards that have common or attribute memory that the driver needs to access (for example, some I/O cards) may allocate memory windows.
Drivers that require memory resources can map the PC Card's memory to system address space by:
  • Requesting system address space with csx_RequestWindow(9F)
  • Mapping PC Card memory to the allocated system address space with csx_MapMemPage(9F)
The following sections provide information on these tasks.

Requesting System Address Space

To request that an area of system address space be assigned to a region of PC Card common or attribute memory, a PC Card driver calls the csx_RequestWindow(9F) function.
int32_t csx_RequestWindow(client_handle_t, window_handle_t *,
                               win_req_t *);

The csx_RequestWindow(9F) win_req_t structure describes the requested memory resources. It specifies the following:
  • Base address of the memory window
  • Size of the requested memory area
  • Memory window attributes that define the type of memory window, the data path width, and the byte-ordering and data-ordering characteristics. An attribute also sets a bit to enable the memory window.
  • Parameter for the window access speed if the driver is requesting a memory window
  • Parameter for I/O address lines decoded for an I/O window if the driver is using csx_RequestWindow(9F) to request an I/O address range
  • Logical socket number
  • Window offset for use with csx_MapMemPage(9F)
The win_req_t structure is defined as:
typedef struct win_req_t {
   uint32_t         Socket;
   uint32_t         Attributes;       /* window flags */
   uint32_t         Base.base;        /* requested window base address */
   acc_handle_t     Base.handle;      /* handle for base of window */
   uint32_t         Size;             /* window size */
   uint32_t         win_params.AccessSpeed;/* window access speed */
   uint32_t         win_params.IOAddrLines;/* for I/O windows only */
   uint32_t         ReqOffset;        /* required window offset */
} win_req_t;

Note the following when requesting memory address space:
  • The driver should set the base address of the memory window to zero. The driver should not request a specific physical address, as a request of this type will most likely fail.
  • Due to hardware dependencies, do not assume that drivers can request a memory window of any specific size; instead, be prepared to deal with different window sizes. For example, if there are memory window sizes that the driver can optimize, it can request those sizes first. In most cases, the driver will register a memory window size of zero to instruct Card Services to return the smallest available window.
csx_RequestWindow(9F) returns the size of the allocated memory area and returns a window handle that corresponds to the first byte of the allocated memory window. The driver will use this handle when accessing the PC Card's memory space through the Common Access functions.

Note - Driver developers should be aware that if any nonzero memory window size is specified, the driver may not be portable. This is due to the fact that some adapters have fixed size memory windows of 1 Mbyte, while others have variable-sized windows. Drivers that request specific sizes must be
prepared to negotiate sizes in order to be portable. Also note that SPARC machines require data access on natural boundaries (byte on byte, short on short, and long on long), while the x86 platform has no such alignment constraints and can access a long on any arbitrary boundary.

Mapping PC Card Memory to System Address Space

Once a memory window has been allocated and the window handle has been obtained, the driver can map the memory area on the PC Card to the allocated memory using the csx_MapMemPage(9F) function. csx_MapMemPage(9F) sets the offset of the PC Card to the allocated window. This is illustrated in Figure 7-1, where a 4 Kbyte window is shown mapped to a PC Card with 64 Kbytes of common or attribute memory space.

Графика

Figure 7-1

The 4 Kbyte memory window in the figure can be moved or remapped anywhere within the 64 Kbyte common or attribute memory space according to the needs of the driver or the PC Card device. In this sense, windows can be made to slide across PC Card memory space.
The offset in bytes from the beginning of the PC Card to the memory area is defined by the value of map_mem_page_t CardOffset. The driver cannot depend on the card offset being set to any particular default value for any particular window.
int32_t csx_MapMemPage(window_handle_t, map_mem_page_t *);

typedef struct map_mem_page_t {
   uint32_t         CardOffset;/* card offset */
   uint32_t         Page;         /* page number */
} map_mem_page_t;

Note that a driver should never position a window to an arbitrary offset. A window offset must also take into account window alignment and size requirements.
The csx_RequestWindow(9F) win_req_t structure returns a WIN_OFFSET_SIZE flag in the Attributes field that indicates that all offsets used when calling csx_MapMemPage(9F) must be a multiple of the window size returned from csx_RequestWindow(9F). For example, if csx_RequestWindow(9F) returns 4 Kbytes in the Size field of the win_req_t structure, the PC Card driver is only allowed to give csx_MapMemPage offsets in multiples of 4 Kbytes (0 Kbytes, 4 Kbytes, 8 Kbytes, 16 Kbytes, and so on).

Modifying a Memory Address Window

A PC Card driver can modify the memory access speed or the type of memory address window with csx_ModifyWindow(9F). The type of memory window can be changed from common to attribute, or vice versa.

Releasing Memory Resources

To release memory resources, the driver must call csx_ReleaseWindow(9F). This function releases window resources that were allocated by csx_RequestWindow(9F). No adapter or socket hardware is modified by csx_ReleaseWindow(9F).

Memory Card Configuration Example

Code Example 7-7 shows a card configuration routine for a memory device, with emphasis on requesting memory resources and configuring the card.
Code Example 7-7 Card Configuration for a Memory Device
static int
xx_card_insertion(xx_state_t  *xx)

{
   int                   ret;
   int                   rval = CS_SUCCESS;
   sockevent_t           se;
   win_req_t             win_req;
   convert_speed_t       convert_speed;
   map_mem_page_t        map_mem_page;
   get_status_t          get_status;

   /* Get card state status */
   ret = csx_GetStatus(xx->client_handle, &get_status);

   /* Make sure that there is a card in the socket */
   if (!(get_status.CardState & CS_EVENT_CARD_INSERTION)) {
        /* error handling */
   }

   /* Set up the client event mask */
   se.Attributes = CONF_EVENT_MASK_CLIENT;
   ret = csx_GetEventMask(xx->client_handle, &se);

   se.EventMask |= (CS_EVENT_BATTERY_LOW |
                CS_EVENT_BATTERY_DEAD |
                CS_EVENT_WRITE_PROTECT);

   ret = csx_SetEventMask(xx->client_handle, &se);

   /*
   /* if necessary, release any allocated windows before
   /* requesting new windows
   /*
   ...

   /* Try to get a memory window to Common Memory space */
   win_req.Attributes = (WIN_MEMORY_TYPE_CM |
                WIN_DATA_WIDTH_16 |
                WIN_ENABLE |
                WIN_ACC_NEVER_SWAP |
                WIN_ACC_STRICT_ORDER);
   win_req.Base.base = 0;/* let CS find a base address */
   win_req.Size = 0;/* let CS return the smallest size window */

   convert_speed.Attributes = CONVERT_NS_TO_DEVSPEED;
   convert_speed.nS = 250;
   csx_ConvertSpeed(&convert_speed);

   win_req.win_params.AccessSpeed = convert_speed.devspeed;

   ret = csx_RequestWindow(xx->client_handle,
            &xx->window_handle, &win_req);

   xx->flags |= XX_HAS_WINDOW;

   /* Now map the offset to the start of the card */
   map_mem_page.CardOffset = 0;
   map_mem_page.Page = 0;

   ret = csx_MapMemPage(xx->window_handle,&map_mem_page);

   /* Store xx->access_handle */
   xx->access_handle = win_req.Base.handle;
   xx->win_size = win_req.Size;

   /* Read CIS information for the card size */
   ...

   /* Create the device nodes */
   ...

   xx->card_event |= XX_CARD_IS_READY;
   ...

   return (rval);
}