NAME | SYNOPSIS | API RESTRICTIONS | FEATURES | DESCRIPTION | EXTENDED DESCRIPTION | PROPERTIES | ALLOWED CALLING CONTEXTS | ATTRIBUTES | SEE ALSO
#include <ddi/pcmcia/pcmcia.h>
The function or functions documented here may not be used safely in all application contexts with all APIs provided in the ChorusOS 5.0 product.
See API(5FEA) for details.
DDI
Provides an API for the development of PCMCIA bus device drivers.
The PCMCIA bus driver offers an API for pcmcia development. This PCMCIA bus API is an abstraction of the low-level PCMCIA bus services and covers the following PCMCIA functional modules:
PCMCIA interrupt management.
Access to the I/O device registers.
Access to the memory of the PCMCIA device.
Power management of the PCMCIA device.
Generation of PCMCIA device events.
First of all, a pcmcia must register itself in the microkernel driver registry. This should be done from its main routine using svDriverRegister(9DKI). The driver must set the bus class to "pcmcia" or "common" (with some restrictions, see PCMCIA probing mechanisms) in its registry entry, and specify the lowest version number of the PCMCIA bus interface required to run correctly. This registration allows the PCMCIA bus driver to call back pcmcia's drv_probe(), drv_bind() and drv_init() routines at bus probing, binding and initialization phases, respectively.
Within these drv routines, pcmcia can open a connection to the PCMCIA bus driver using the open() function. Once such a connection is established, pcmcia may use services provided by the PCMCIA bus driver. See the PCMCIA Probing Mechanisms section below for more details about drv_probe(), drv_bind() and drv_init() functions.
Initialization Connection
If the drv_init() routine is defined, the PCMCIA bus driver invokes it at bus initialization time. The drv_init() routine is called in the context of a DKI thread which makes it possible to invoke the PCMCIA bus services allowed in the DKI thread context only. The drv_init() routine is defined if the drv_init field of the driver registry entry is set to a non NULL value.
The drv_init() routine is called with three arguments:
pcmcia's own node, which identifies the node associated with the device in the device tree.
The PCMCIA bus operations structure, which defines the PCMCIA bus API and its version.
The PCMCIA bus identifier, which is an opaque type to pass back when calling the PcmciaBusOps.open() operation to connect the device driver to the PCMCIA bus.
From this point, PcmciaBusOps.open() must be the first call issued by pcmcia to the PCMCIA bus driver:
KnError (*open) (PcmciaId pcmcia_id, DevNode dev_node, PcmciaEventHandler dev_event_handler, PcmciaLoadHandler dev_load_handler, void* dev_cookie, PcmciaDevId* dev_id);
This establishes a connection to the PCMCIA bus, identified by the pcmcia_id value returned in the last argument. The returned dev_id parameter identifies the new connection.
pcmcia_id and dev_node are given by the PCMCIA bus driver as parameters to the driver drv_init() routine. dev_event_handler is a handler in the device driver, invoked by the PCMCIA bus driver, when a PCMCIA bus event occurs. dev_event_handler is optional but must be set to NULL when it is not implemented by the device driver. The dev_cookie argument is passed back to this handler as the first argument. This PCMCIA bus event handler takes two additional parameters. The first additional parameter is the bus event type, which is one of the following:
Notifies a device driver that the system is going to be shut down. The device driver should reset the device hardware and return from the event handler. The driver must neither notify clients nor free allocated resources.
Notifies a device driver that the device should be shut down. The device driver should notify driver clients, via the event handler that is defined by the client when opening, that the device is going to be shut down and should then return from the event handler. Once the device entry is released by the last driver client, the device registry module invokes a driver callback handler. Within this handler, the device driver should reset the device hardware, release all used resources and close the bus connection by invoking PcmciaBusOps.close. The PCMCIA_DEV_SHUTDOWN event may be used by a bus driver in order to confiscate (or to re-allocate) bus resources.
Notifies a device driver that the device has been removed from the bus and therefore the device driver instance has to be shut down. The actions taken by the driver are similar to the PCMCIA_DEV_SHUTDOWN case except that the device hardware must not be accessed by the driver.
This event has been added in the PCMCIA_VERSION_1. It signaled a defective interrupt line. In this case, the device driver should detach from this interrupt line, and from the bus driver, as normal operation cannot continue.
Notifies a device driver that the card status changed on the bus (a card has been inserted or removed). This is useful for drivers probing the PCMCIA bus (called card enablers): once notified, they can interpret the CIS present in the PCMCIA card and then fulfill the PROP_DRIVER property. The PCMCIA bus driver will then be able to call the drv_init routine of the driver matching the new inserted card.
Notifies a device driver that the memory card is ready. This is only relevant to memory cards.
Notifies a device driver that the card battery is low on power.
Notifies a device driver that the card battery is dead and must be replaced quickly to prevent data from being lost.
The last fourevents (PCMCIA_CARD_DETECT, PCMCIA_READY, PCMCIA_BVD2, PCMCIA_BVD1) have to be registered using the PcmciaBusOps.enable_events() function to be triggered.
The second additional parameter is an event type specific structure pointer: This argument is always NULL for the PCMCIA bus events.
As the event handler may be executed in the context of an interrupt, its implementation must be restricted to the API allowed at interrupt level.
dev_load_handler is a handler in the device driver which is invoked by the PCMCIA bus driver, when a new driver appears in the system (for example, a new driver is downloaded at run time). Note that dev_load_handler is optional and must be set to NULL when it is not implemented by the device driver. The dev_cookie argument is also passed back to this handler as a unique argument. Typically, a leaf pcmcia is not interested in such an event. dev_load_handler is usually used by a PCMCIA nexus driver which is interested in applying a newly downloaded driver to its child devices which are not yet serviced, or by a card enabler which has detected a PCMCIA card without a driver. loadHandler() is called in the DKI thread context.
On success, PcmciaBusOps.open() returns K_OK and a valid identifier is returned in the dev_id argument. This identifier must be used to call other services defined in the PcmciaBusOps structure. Some of these services also return an Ops structure defining new services on a new object instance, like an I/O or a memory region, for example. Others perform a simple service for the device driver, without giving access to a part of the API.
On failure, PcmciaBusOps.open() returns an error code as follows:
The device node is invalid, that is, the device node is not a child of the bus node.
A connection is already open for the given device node.
The system is out of memory.
The PCMCIA card corresponding to the driver has been removed.
To release the connection with the PCMCIA bus, the driver must call the PcmciaBusOps.close() service:
void (*close) __((PcmciaDevId dev_id));
After being disconnected from the PCMCIA bus, the device driver is not allowed to use any of the PCMCIA bus API services.
Probing Connection
If the drv_probe() routine is defined, the PCMCIA bus driver invokes it at bus probing time. The drv_probe() routine is called in the context of DKI threads which makes it possible to invoke the PCMCIA bus services allowed in the DKI thread context only.
The probe routine is not defined if the drv_probe field of DrvRegEntry is set to NULL.
The drv_probe() routine is called with three arguments:
pcmcia's own node, which identifies the node associated with the device in the device tree.
The PCMCIA bus operations structure, which defines the PCMCIA bus API and its version.
The PCMCIA bus identifier, which is an opaque type to pass back when calling the PcmciaBusOps.open() routine to connect the device driver to the PCMCIA bus.
The drv_probe() invocation precedes the drv_init() invocation. That gives an opportunity for pcmcia to discover a device on the bus, which can be serviced by the device driver, and create a node for this device in the device tree. If drv_probe() creates a node corresponding to a device residing on the bus, it should put properties specifying the bus resources required, on the device node (for example, intr and io-regs). Looking at the device node properties, the PCMCIA bus driver will allocate bus resources for the device, if required, and will check resource conflicts with other devices residing on the bus.
Once such a device is discovered and the device node is created, the driver component will be bound to the device. This type of driver initiated binding is done by putting the driver property on the device node. The driver property value is a NULL terminated ASCII string specifying the driver name. The driver name specified in the property must match the driver name specified in the driver registry entry. Under such conditions, the PCMCIA bus driver will invoke the driver's drv_init() routine for this device node as described above. The drv_init() routine is invoked by the bus driver if, and only if, the bus resources required for the device are successfully allocated and checked by the bus driver.
Despite the presence of other busses, the PCMCIA bus does not provide a mechanism to identify devices attached to it. Only heuristic methods can be used, such as reading memory locations, to be sure that the device identified is the one expected. Therefore it is not recommended to use the drv_probe() routine to discover and identify PCMCIA devices. PCMCIA devices are, in general, built statically when building the device tree during the boot process.
A HBA (Host Bus Adapter) has a fixed number of sockets which can be used by different cards. Thus, the device tree has a particular structure for a PCMCIA node:
There is the HBA node which contains properties for the HBA (its I/O regions to be mapped and its interrupt requirements, for example) but also properties describing the sockets and some of their properties. Here is a possible configuration:
DevNode i82365; IsaPropIoRegs ioRegs; IsaPropIntr intr,sockintr[2]; IsaPropMemRgn mem; i82365 = dtreeNodeAdd(isa, I82365_DEV_NAME); dtreePropAdd(i82365, PROP_DRIVER, I82365_DRV_NAME, strlen(I82365_DRV_NAME)+1); ioRegs.addr = 0x3e0; ioRegs.size = 0x2; dtreePropAdd(i82365, ISA_PROP_IO_REGS, &ioRegs, sizeof(IsaPropIoRegs)); intr.irq = 12; /* HBA Interrupt */ intr.type = ISA_INTR_T_LEVEL; /* edge required */ intr.mask = 1 << intr.irq; dtreePropAdd(i82365, ISA_PROP_INTR, &intr, sizeof(IsaPropIntr)); mem.addr = 0xE0000; mem.size = 0x10000; dtreePropAdd(i82365, ISA_PROP_MEM_RGN, &mem, sizeof(IsaPropMemRgn)); sockintr[0].irq = 7; sockintr[0].type = ISA_INTR_T_LEVEL; sockintr[0].mask = 1<<sockintr[0].irq; sockintr[1].irq = 15; sockintr[1].type = ISA_INTR_T_LEVEL; sockintr[1].mask = 1 << sockintr[1].irq; dtreePropAdd(i82365, PCMCIA_PROP_SOCK_INTR, &sockintr, 2*sizeof(IsaPropIntr));
Some properties relative to the HBA are present (ioRegs and intr variables) and an array describes the sockets and the interrupt they use (sockintr property).
There must be a node for each socket. These nodes are children of the HBA node. In this way, the child drivers do not have to create a node for the device they are driving. The socket node is given to the drv_probe() function, instead of the HBA node, as in the past. These socket nodes contain a property giving the slot number it is attached to. Here is the simplest example of nodes corresponding to the previous description:
DevNode sock0, sock1; int slotNbr; sock0 = dtreeNodeAdd(i82365, "socket-0"); slotNbr = 0; dtreePropAdd(sock0, PCMCIA_PROP_SOCK_NUM, &slotNbr, sizeof(int)); sock1 = dtreeNodeAdd(i82365, "socket-1"); slotNbr = 1; dtreePropAdd(sock1, PCMCIA_PROP_SOCK_NUM, &slotNbr, sizeof(int));
In order to start the driver, several properties have to be present in the device tree before calling the drv_init() routine, for example PCMCIA_PROP_IO_REGS. See the PCMCIA Specific Node Properties section.
There are 3 ways to reach this goal:
The completely static way.
The properties needed can simply be put in the device tree. See the algorithm run by the HBA.
The partially static way.
PCMCIA cards often offer several possible configuration sets stored in the CIS memory. Depending on the available resources in the system, you can choose a configuration which only uses free ones. You then have to fill the device tree by defining these sets. When the HBA finds these sets, it looks for a free configuration, then adds the properties describing the chosen configuration (For example, a PCMCIA_PROP_IO_REGS property) and calls the drv_init() function.
Each configuration has a number. The default configuration is 0. A possible set of resources, n, groups resources of number n. It groups resources of number 0 only if there are no resources of number n.
Here is an example of a configuration which gives a default configuration and a specific configuration for the I/O resource:
PcmciaCorValue corValues[] = { {PCMCIA_CISREG_COR, 0x43}, {0xFF, 0x0}, }; PcmciaPropCOR propCOR[] = { { { 0x10000, corValues }, 0 }, { {}, 0xFF } }; PcmciaPropPower propPower[] = { { {PCMCIA_PWR_5V, PCMCIA_PWR_5V, PCMCIA_PWR_5V}, 0}, { {}, 0xFF } }; PcmciaPropIoMap propIoMap[] = { { {0x320, 16, PCMCIA_SPEED_150, PCMCIA_DATA_ANY}, 0 }, { {0x300, 16, PCMCIA_SPEED_150, PCMCIA_DATA_ANY}, 1 }, { {}, 0xFF} }; dtreePropAdd(sock0, PCMCIA_PROP_COR, propCOR, 2*sizeof(PcmciaPropCOR)); dtreePropAdd(sock0, PCMCIA_PROP_POWER, propPower, 2*sizeof(PcmciaPropPower)); dtreePropAdd(sock0, PCMCIA_PROP_IO_MAP, propIoMap, 2*sizeof(PcmciaPropIoMap));
The drv_probe() routines.
The last possibility is to write drv_probe() routines.
There are 2 cases where you can implement a probe routine:
Your driver is a specific PCMCIA driver which contains a probe routine.
You write a probe only driver (called a card enabler) which implements the probing mechanisms of the common driver corresponding to the card. Sample card enablers are provided within the ChorusOS drivers.
To make use of all the features offered by this powerful probing mechanism, you need to know the different steps followed by the HBA driver:
At HBA driver initialization time, all static properties found in the device tree are saved.
When a card is detected, the HBA driver restores the static properties found at initialization time.
The HBA driver calls all the drv_probe() routines in drivers of the pcmcia or common class. These probe routines are free to delete all static properties found and replace them with their own properties.
If the PCMCIA_PROP_COR property is found, indicating that only sets of configurations are given, the HBA looks for a configuration using only free resources and creates nodes corresponding to the choice.
The HBA driver launches the drv_init() routine of the driver given in the PROP_DRIVER property.
When the card is removed, nothing is done in the device tree. When a new PCMCIA card, or the same card, is inserted, the procedure is repeated from step 2.
To connect a handler to a PCMCIA interrupt, a PCMCIA driver should:
Disable the interrupt at the device level, typically by accessing the device registers.
Attach a handler to the interrupt.
Enable the interrupt at the device level.
Use services defined by the attached interrupt object.
Disable the interrupt at the device level.
Detach the interrupt handler when no longer needed.
Attaching a Handler to a PCMCIA Interrupt
A pcmcia can connect a handler to a PCMCIA interrupt by calling the PcmciaBusOps.intr_attach() service:
KnError (*intr_attach) __((PcmciaDevId dev_id, PcmciaPropIntr* dev_intr_value, PcmciaIntrHandler dev_intr_handler, void* dev_intr_cookie, PcmciaIntrOps** intr_ops, PcmciaIntrId* intr_id));
The dev_id argument identifies the connection with the PCMCIA bus driver.
The dev_intr_value argument indicates the PCMCIA interrupt to which the handler will be connected. This property should be retrieved by the driver using the device tree API, prior to calling PcmciaBusOps.intr_attach(). The name of the property used to describe a PCMCIA interrupt is PCMCIA_PROP_INTR. Its value contains a PcmciaProplntr value which can take one of the following values:
The interrupt is triggered by a high signal.
The interrupt is triggered by driving the line low. The pull-up resistors will then generate the rising edge. This low to high transition registers an interrupt request managed by the interrupt controller.
PCMCIA devices generally use level interrupt types due to the HBA hardware specifications.
The interrupt type is the only relevant parameter for the child driver. Other parameters such as the IRQ number are only known by the HBA.
The intrHandler argument is a handler in the device driver which is invoked by the PCMCIA bus driver when the corresponding interrupt occurs on the bus:
typedef PcmciaIntrStatus (*PcmciaIntrHandler) (void* dev_intr_cookie);
The dev_intr_cookie is passed back to this interrupt handler as an argument. This type of PCMCIA interrupt handler must return a PcmciaIntrStatus value which indicates to the bus driver whether the interrupt was claimed by the handler, and how it was handled:
Must be returned by the interrupt handler if there is no pending interrupt for the device.
Must be returned by the interrupt handler if a pending device interrupt has been serviced by the interrupt handler and the interrupt has not been enabled (acknowledged) at PCMCIA bus level (see the Enabling/Disabling a Serviced Interrupt section).
Must be returned by the interrupt handler if a pending device interrupt has been serviced by the interrupt handler and the interrupt has been enabled (acknowledged) at PCMCIA bus level (see the Enabling/Disabling an Attached Interrupt section).
On success, K_OK is returned and services defined on an attached interrupt object are returned in the intr_ops parameter. An identifier for the attached interrupt is also returned in intr_id. This identifier must be used as first parameter to further calls to the PcmciaIntrOps() services.
As explained above, the PCMCIA bus architecture does not allow an interrupt to be shared among multiple devices residing on the bus.
Masking/Unmasking an Attached Interrupt
The PcmciaIntrOps.mask() service routine masks the interrupt source specified by intr_id:
void (*mask) (PcmciaIntrId intr_id);
This service routine does not guarantee that all other interrupt sources are still unmasked.
The PcmcialntrOps.unmask() service routine unmasks the interrupt source previously masked by PcmciaIntrOps.mask():
void (*unmask) (PcmciaIntrId intr_id);
This service routine does not guarantee that the interrupt source is unmasked immediately. The real interrupt source unmasking may be deferred.
The PcmcialntrOps.mask()/PcmciaIntrOps.unmask() pair may be used at either base or interrupt level. Note that the Pcmcialntrops.mask()/PcmcialntrOps.unmask() pairs cannot be nested.
Enabling/Disabling a Serviced Interrupt
The PcmciaIntrOps.enable() and PcmcialntrOps.disable() service routines are dedicated to interrupt handler usage only. In other words, these routines may only be called by an interrupt handler.
The PcmcialntrOps.enable() service routine enables and acknowledges the bus interrupt source specified by intr_id:
PcmciaIntrStatus (*enable) (PcmciaIntrId intr_id);
PcmciaIntrOps.enable() returns either PCMCIA_INTR_ACKNOWLEDGED or PCMCIA_INTR_CLAIMED. The PCMCIA_INTR_ACKNOWLEDGED return value means that the PCMCIA bus driver has enabled and acknowledged an interrupt at bus level. The PCMCIA_INTR_CLAIMED return value means that the PCMCIA bus driver has ignored the enable request and therefore the interrupt source is still disabled and not acknowledged at bus level.
In cases where the PcmcialntrOps.enable() routine has been called by an interrupt handler, the handler must return the value which was returned by PcmcialntrOps.enable(). Once PcmciaIntrOps.enable() is called, the driver should be able to handle an immediate re-entrance in the interrupt handler code.
The Pcmcialntrops.disable() service routine disables the interrupt source previously enabled by PcmciaIntrOps.enable():
void (*disable) (PcmciaIntrId intr_id);
lf PcmciaIntrOps.enable() returns PCMCIA_INTR_ACKNOWLEDGED, the driver must call PcmcialntrOps.disable() prior to returning from the interrupt handler.
When an interrupt occurs, the attached PcmciaIntrHandler() is invoked with the interrupt source disabled at bus level. This produces the same result as calling PcmciaIntrOps.disable() prior to handler invocation. The interrupt handler must return to the bus driver in the same context as it was called, that is, with the interrupt source disabled at bus level. The called interrupt handler may, however, use the PcmcialntrOps.enable()/PcmciaIntrOps.disable() pair to allow the interrupt to be nested.
Detaching an Attached Interrupt
When a driver no longer needs to handle an interrupt, or before closing the connection to the PCMCIA bus, it should detach the handler attached to an interrupt line.
The PcmciaBusOps.intr_detach() is used to detach a handler, previously attached with PcmciaBusOps.intr_attach(), and to release any resources allocated for this attachment:
void (*intr_detach) __((PcmciaIntrId intr_id));
The intr_id argument identifies the attachment to release.
When the PcmciaBusOps.intr_detach() function is called, the driver can no longer use the identifier intr_id.
To perform I/O access to registers of a PCMCIA device, a PCMCIA driver must:
Map the device's registers into an I/O region.
Use services defined by the mapped I/O region to access registers.
Unmap the region when access is no longer needed.
The PcmciaBusOps.io_map() service is used to map an I/O region from a PCMCIA device to enable access to this region:
KnError (*io_map) __((PcmciaDevId dev_id, PcmciaPropIoRegs* param, PcmciaErrHandler err_handler, void* err_cookie, PcmciaIoOps** io_ops, PcmciaIoId* io_id));
dev_id is returned by PcmciaBusOps.open().
The param structure defines the I/O region to map. This structure is stored in a property of the device node. This property should be retrieved by the driver using the device tree API (dtreePropFind(), dtreePropLength(), dtreePropValue()), prior to calling PcmciaBusOps.io_map().
The name of the property used to describe device I/O regions is PCMCIA_PROP_IO_REGS. Its value contains a PcmciaPropIoRegs structure shown below:
typedef struct PcmciaPropIoRegs { PcmciaAddr base; /* base address in io space */ PcmciaSize size; /* size in bytes */ PcmciaSpeed speed; /* or int in ns */ PcmciaPath width; /* io-map data path (8/16/any) */ } PcmciaPropIoRegs;
The base field specifies the PCMCIA start address of the register's range.
The size field specifies the register's range size in bytes.
The speed field specifies the access speed supported by the PCMCIA card. Its value must be one of the following:
PCMCIA_SPEED_ANY should work with all devices in the case where the best value is unknown.
The width field specifies whether accesses to the PCMCIA card I/O region will be performed in 8 or 16 bits. Its value must be one of the following:
PCMCIA_DATA_ANY means that the HBA will switch between 8 and 16 bit access configuration depending on the function called (xxx_8 or xxx_16 routines, see below).
The err_handler argument is a handler in the device driver, which is invoked by the PCMCIA bus driver, when a PCMCIA bus error occurs while accessing the mapped region. It is not always possible for a PCMCIA bus driver to detect this type of error, and in this case, the err_handler will never be called. err_cookie is passed back to the err_handler as first parameter (see the PCMCIA Error Handling section). The main case where err_handler is called is an access to a mapped IO region when the PCMCIA card has been removed.
On success, K_OK is returned and appropriate I/O services are returned in the io_ops parameter. An identifier for the mapped I/O region is also returned in io_id. This identifier must be used as the first parameter in further calls to the PCMCIA I/O operations services, as defined in PcmcialoOps().
On failure, PcmciaBusOps.io_map() returns one of the following error codes:
A zero size was specified.
Not enough virtual address space to map I/O registers.
The system is out of resources.
The PCMCIA card has been removed.
Once the region is successfully mapped, the driver can use the services defined by the PcmciaIoOps structure.
The PCMCIA bus provides four service routine sets to access a mapped I/O region:
PcmciaIoOps.load_8/16/32/64
PcmciaIoOps.store_8/16/32/64
PcmciaIoOps.read_8/16/32/64
PcmciaIoOps.write_8/16/32/64
/* Routines added to PCMCIA_VERSION_1 */
PcmciaIoOps.read_swap_16/32/64
PcmciaIoOps.write_swap_16/32/64
There are four service routines in each set which deal with I/O registers of different width. The _8, _16, _32 or _64 suffix indicates the data size of the transfer on the PCMCIA bus.
Even if 32 and 64 bit accesses are present, to be compatible with common bus I/O operations, they are not implemented by the PCMCIA bus device. In the case they are called, they simply send a PCMCIA_ERR_INVALID_SIZE error to the err_handler if provided by the client driver.
In all service routines provided by PcmciaIoOps(), the io_id argument identifies the mapped I/O region as returned by PcmciaBusOps.io_map().
The offset argument specifies the register offset, in bytes, within the mapped region.
All these routines handle byte swapping should the endian be different between the PCMCIA bus and CPU.
Load from a Register
The PcmciaIoOps.load_xx() routine set returns a value loaded from a device register:
uintxx_f (*load_xx) (PcmciaIoId io_id, PcmciaSize offset);
The size of the returned value and of the data transfer on the PCMCIA bus is specified by the xx suffix.
Store to a Register
The PcmciaIoOps.store_xx() routine set stores a given value into a device register:
void (*store_xx) (PcmciaIoId io_id, PcmciaSize offset, uintxx_f value);
The size of the given value and of the data transfer on the PCMCIA bus is specified by the xx suffix.
Multiple Read from a Register
The PcmciaIoOps.read_xx() routine set loads values from a specified device register and sequentially writes them into a memory buffer:
void (*read_xx) (PcmciaIoId io_id, PcmciaSize offset, uintxx_f* buf, PcmciaSize count);
The size of each value loaded and of each data transfer on the PCMCIA bus is specified by the xx suffix.
The count argument specifies the number of read transactions to perform.
The buf argument specifies the address of the memory buffer. The size of this buffer must be at least the value of count multiplied by the size of each data transfer.
Multiple write to a Register
The PcmciaIoOps.write_xx() routine set, reads values sequentially from a memory buffer and stores them into a device register:
void (*write_xx) (PcmciaIoId ioId, PcmciaSize offset, uintxx_f* buf, PcmciaSize count);
The size of each value stored and of each data transfer on the PCMCIA bus is specified by the xx suffix.
The count argument specifies the number of write transactions to perform.
The buf argument specifies the address of the memory buffer. The size of this buffer must be at least the value of count multiplied by the size of each data transfer.
When a driver no longer needs access to the device I/O register, or before closing the connection to the PCMCIA bus, it should unmap the I/O region used for these devices.
PcmciaBusOps.io_unmap() is used to unmap an I/O region previously mapped with PcmciaBusOps.io_map(), and to release any resources allocated for this mapping:
void (*io_unmap) (PcmciaIoId io_id);
The io_id argument identifies the region to unmap.
When the PcmciaBusOps.io_unmap() function is called, the driver can no longer use any services defined for the unmapped I/O region.
To perform a memory access to a PCMCIA device, a PCMCIA driver must:
Map the device's memory region.
Use services defined by the mapped memory region.
Unmap the region when access is no longer needed.
Mapping a device's memory region
The PcmciaBusOps.mem_map() service is used to map a memory region from a PCMCIA device to enable access to this region:
KnError (*mem_map) __((PcmciaDevId dev_id, PcmciaPropMemRgn* mem_param, PcmciaMemAttr mem_attr, PcmciaErrHandler err_handler, void* err_cookie, PcmciaMemOps** memOps, PcmciaMemId* memId));
In the case where the child driver is a common bus driver, any mem_map operation it is given will simply return K_EFAIL. This is because only PCMCIA specific driver can perform an access to the pccard memory.
dev_id is returned by PcmciaBusOps.open().
The mem_param structure defines the memory region to map. This structure is stored in a property of the device node. This property should be retrieved by the driver using the device tree API (dtreePropFind(), dtreePropLength(), dtreePropValue()), prior to calling PcmciaBusOps.mem_map().
The name of the property used to describe device memory regions is PCMCIA_PROP_MEM_RGN. Its value contains a PcmciaPropMemRgn structure shown below:
typedef struct PcmciaPropMemRgn { PcmciaMemSpace space; /* required mem space: attr/common */ PcmciaAddr base; /* Base address in memory space */ PcmciaSize size; /* size in bytes */ PcmciaSpeed speed; /* or int in ns */ PcmciaPath width; /* mem-map data path (8/16/ANY) */ } PcmciaPropMemRgn;
The space field specifies the memory space to be mapped. It can take one of the following values: PCMCIA_MEM_ATTR; PCMCIA_MEM_COMMON. Attribute memory and common memory can be mapped in this way.
The base field specifies the PCMCIA start address of the memory concerned.
The size field specifies the memory size in bytes.
The speed field specifies the access speed supported by the PCMCIA card. Its value must be one of the following:
PCMCIA_SPEED_ANY will work with any device where the best value is unknown.
The width field specifies whether accesses to the pccard memory region will be performed in 8 or 16 bits. Its value must be one of the following:
PCMCIA_DATA_ANY will cause the HBA to switch between 8 and 16 bit access configuration depending on the function called (xxx_8 or xxx_16 routines, see below).
The err_handler argument is a handler in the device driver, which is invoked by the PCMCIA bus driver, when a PCMCIA bus error occurs while accessing the mapped region. It is not always possible for a PCMCIA bus driver to detect this type of error, and in this case, the err_handler will never be called. err_cookie is passed back to the err_handler as the first parameter (see the PCMCIA Error Handling section). The main case where err_handler is called is to access a mapped region when the PCMCIA card has been removed.
On success, K_OK is returned and appropriate memory services are returned in the memOps parameter. An identifier for the mapped region is also returned in memId. This identifier must be used as first parameter to further calls to the PCMCIA memory operations services, as defined in PcmciaMemOps.
On failure, PcmciaBusOps.mem_map() returns one of the following error codes:
The system is out of memory.
The PCMCIA card has been removed.
Performing Memory Access to a Mapped Memory Region
Once the region is successfully mapped, the driver can use the services defined by the PcmciaMemOps structure.
PCMCIA bus provides four service routine sets to access a mapped memory region:
PcmciaMemOps.load_8/16()
PcmciaMemOps.store_8/16()
PcmciaMemOps.copy_to_8/16()
PcmciaMemOps.copy_from_8/16()
There are two service routines in each set which deal with memory accesses of different widths. The _8 or _16 suffix indicates the data size of the transfer on the PCMCIA bus.
In all service routines provided by PcmciaMemOps(), the memId argument identifies the mapped region as returned by PcmciaBusOps.mem_map().
The offset argument specifies the register offset, in bytes, within the mapped region.
Load from memory
The PcmciaMemOps.load_xx() routine set returns a value loaded from the device memory:
uintxx_f (*load_xx) __((PcmciaMemId mem_id, PcmciaSize offset));
The size of the returned value and of the data transfer on the PCMCIA bus is specified by the xx suffix.
Store to memory
The PcmciaIoOps.store_xx() routine set stores a given value into a device register:
void (*store_xx) __((PcmciaMemId mem_id, PcmciaSize offset, uintxx_f val));
The size of the given value and of the data transfer on the PCMCIA bus is specified by the xx suffix.
Continuous write to memory
The PcmciaMemOps.copy_to_xx() routine set dumps values from the memory buffer to the specified pccard memory location:
void (*copy_to_xx) __((PcmciaMemId mem_id, PcmciaSize offset, uintxx_f* addr, PcmciaSize count));
The size of each value stored and of each data transfer on the PCMCIA bus is specified by the xx suffix.
The count argument specifies the size of the memory region to dump on the pccard.
The addr argument specifies the address of the memory buffer. The size of this buffer must be at least the value of count multiplied by the size of each data transfer.
Continuous read from memory
The PcmciaMemOps.copy_from_xx() routine set dumps values from the specified pccard memory region to a buffer:
void (*copy_from_xx) __((PcmciaMemId mem_id, PcmciaSize offset, uintxx_f* addr, PcmciaSize count));
The size of each value loaded and of each data transfer on the PCMCIA bus is specified by the xx suffix.
The count argument specifies the size of the memory region to dump from the pccard.
The addr argument specifies the address of the memory buffer. The size of this buffer must be at least the value of count multiplied by the size of each data transfer.
Unmapping Device Memory Regions
When a driver no longer needs access to the device memory, or before closing the connection to the PCMCIA bus, it should unmap the memory region used for these devices.
The PcmciaBusOps.mem_unmap() is used to unmap a memory region previously mapped with PcmciaBusOps.mem_map(), and to release any resources allocated for this mapping:
void (*mem_unmap) __((PcmciaMemId memId));
The memId argument identifies the region to unmap.
When the PcmciaBusOps.mem_unmap() function is called, the driver is no longer allowed to use any services defined for the unmapped memory region.
When mapping PCMCIA bus space, a PCMCIA driver gives an error handler that is invoked by the PCMCIA bus driver when a bus error occurs.
Error handlers are used for the following services:
PcmciaBusOps.io_map()
PcmciaBusOps.mem_map()
When an access is aborted because of a bus error, the PCMCIA bus driver invokes the error handler that is connected for the PCMCIA address and space concerned.
Depending on hardware, it is not always possible for the PCMCIA bus driver to detect these types of errors. In this case, the PCMCIA bus driver will not invoke device driver error handlers.
A PcmciaErrHandler is defined as follows:
typedef void (*PcmciaErrHandler) (void* errCookie, PcmciaBusError* err);
The errCookie is an opaque type given by the caller and passed back when the handler is called.
The err argument points to the PcmciaBusError structure describing the error as follows:
typedef struct PcmciaBusError { PcmciaErrorCode code; /* Error type */ PcmciaSize offset; /* faulted address within region */ } PcmciaBusError;
The code field indicates the type of error that occurred. The offset field indicates the offset within the associated region at which the error occurred. The error code can be one of the following:
Unknown error, that is, the PCMCIA bus driver is unable to determine the reason for the error.
Invalid (that is, unsupported) access granularity has been used.
The pccard has been removed and thus the access cannot be performed.
As a bus error may be reported in the context of an exception or an interrupt, the implementation of the error handler must be restricted to the interrupt level API.
The PCMCIA Specific Properties table lists the PCMCIA specific node properties. The Alias column specifies the alias name which should be used by a PCMCIA bus or device driver to reference the property name. The Name column specifies the property name ASCII string. The Value column specifies the type of property value. The Bus column specifies properties specific to the PCMCIA bus node. The Dev column specifies properties specific to the PCMCIA device node. The Bus and Dev fields can take the following values:
Alias | Name | Value | Bus | Dev |
PCMCIA_PROP_INTR | "intr" | PcmciaPropIntr | - | o |
PCMCIA_PROP_IO_REGS | "io-regs" | PcmciaPropIoRegs | - | o |
PCMCIA_PROP_MEM_RGN | "mem-rgn" | PcmciaPropMemRgn | - | o |
PCMCIA_PROP_COR | "pcmcia-prop-cor" | PcmciaPropCOR[] | - | o |
PCMCIA_PROP_POWER | "pcmcia-prop-power" | PcmciaPropPower[] | - | o |
PCMCIA_PROP_INTR_PARAM | "pcmcia-prop-intr-param" | PcmciaPropIntrParam[] | - | o |
PCMCIA_PROP_IO_MAP | "pcmcia-prop-io" | PcmciaPropIoMap[] | - | o |
PCMCIA_PROP_MEM_MAP | "pcmcia-prop-mem" | PcmciaPropMemMap[] | - | o |
PCMCIA_PROP_SOCK_INTR | "prop-sock-intr" | PcmciaPropIntr | m | - |
Flags' mandatory properties | o |
Flags' optional properties | - |
Flags' properties which are not applied to a given node type |
For a full description of these properties, see the PCMCIA Probing Mechanisms section.
KnError (*resource_alloc) __((PcmciaDevId dev_id, DevProperty prop));
dev_id is returned by PcmciaBusOps.open().
prop specifies the bus resource being allocated.
The PcmciaBusOps.resource_alloc() service routine allocates a given bus resource and, if the allocation request is satisfied, updates the device node properties in order to add the newly allocated bus resource.
On success, PcmciaBusOps.resource_alloc() returns K_OK.
On failure, one of the following error codes is returned:
Dynamic resource allocation is not supported by the bus driver.
The property name is unknown and invalid.
The bus resource is not available.
The system is out of memory.
When a dynamically allocated bus resource is no longer used, a PCMCIA device driver may release it by calling the PcmciaBusOps.resource_free() service routine:
void (*resource_free) __((PcmciaDevId dev_id, DevProperty prop));
dev_id is returned by PcmciaBusOps.open().
prop specifies the bus resource being released.
The PcmciaBusOps.resource_free() service routine releases a given bus resource and updates the device node properties in order to remove the bus resource being released.
The following bus resource properties may be dynamically allocated and released:
PCMCIA_PROP_INTR
PCMCIA_PROP_IO_REGS
PCMCIA_PROP_MEM_RGN
The PcmciaBusOps.resource_alloc() and PcmciaBusOps.resource_free() routines should not be used by a simple device driver. This type of driver should assume that all resources needed are already allocated and specified as properties in the device node by the bus driver. The driver should only find this type of property and call an appropriate service routine passing a pointer to the property value.
However, it is not always possible to determine all needed resources prior to the device initialization. A typical case is a bus-to-bus bridge driver which can discover devices residing on the secondary bus only when the bus bridge hardware has already been initialized. In this case, when drv_init() is called, the bus-to-bus bridge node would contain only resources needed for the bus-to-bus bridge device itself (for example, internal bus-to-bus bridge registers). Once devices residing on the secondary bus are discovered, the bus-to-bus bridge driver would request additional primary bus resources in order to satisfy the resource requirements for these devices. Another example is a bus which supports hot-pluggable devices. On this type of bus, the primary bus resources allocated by the hot-pluggable bus driver depend on devices currently plugged into the secondary bus. The resource requirements usually change when a hot-plug insertion or removal occurs.
Dynamic resource allocation might also be used by a device driver which implements lazy resource allocation. In this type of driver, the bus resource allocation might be performed at open time using PCMCIABusOps.resource_alloc(). The dynamically allocated resources might then be released at close time using PcmciaBusOps.resource_free().
The following table specifies the contexts in which a caller is allowed to invoke each service:
Services | Base level | DKI thread | Interrupt | Blocking |
PcmciaBusOps.open | - | + | - | + |
PcmciaBusOps.close | - | + | - | + |
PcmciaBusOps.intr_attach | - | + | - | + |
PcmciaBusOps.intr_detach | - | + | - | + |
PcmciaBusOps.io_map | - | + | - | + |
PcmciaBusOps.io_unmap | - | + | - | + |
PcmciaBusOps.mem_map | - | + | - | + |
PcmciaBusOps.mem_unmap | - | + | - | + |
PcmciaBusOps.resource_alloc | - | + | - | + |
PcmciaBusOps.resource_free | - | + | - | + |
PcmciaIoOps.load_xx | + | + | + | - |
PcmciaIoOps.store_xx | + | + | + | - |
PcmciaIoOps.read_xx | + | + | + | - |
PcmciaIoOps.write_xx | + | + | + | - |
See attributes(5) for descriptions of the following attributes:
ATTRIBUTE TYPE | ATTRIBUTE VALUE |
---|---|
Interface Stability | Evolving |
dtreeNodeRoot(9DKI), svDriverRegister(9DKI), svMemAlloc(9DKI), svPhysAlloc(9DKI), svPhysMap(9DKI), svDkiThreadCall(9DKI), svTimeoutSet(9DKI), usecBusyWait(9DKI), DISABLE_PREEMPT(9DKI)
NAME | SYNOPSIS | API RESTRICTIONS | FEATURES | DESCRIPTION | EXTENDED DESCRIPTION | PROPERTIES | ALLOWED CALLING CONTEXTS | ATTRIBUTES | SEE ALSO