diff --git a/en_US.ISO8859-1/books/arch-handbook/isa/chapter.sgml b/en_US.ISO8859-1/books/arch-handbook/isa/chapter.sgml new file mode 100644 index 0000000000..c68a55d85c --- /dev/null +++ b/en_US.ISO8859-1/books/arch-handbook/isa/chapter.sgml @@ -0,0 +1,2479 @@ + + + + ISA device drivers + + + + This chapter was written by &a.babkin; Modifications for the + handbook made by &a.murray;, &a.wylie;, and &a.logo;. + + + + + Synopsis + + This chapter introduces the issues relevant to writing a + driver for an ISA device. The pseudo-code presented here is + rather detailed and reminiscent of the real code but is still + only pseudo-code. It avoids the details irrelevant to the + subject of the discussion. The real-life examples can be found + in the source code of real drivers. In particular the drivers + "ep" and "aha" are good sources of information. + + + + Basic information + + A typical ISA driver would need the following include + files: + +#include <sys/module.h> +#include <sys/bus.h> +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <isa/isavar.h> +#include <isa/pnpvar.h> + + They describe the things specific to the ISA and generic + bus subsystem. + + The bus subsystem is implemented in an object-oriented + fashion, its main structures are accessed by associated method + functions. + + The list of bus methods implemented by an ISA driver is like + one for any other bus. For a hypothetical driver named "xxx" + they would be: + + + + static void xxx_isa_identify (driver_t *, + device_t); Normally used for bus drivers, not + device drivers. But for ISA devices this method may have + special use: if the device provides some device-specific + (non-PnP) way to auto-detect devices this routine may + implement it. + + + + static int xxx_isa_probe (device_t + dev); Probe for a device at a known (or PnP) + location. This routine can also accommodate device-specific + auto-detection of parameters for partially configured + devices. + + + + static int xxx_isa_attach (device_t + dev); Attach and initialize device. + + + + static int xxx_isa_detach (device_t + dev); Detach device before unloading the driver + module. + + + + static int xxx_isa_shutdown (device_t + dev); Execute shutdown of the device before + system shutdown. + + + + static int xxx_isa_suspend (device_t + dev); Suspend the device before the system goes + to the power-save state. May also abort transition to the + power-save state. + + + + static int xxx_isa_resume (device_t + dev); Resume the device activity after return + from power-save state. + + + + + xxx_isa_probe() and + xxx_isa_attach() are mandatory, the rest of + the routines are optional, depending on the device's + needs. + + The driver is linked to the system with the following set of + descriptions. + + /* table of supported bus methods */ + static device_method_t xxx_isa_methods[] = { + /* list all the bus method functions supported by the driver */ + /* omit the unsupported methods */ + DEVMETHOD(device_identify, xxx_isa_identify), + DEVMETHOD(device_probe, xxx_isa_probe), + DEVMETHOD(device_attach, xxx_isa_attach), + DEVMETHOD(device_detach, xxx_isa_detach), + DEVMETHOD(device_shutdown, xxx_isa_shutdown), + DEVMETHOD(device_suspend, xxx_isa_suspend), + DEVMETHOD(device_resume, xxx_isa_resume), + + { 0, 0 } + }; + + static driver_t xxx_isa_driver = { + "xxx", + xxx_isa_methods, + sizeof(struct xxx_softc), + }; + + + static devclass_t xxx_devclass; + + DRIVER_MODULE(xxx, isa, xxx_isa_driver, xxx_devclass, + load_function, load_argument); + + Here struct xxx_softc is a + device-specific structure that contains private driver data + and descriptors for the driver's resources. The bus code + automatically allocates one softc descriptor per device as + needed. + + If the driver is implemented as a loadable module then + load_function() is called to do + driver-specific initialization or clean-up when the driver is + loaded or unloaded and load_argument is passed as one of its + arguments. If the driver does not support dynamic loading (in + other words it must always be linked into kernel) then these + values should be set to 0 and the last definition would look + like: + + DRIVER_MODULE(xxx, isa, xxx_isa_driver, + xxx_devclass, 0, 0); + + If the driver is for a device which supports PnP then a + table of supported PnP IDs must be defined. The table + consists of a list of PnP IDs supported by this driver and + human-readable descriptions of the hardware types and models + having these IDs. It looks like: + + static struct isa_pnp_id xxx_pnp_ids[] = { + /* a line for each supported PnP ID */ + { 0x12345678, "Our device model 1234A" }, + { 0x12345679, "Our device model 1234B" }, + { 0, NULL }, /* end of table */ + }; + + If the driver does not support PnP devices it still needs + an empty PnP ID table, like: + + static struct isa_pnp_id xxx_pnp_ids[] = { + { 0, NULL }, /* end of table */ + }; + + + + + Device_t pointer + + Device_t is the pointer type for + the device structure. Here we consider only the methods + interesting from the device driver writer's standpoint. The + methods to manipulate values in the device structure + are: + + + + device_t + device_get_parent(dev) Get the parent bus of a + device. + + driver_t + device_get_driver(dev) Get pointer to its driver + structure. + + char + *device_get_name(dev) Get the driver name, such + as "xxx" for our example. + + int device_get_unit(dev) + Get the unit number (units are numbered from 0 for the + devices associated with each driver). + + char + *device_get_nameunit(dev) Get the device name + including the unit number, such as "xxx0" , "xxx1" and so + on. + + char + *device_get_desc(dev) Get the device + description. Normally it describes the exact model of device + in human-readable form. + + device_set_desc(dev, + desc) Set the description. This makes the device + description point to the string desc which may not be + deallocated or changed after that. + + device_set_desc_copy(dev, + desc) Set the description. The description is + copied into an internal dynamically allocated buffer, so the + string desc may be changed afterwards without adverse + effects. + + void + *device_get_softc(dev) Get pointer to the device + descriptor (struct xxx_softc) + associated with this device. + + u_int32_t + device_get_flags(dev) Get the flags specified for + the device in the configuration file. + + + + A convenience function device_printf(dev, fmt, + ...) may be used to print the messages from the + device driver. It automatically prepends the unitname and + colon to the message. + + The device_t methods are implemented in the file + kern/bus_subr.c. + + + + + Config file and the order of identifying and probing + during auto-configuration + + The ISA devices are described in the kernel config file + like: + + device xxx0 at isa? port 0x300 irq 10 drq 5 + iomem 0xd0000 flags 0x1 sensitive + + The values of port, IRQ and so on are converted to the + resource values associated with the device. They are optional, + depending on the device needs and abilities for + auto-configuration. For example, some devices don't need DRQ + at all and some allow the driver to read the IRQ setting from + the device configuration ports. If a machine has multiple ISA + buses the exact bus may be specified in the configuration + line, like "isa0" or "isa1", otherwise the device would be + searched for on all the ISA buses. + + "sensitive" is a resource requesting that this device must + be probed before all non-sensitive devices. It is supported + but does not seem to be used in any current driver. + + For legacy ISA devices in many cases the drivers are still + able to detect the configuration parameters. But each device + to be configured in the system must have a config line. If two + devices of some type are installed in the system but there is + only one configuration line for the corresponding driver, ie: + device xxx0 at isa? then only + one device will be configured. + + But for the devices supporting automatic identification by + the means of Plug-n-Play or some proprietary protocol one + configuration line is enough to configure all the devices in + the system, like the one above or just simply: + + device xxx at isa? + + If a driver supports both auto-identified and legacy + devices and both kinds are installed at once in one machine + then it's enough to describe in the config file the legacy + devices only. The auto-identified devices will be added + automatically. + + When an ISA bus is auto-configured the events happen as + follows: + + All the drivers' identify routines (including the PnP + identify routine which identifies all the PnP devices) are + called in random order. As they identify the devices they add + them to the list on the ISA bus. Normally the drivers' + identify routines associate their drivers with the new + devices. The PnP identify routine does not know about the + other drivers yet so it does not associate any with the new + devices it adds. + + The PnP devices are put to sleep using the PnP protocol to + prevent them from being probed as legacy devices. + + The probe routines of non-PnP devices marked as + "sensitive" are called. If probe for a device went + successfully, the attach routine is called for it. + + The probe and attach routines of all non-PNP devices are + called likewise. + + The PnP devices are brought back from the sleep state and + assigned the resources they request: I/O and memory address + ranges, IRQs and DRQs, all of them not conflicting with the + attached legacy devices. + + Then for each PnP device the probe routines of all the + present ISA drivers are called. The first one that claims the + device gets attached. It is possible that multiple drivers + would claim the device with different priority, the + highest-priority driver wins. The probe routines must call + ISA_PNP_PROBE() to compare the actual PnP + ID with the list of the IDs supported by the driver and if the + ID is not in the table return failure. That means that + absolutely every driver, even the ones not supporting any PnP + devices must call ISA_PNP_PROBE(), at + least with an empty PnP ID table to return failure on unknown + PnP devices. + + The probe routine returns a positive value (the error + code) on error, zero or negative value on success. + + The negative return values are used when a PnP device + supports multiple interfaces. For example, an older + compatibility interface and a newer advanced interface which + are supported by different drivers. Then both drivers would + detect the device. The driver which returns a higher value in + the probe routine takes precedence (in other words, the driver + returning 0 has highest precedence, returning -1 is next, + returning -2 is after it and so on). In result the devices + which support only the old interface will be handled by the + old driver (which should return -1 from the probe routine) + while the devices supporting the new interface as well will be + handled by the new driver (which should return 0 from the + probe routine). If multiple drivers return the same value then + the one called first wins. So if a driver returns value 0 it + may be sure that it won the priority arbitration. + + The device-specific identify routines can also assign not + a driver but a class of drivers to the device. Then all the + drivers in the class are probed for this device, like the case + with PnP. This feature is not implemented in any existing + driver and is not considered further in this document. + + Because the PnP devices are disabled when probing the + legacy devices they will not be attached twice (once as legacy + and once as PnP). But in case of device-dependent identify + routines it's the responsibility of the driver to make sure + that the same device won't be attached by the driver twice: + once as legacy user-configured and once as + auto-identified. + + Another practical consequence for the auto-identified + devices (both PnP and device-specific) is that the flags can + not be passed to them from the kernel configuration file. So + they must either not use the flags at all or use the flags + from the device unit 0 for all the auto-identified devices or + use the sysctl interface instead of flags. + + Other unusual configurations may be accommodated by + accessing the configuration resources directly with functions + of families resource_query_*() and + resource_*_value(). Their implementations + are located in kern/subr_bus.h. The old IDE disk driver + i386/isa/wd.c contains examples of such use. But the standard + means of configuration must always be preferred. Leave parsing + the configuration resources to the bus configuration + code. + + + + + Resources + + The information that a user enters into the kernel + configuration file is processed and passed to the kernel as + configuration resources. This information is parsed by the bus + configuration code and transformed into a value of structure + device_t and the bus resources associated with it. The drivers + may access the configuration resources directly using + functions resource_* for more complex cases of + configuration. But generally it's not needed nor recommended, + so this issue is not discussed further. + + The bus resources are associated with each device. They + are identified by type and number within the type. For the ISA + bus the following types are defined: + + + + SYS_RES_IRQ - interrupt + number + + + + SYS_RES_DRQ - ISA DMA channel + number + + + + SYS_RES_MEMORY - range of + device memory mapped into the system memory space + + + + + SYS_RES_IOPORT - range of + device I/O registers + + + + The enumeration within types starts from 0, so if a device + has two memory regions if would have resources of type + SYS_RES_MEMORY numbered 0 and 1. The resource type has + nothing to do with the C language type, all the resource + values have the C language type "unsigned long" and must be + cast as necessary. The resource numbers don't have to be + contiguous although for ISA they normally would be. The + permitted resource numbers for ISA devices are: + + IRQ: 0-1 + DRQ: 0-1 + MEMORY: 0-3 + IOPORT: 0-7 + + All the resources are represented as ranges, with a start + value and count. For IRQ and DRQ resources the count would be + normally equal to 1. The values for memory refer to the + physical addresses. + + Three types of activities can be performed on + resources: + + + set/get + allocate/release + activate/deactivate + + + Setting sets the range used by the resource. Allocation + reserves the requested range that no other driver would be + able to reserve it (and checking that no other driver reserved + this range already). Activation makes the resource accessible + to the driver doing whatever is necessary for that (for + example, for memory it would be mapping into the kernel + virtual address space). + + The functions to manipulate resources are: + + + + int bus_set_resource(device_t dev, int type, + int rid, u_long start, u_long count) + + Set a range for a resource. Returns 0 if successful, + error code otherwise. Normally the only reason this + function would return an error is value of type, rid, + start or count out of permitted range. + + + + dev - driver's device + + + type - type of resource, SYS_RES_* + + + rid - resource number (ID) within type + + + start, count - resource range + + + + + + int bus_get_resource(device_t dev, int type, + int rid, u_long *startp, u_long *countp) + + Get the range of resource. Returns 0 if successful, + error code if the resource is not defined yet. + + + + u_long bus_get_resource_start(device_t dev, + int type, int rid) u_long bus_get_resource_count (device_t + dev, int type, int rid) + + Convenience functions to get only the start or + count. Return 0 in case of error, so if the resource start + has 0 among the legitimate values it would be impossible + to tell if the value is 0 or an error occurred. Luckily, + no ISA resources for add-on drivers may have a start value + equal 0. + + + + void bus_delete_resource(device_t dev, int + type, int rid) + Delete a resource, make it undefined. + + + + struct resource * + bus_alloc_resource(device_t dev, int type, int *rid, + u_long start, u_long end, u_long count, u_int + flags) + + Allocate a resource as a range of count values not + allocated by anyone else, somewhere between start and + end. Alas, alignment is not supported. If the resource + was not set yet it's automatically created. The special + values of start 0 and end ~0 (all ones) means that the + fixed values previously set by + bus_set_resource() must be used + instead: start and count as themselves and + end=(start+count), in this case if the resource was not + defined before then an error is returned. Although rid is + passed by reference it's not set anywhere by the resource + allocation code of the ISA bus. (The other buses may use a + different approach and modify it). + + + + Flags are a bitmap, the flags interesting for the caller + are: + + + + RF_ACTIVE - causes the resource + to be automatically activated after allocation. + + + + RF_SHAREABLE - resource may be + shared at the same time by multiple drivers. + + + + RF_TIMESHARE - resource may be + time-shared by multiple drivers, i.e. allocated at the + same time by many but activated only by one at any given + moment of time. + + + + Returns 0 on error. The allocated values may be + obtained from the returned handle using methods + rhand_*(). + + + int bus_release_resource(device_t dev, int + type, int rid, struct resource *r) + + + + Release the resource, r is the handle returned by + bus_alloc_resource(). Returns 0 on + success, error code otherwise. + + + + int bus_activate_resource(device_t dev, int + type, int rid, struct resource *r) + int bus_deactivate_resource(device_t dev, int + type, int rid, struct resource *r) + + + + Activate or deactivate resource. Return 0 on success, + error code otherwise. If the resource is time-shared and + currently activated by another driver then EBUSY is + returned. + + + + int bus_setup_intr(device_t dev, struct + resource *r, int flags, driver_intr_t *handler, void *arg, + void **cookiep) int + bus_teardown_intr(device_t dev, struct resource *r, void + *cookie) + + + + Associate or de-associate the interrupt handler with a + device. Return 0 on success, error code otherwise. + + + + r - the activated resource handler describing the + IRQ + flags - the interrupt priority level, one of: + + + + INTR_TYPE_TTY - terminals and + other likewise character-type devices. To mask them + use spltty(). + + + (INTR_TYPE_TTY | + INTR_TYPE_FAST) - terminal type devices + with small input buffer, critical to the data loss on + input (such as the old-fashioned serial ports). To + mask them use spltty(). + + + INTR_TYPE_BIO - block-type + devices, except those on the CAM controllers. To mask + them use splbio(). + + + INTR_TYPE_CAM - CAM (Common + Access Method) bus controllers. To mask them use + splcam(). + + + INTR_TYPE_NET - network + interface controllers. To mask them use + splimp(). + + + INTR_TYPE_MISC - + miscellaneous devices. There is no other way to mask + them than by splhigh() which + masks all interrupts. + + + + + + When an interrupt handler executes all the other + interrupts matching its priority level will be masked. The + only exception is the MISC level for which no other interrupts + are masked and which is not masked by any other + interrupt. + + + + handler - pointer to the handler + function, the type driver_intr_t is defined as "void + driver_intr_t(void *)" + + + arg - the argument passed to the + handler to identify this particular device. It is cast + from void* to any real type by the handler. The old + convention for the ISA interrupt handlers was to use the + unit number as argument, the new (recommended) convention + is using a pointer to the device softc structure. + + + cookie[p] - the value received + from setup() is used to identify the + handler when passed to + teardown() + + + + A number of methods is defined to operate on the resource + handlers (struct resource *). Those of interest to the device + driver writers are: + + + + u_long rman_get_start(r) u_long + rman_get_end(r) Get the start and end of + allocated resource range. + + + void *rman_get_virtual(r) Get + the virtual address of activated memory resource. + + + + + + + Bus memory mapping + + In many cases data is exchanged between the driver and the + device through the memory. Two variants are possible: + + (a) memory is located on the device card + (b) memory is the main memory of computer + + In the case (a) the driver always copies the data back and + forth between the on-card memory and the main memory as + necessary. To map the on-card memory into the kernel virtual + address space the physical address and length of the on-card + memory must be defined as a SYS_RES_MEMORY resource. That + resource can then be allocated and activated, and its virtual + address obtained using + rman_get_virtual(). The older drivers + used the function pmap_mapdev() for this + purpose, which should not be used directly any more. Now it's + one of the internal steps of resource activation. + + Most of the ISA cards will have their memory configured + for physical location somewhere in range 640KB-1MB. Some of + the ISA cards require larger memory ranges which should be + placed somewhere under 16MB (because of the 24-bit address + limitation on the ISA bus). In that case if the machine has + more memory than the start address of the device memory (in + other words, they overlap) a memory hole must be configured at + the address range used by devices. Many BIOSes allow to + configure a memory hole of 1MB starting at 14MB or + 15MB. FreeBSD can handle the memory holes properly if the BIOS + reports them properly (old BIOSes may have this feature + broken). + + In the case (b) just the address of the data is sent to + the device, and the device uses DMA to actually access the + data in the main memory. Two limitations are present: First, + ISA cards can only access memory below 16MB. Second, the + contiguous pages in virtual address space may not be + contiguous in physical address space, so the device may have + to do scatter/gather operations. The bus subsystem provides + ready solutions for some of these problems, the rest has to be + done by the drivers themselves. + + Two structures are used for DMA memory allocation, + bus_dma_tag_t and bus_dmamap_t. Tag describes the properties + required for the DMA memory. Map represents a memory block + allocated according to these properties. Multiple maps may be + associated with the same tag. + + Tags are organized into a tree-like hierarchy with + inheritance of the properties. A child tag inherits all the + requirements of its parent tag or may make them more strict + but never more loose. + + Normally one top-level tag (with no parent) is created for + each device unit. If multiple memory areas with different + requirements are needed for each device then a tag for each of + them may be created as a child of the parent tag. + + The tags can be used to create a map in two ways. + + First, a chunk of contiguous memory conformant with the + tag requirements may be allocated (and later may be + freed). This is normally used to allocate relatively + long-living areas of memory for communication with the + device. Loading of such memory into a map is trivial: it's + always considered as one chunk in the appropriate physical + memory range. + + Second, an arbitrary area of virtual memory may be loaded + into a map. Each page of this memory will be checked for + conformance to the map requirement. If it conforms then it's + left at it's original location. If it is not then a fresh + conformant "bounce page" is allocated and used as intermediate + storage. When writing the data from the non-conformant + original pages they will be copied to their bounce pages first + and then transferred from the bounce pages to the device. When + reading the data would go from the device to the bounce pages + and then copied to their non-conformant original pages. The + process of copying between the original and bounce pages is + called synchronization. This is normally used on per-transfer + basis: buffer for each transfer would be loaded, transfer done + and buffer unloaded. + + The functions working on the DMA memory are: + + + + int bus_dma_tag_create(bus_dma_tag_t parent, + bus_size_t alignment, bus_size_t boundary, bus_addr_t + lowaddr, bus_addr_t highaddr, bus_dma_filter_t *filter, void + *filterarg, bus_size_t maxsize, int nsegments, bus_size_t + maxsegsz, int flags, bus_dma_tag_t *dmat) + + Create a new tag. Returns 0 on success, the error code + otherwise. + + + + parent - parent tag, or NULL to + create a top-level tag alignment - + required physical alignment of the memory area to be + allocated for this tag. Use value 1 for "no specific + alignment". Applies only to the future + bus_dmamem_alloc() but not + bus_dmamap_create() calls. + boundary - physical address + boundary that must not be crossed when allocating the + memory. Use value 0 for "no boundary". Applies only to + the future bus_dmamem_alloc() but + not bus_dmamap_create() calls. + Must be power of 2. If the memory is planned to be used + in non-cascaded DMA mode (i.e. the DMA addresses will be + supplied not by the device itself but by the ISA DMA + controller) then the boundary must be no larger than + 64KB (64*1024) due to the limitations of the DMA + hardware. + + + + lowaddr, highaddr - the names + are slighlty misleading; these values are used to limit + the permitted range of physical addresses used to + allocate the memory. The exact meaning varies depending + on the planned future use: + + + + For bus_dmamem_alloc() all + the addresses from 0 to lowaddr-1 are considered + permitted, the higher ones are forbidden. + + + + For bus_dmamap_create() all + the addresses outside the inclusive range [lowaddr; + highaddr] are considered accessible. The addresses + of pages inside the range are passed to the filter + function which decides if they are accessible. If no + filter function is supplied then all the range is + considered unaccessible. + + + + For the ISA devices the normal values (with no + filter function) are: + lowaddr = BUS_SPACE_MAXADDR_24BIT + highaddr = BUS_SPACE_MAXADDR + + + + + + + filter, filterarg - the filter + function and its argument. If NULL is passed for filter + then the whole range [lowaddr, highaddr] is considered + unaccessible when doing + bus_dmamap_create(). Otherwise the + physical address of each attempted page in range + [lowaddr; highaddr] is passed to the filter function + which decides if it is accessible. The prototype of the + filter function is: int filterfunc(void *arg, + bus_addr_t paddr) It must return 0 if the + page is accessible, non-zero otherwise. + + + + maxsize - the maximal size of + memory (in bytes) that may be allocated through this + tag. In case it's difficult to estimate or could be + arbitrarily big, the value for ISA devices would be + BUS_SPACE_MAXSIZE_24BIT. + + + + nsegments - maximal number of + scatter-gather segments supported by the device. If + unrestricted then the value BUS_SPACE_UNRESTRICTED + should be used. This value is recommended for the parent + tags, the actual restrictions would then be specified + for the descendant tags. Tags with nsegments equal to + BUS_SPACE_UNRESTRICTED may not be used to actually load + maps, they may be used only as parent tags. The + practical limit for nsegments seems to be about 250-300, + higher values will cause kernel stack overflow. But + anyway the hardware normally can't support that many + scatter-gather buffers. + + + + maxsegsz - maximal size of a + scatter-gather segment supported by the device. The + maximal value for ISA device would be + BUS_SPACE_MAXSIZE_24BIT. + + + + flags - a bitmap of flags. The + only interesting flags are: + + + + BUS_DMA_ALLOCNOW - requests + to allocate all the potentially needed bounce pages + when creating the tag + + + + BUS_DMA_ISA - mysterious + flag used only on Alpha machines. It is not defined + for the i386 machines. Probably it should be used + by all the ISA drivers for Alpha machines but it + looks like there are no such drivers yet. + + + + + + dmat - pointer to the storage + for the new tag to be returned + + + + + + + + int bus_dma_tag_destroy(bus_dma_tag_t + dmat) + + Destroy a tag. Returns 0 on success, the error code + otherwise. + + dmat - the tag to be destroyed + + + + + int bus_dmamem_alloc(bus_dma_tag_t dmat, + void** vaddr, int flags, bus_dmamap_t + *mapp) + + Allocate an area of contiguous memory described by the + tag. The size of memory to be allocated is tag's maxsize. + Returns 0 on success, the error code otherwise. The result + still has to be loaded by + bus_dmamap_load() before used to get + the physical address of the memory. + + + + + + + dmat - the tag + + + + + vaddr - pointer to the storage + for the kernel virtual address of the allocated area + to be returned. + + + + + flags - a bitmap of flags. The only interesting flag is: + + + + + BUS_DMA_NOWAIT - if the + memory is not immediately available return the + error. If this flag is not set then the routine + is allowed to sleep waiting until the memory + will become available. + + + + + + + mapp - pointer to the storage + for the new map to be returned + + + + + + + + void bus_dmamem_free(bus_dma_tag_t dmat, void + *vaddr, bus_dmamap_t map) + + + Free the memory allocated by + bus_dmamem_alloc(). As of now + freeing of the memory allocated with ISA restrictions is + not implemented. Because of this the recommended model + of use is to keep and re-use the allocated areas for as + long as possible. Do not lightly free some area and then + shortly allocate it again. That does not mean that + bus_dmamem_free() should not be + used at all: hopefully it will be properly implemented + soon. + + + + + dmat - the tag + + + + + vaddr - the kernel virtual + address of the memory + + + + + map - the map of the memory (as + returned from + bus_dmamem_alloc()) + + + + + + + + int bus_dmamap_create(bus_dma_tag_t dmat, int + flags, bus_dmamap_t *mapp) + + + Create a map for the tag, to be used in + bus_dmamap_load() later. Returns 0 + on success, the error code otherwise. + + + + + dmat - the tag + + + + + flags - theoretically, a bit map + of flags. But no flags are defined yet, so as of now + it will be always 0. + + + + + mapp - pointer to the storage + for the new map to be returned + + + + + + + + int bus_dmamap_destroy(bus_dma_tag_t dmat, + bus_dmamap_t map) + + + Destroy a map. Returns 0 on success, the error code otherwise. + + + + + + dmat - the tag to which the map is associated + + + + + map - the map to be destroyed + + + + + + + + int bus_dmamap_load(bus_dma_tag_t dmat, + bus_dmamap_t map, void *buf, bus_size_t buflen, + bus_dmamap_callback_t *callback, void *callback_arg, int + flags) + + + Load a buffer into the map (the map must be previously + created by bus_dmamap_create() or + bus_dmamem_alloc()). All the pages + of the buffer are checked for conformance to the tag + requirements and for those not conformant the bounce + pages are allocated. An array of physical segment + descriptors is built and passed to the callback + routine. This callback routine is then expected to + handle it in some way. The number of bounce buffers in + the system is limited, so if the bounce buffers are + needed but not immediately available the request will be + queued and the callback will be called when the bounce + buffers will become available. Returns 0 if the callback + was executed immediately or EINPROGRESS if the request + was queued for future execution. In the latter case the + synchronization with queued callback routine is the + responsibility of the driver. + + + + + + dmat - the tag + + + + + map - the map + + + + + buf - kernel virtual address of + the buffer + + + + + buflen - length of the buffer + + + + + callback, + callback_arg - the callback function and + its argument + + + + + + The prototype of callback function is: + + + void callback(void *arg, bus_dma_segment_t + *seg, int nseg, int error) + + + + + + arg - the same as callback_arg + passed to bus_dmamap_load() + + + + + seg - array of the segment + descriptors + + + + + nseg - number of descriptors in + array + + + + + error - indication of the + segment number overflow: if it's set to EFBIG then + the buffer did not fit into the maximal number of + segments permitted by the tag. In this case only the + permitted number of descriptors will be in the + array. Handling of this situation is up to the + driver: depending on the desired semantics it can + either consider this an error or split the buffer in + two and handle the second part separately + + + + + + Each entry in the segments array contains the fields: + + + + + + + ds_addr - physical bus address + of the segment + + + + + ds_len - length of the segment + + + + + + + + + void bus_dmamap_unload(bus_dma_tag_t dmat, + bus_dmamap_t map) + + unload the map. + + + + + + dmat - tag + + + + + map - loaded map + + + + + + + + + void bus_dmamap_sync (bus_dma_tag_t dmat, + bus_dmamap_t map, bus_dmasync_op_t op) + + + Synchronise a loaded buffer with its bounce pages before + and after physical transfer to or from device. This is + the function that does all the necessary copying of data + between the original buffer and its mapped version. The + buffers must be synchronized both before and after doing + the transfer. + + + + + + dmat - tag + + + + + map - loaded map + + + + + op - type of synchronization + operation to perform: + + + + + + + + BUS_DMASYNC_PREREAD - before + reading from device into buffer + + + + + BUS_DMASYNC_POSTREAD - after + reading from device into buffer + + + + + BUS_DMASYNC_PREWRITE - before + writing the buffer to device + + + + + BUS_DMASYNC_POSTWRITE - after + writing the buffer to device + + + + + + + + + As of now PREREAD and POSTWRITE are null operations but that + may change in the future, so they must not be ignored in the + driver. Synchronization is not needed for the memory + obtained from bus_dmamem_alloc(). + + + Before calling the callback function from + bus_dmamap_load() the segment array is + stored in the stack. And it gets pre-allocated for the + maximal number of segments allowed by the tag. Because of + this the practical limit for the number of segments on i386 + architecture is about 250-300 (the kernel stack is 4KB minus + the size of the user structure, size of a segment array + entry is 8 bytes, and some space must be left). Because the + array is allocated based on the maximal number this value + must not be set higher than really needed. Fortunately, for + most of hardware the maximal supported number of segments is + much lower. But if the driver wants to handle buffers with a + very large number of scatter-gather segments it should do + that in portions: load part of the buffer, transfer it to + the device, load next part of the buffer, and so on. + + + Another practical consequence is that the number of segments + may limit the size of the buffer. If all the pages in the + buffer happen to be physically non-contiguous then the + maximal supported buffer size for that fragmented case would + be (nsegments * page_size). For example, if a maximal number + of 10 segments is supported then on i386 maximal guaranteed + supported buffer size would be 40K. If a higher size is + desired then special tricks should be used in the driver. + + + If the hardware does not support scatter-gather at all or + the driver wants to support some buffer size even if it's + heavily fragmented then the solution is to allocate a + contiguous buffer in the driver and use it as intermediate + storage if the original buffer does not fit. + + + Below are the typical call sequences when using a map depend + on the use of the map. The characters -> are used to show + the flow of time. + + + For a buffer which stays practically fixed during all the + time between attachment and detachment of a device: + + bus_dmamem_alloc -> bus_dmamap_load -> ...use buffer... -> + -> bus_dmamap_unload -> bus_dmamem_free + + + For a buffer that changes frequently and is passed from + outside the driver: + + + bus_dmamap_create -> + -> bus_dmamap_load -> bus_dmamap_sync(PRE...) -> do transfer -> + -> bus_dmamap_sync(POST...) -> bus_dmamap_unload -> + ... + -> bus_dmamap_load -> bus_dmamap_sync(PRE...) -> do transfer -> + -> bus_dmamap_sync(POST...) -> bus_dmamap_unload -> + -> bus_dmamap_destroy + + + + When loading a map created by + bus_dmamem_alloc() the passed address + and size of the buffer must be the same as used in + bus_dmamem_alloc(). In this case it is + guaranteed that the whole buffer will be mapped as one + segment (so the callback may be based on this assumption) + and the request will be executed immediately (EINPROGRESS + will never be returned). All the callback needs to do in + this case is to save the physical address. + + + A typical example would be: + + + static void + alloc_callback(void *arg, bus_dma_segment_t *seg, int nseg, int error) + { + *(bus_addr_t *)arg = seg[0].ds_addr; + } + + ... + int error; + struct somedata { + .... + }; + struct somedata *vsomedata; /* virtual address */ + bus_addr_t psomedata; /* physical bus-relative address */ + bus_dma_tag_t tag_somedata; + bus_dmamap_t map_somedata; + ... + + error=bus_dma_tag_create(parent_tag, alignment, + boundary, lowaddr, highaddr, /*filter*/ NULL, /*filterarg*/ NULL, + /*maxsize*/ sizeof(struct somedata), /*nsegments*/ 1, + /*maxsegsz*/ sizeof(struct somedata), /*flags*/ 0, + &tag_somedata); + if(error) + return error; + + error = bus_dmamem_alloc(tag_somedata, &vsomedata, /* flags*/ 0, + &map_somedata); + if(error) + return error; + + bus_dmamap_load(tag_somedata, map_somedata, (void *)vsomedata, + sizeof (struct somedata), alloc_callback, + (void *) &psomedata, /*flags*/0); + + + Looks a bit long and complicated but that's the way to do + it. The practical consequence is: if multiple memory areas + are allocated always together it would be a really good idea + to combine them all into one structure and allocate as one + (if the alignment and boundary limitations permit). + + + When loading an arbitrary buffer into the map created by + bus_dmamap_create() special measures + must be taken to synchronize with the callback in case it + would be delayed. The code would look like: + + + { + int s; + int error; + + s = splsoftvm(); + error = bus_dmamap_load( + dmat, + dmamap, + buffer_ptr, + buffer_len, + callback, + /*callback_arg*/ buffer_descriptor, + /*flags*/0); + if (error == EINPROGRESS) { + /* + * Do whatever is needed to ensure synchronization + * with callback. Callback is guaranteed not to be started + * until we do splx() or tsleep(). + */ + } + splx(s); + } + + + Two possible approaches for the processing of requests are: + + + 1. If requests are completed by marking them explicitly as + done (such as the CAM requests) then it would be simpler to + put all the further processing into the callback driver + which would mark the request when it's done. Then not much + extra synchronization is needed. For the flow control + reasons it may be a good idea to freeze the request queue + until this request gets completed. + + + 2. If requests are completed when the function returns (such + as classic read or write requests on character devices) then + a synchronization flag should be set in the buffer + descriptor and tsleep() called. Later + when the callback gets called it will do it's processing and + check this synchronization flag. If it's set then the + callback should issue a wakeup. In this approach the + callback function could either do all the needed processing + (just like the previous case) or simply save the segments + array in the buffer descriptor. Then after callback + completes the calling function could use this saved segments + array and do all the processing. + + + + + + + + DMA + + + The Direct Memory Access (DMA) is implemented in the ISA bus + through the DMA controller (actually, two of them but that's + an irrelevant detail). To make the early ISA devices simple + and cheap the logic of the bus control and address + generation was concentrated in the DMA controller. + Fortunately, FreeBSD provides a set of functions that mostly + hide the annoying details of the DMA controller from the + device drivers. + + + + The simplest case is for the fairly intelligent + devices. Like the bus master devices on PCI they can + generate the bus cycles and memory addresses all by + themselves. The only thing they really need from the DMA + controller is bus arbitration. So for this purpose they + pretend to be cascaded slave DMA controllers. And the only + thing needed from the system DMA controller is to enable the + cascaded mode on a DMA channel by calling the following + function when attaching the driver: + + + + void isa_dmacascade(int channel_number) + + + + All the further activity is done by programming the + device. When detaching the driver no DMA-related functions + need to be called. + + + + For the simpler devices things get more complicated. The + functions used are: + + + + + + + int isa_dma_acquire(int chanel_number) + + + Reserve a DMA channel. Returns 0 on success or EBUSY + if the channel was already reserved by this or a + different driver. Most of the ISA devices are not able + to share DMA channels anyway, so normally this + function is called when attaching a device. This + reservation was made redundant by the modern interface + of bus resources but still must be used in addition to + the latter. If not used then later, other DMA routines + will panic. + + + + + + int isa_dma_release(int chanel_number) + + + Release a previously reserved DMA channel. No + transfers must be in progress when the channel is + released (as well as the device must not try to + initiate transfer after the channel is released). + + + + + + void isa_dmainit(int chan, u_int + bouncebufsize) + + + Allocate a bounce buffer for use with the specified + channel. The requested size of the buffer can't exceed + 64KB. This bounce buffer will be automatically used + later if a transfer buffer happens to be not + physically contiguous or outside of the memory + accessible by the ISA bus or crossing the 64KB + boundary. If the transfers will be always done from + buffers which conform to these conditions (such as + those allocated by + bus_dmamem_alloc() with proper + limitations) then isa_dmainit() + does not have to be called. But it's quite convenient + to transfer arbitrary data using the DMA controller. + The bounce buffer will automatically care of the + scatter-gather issues. + + + + + + chan - channel number + + + + + bouncebufsize - size of the + bounce buffer in bytes + + + + + + + + + + void isa_dmastart(int flags, caddr_t addr, u_int + nbytes, int chan) + + + Prepare to start a DMA transfer. This function must be + called to set up the DMA controller before actually + starting transfer on the device. It checks that the + buffer is contiguous and falls into the ISA memory + range, if not then the bounce buffer is automatically + used. If bounce buffer is required but not set up by + isa_dmainit() or too small for + the requested transfer size then the system will + panic. In case of a write request with bounce buffer + the data will be automatically copied to the bounce + buffer. + + + + flags - a bitmask determining the type of operation to + be done. The direction bits B_READ and B_WRITE are mutually + exclusive. + + + + + + B_READ - read from the ISA bus into memory + + + + + B_WRITE - write from the memory to the ISA bus + + + + + B_RAW - if set then the DMA controller will remember + the buffer and after the end of transfer will + automatically re-initialize itself to repeat transfer + of the same buffer again (of course, the driver may + change the data in the buffer before initiating + another transfer in the device). If not set then the + parameters will work only for one transfer, and + isa_dmastart() will have to be + called again before initiating the next + transfer. Using B_RAW makes sense only if the bounce + buffer is not used. + + + + + + + + addr - virtual address of the buffer + + + + + nbytes - length of the buffer. Must be less or equal to + 64KB. Length of 0 is not allowed: the DMA controller will + understand it as 64KB while the kernel code will + understand it as 0 and that would cause unpredictable + effects. For channels number 4 and higher the length must + be even because these channels transfer 2 bytes at a + time. In case of an odd length the last byte will not be + transferred. + + + + + chan - channel number + + + + + + void isa_dmadone(int flags, caddr_t addr, int + nbytes, int chan) + + + Synchronize the memory after device reports that transfer + is done. If that was a read operation with a bounce buffer + then the data will be copied from the bounce buffer to the + original buffer. Arguments are the same as for + isa_dmastart(). Flag B_RAW is + permitted but it does not affect + isa_dmadone() in any way. + + + + + + int isa_dmastatus(int channel_number) + + + Returns the number of bytes left in the current transfer + to be transferred. In case the flag B_READ was set in + isa_dmastart() the number returned + will never be equal to zero. At the end of transfer it + will be automatically reset back to the length of + buffer. The normal use is to check the number of bytes + left after the device signals that the transfer is + completed. If the number of bytes is not 0 then probably + something went wrong with that transfer. + + + + + + int isa_dmastop(int channel_number) + + + Aborts the current transfer and returns the number of + bytes left untransferred. + + + + + + + xxx_isa_probe + + + + This function probes if a device is present. If the driver + supports auto-detection of some part of device configuration + (such as interrupt vector or memory address) this + auto-detection must be done in this routine. + + + + As for any other bus, if the device can not be detected or + is detected but failed the self-test or some other problem + happened then it returns a positive value of error. The + value ENXIO must be returned if the device is not + present. Other error values may mean other conditions. Zero + or negative values mean success. Most of the drivers return + zero as success. + + + + The negative return values are used when a PnP device + supports multiple interfaces. For example, an older + compatibility interface and a newer advanced interface which + are supported by different drivers. Then both drivers would + detect the device. The driver which returns a higher value + in the probe routine takes precedence (in other words, the + driver returning 0 has highest precedence, one returning -1 + is next, one returning -2 is after it and so on). In result + the devices which support only the old interface will be + handled by the old driver (which should return -1 from the + probe routine) while the devices supporting the new + interface as well will be handled by the new driver (which + should return 0 from the probe routine). + + + + The device descriptor struct xxx_softc is allocated by the + system before calling the probe routine. If the probe + routine returns an error the descriptor will be + automatically deallocated by the system. So if a probing + error occurs the driver must make sure that all the + resources it used during probe are deallocated and that + nothing keeps the descriptor from being safely + deallocated. If the probe completes successfully the + descriptor will be preserved by the system and later passed + to the routine xxx_isa_attach(). If a + driver returns a negative value it can't be sure that it + will have the highest priority and its attach routine will + be called. So in this case it also must release all the + resources before returning and if necessary allocate them + again in the attach routine. When + xxx_isa_probe() returns 0 releasing the + resources before returning is also a good idea, a + well-behaved driver should do so. But in case if there is + some problem with releasing the resources the driver is + allowed to keep resources between returning 0 from the probe + routine and execution of the attach routine. + + + + A typical probe routine starts with getting the device + descriptor and unit: + + + struct xxx_softc *sc = device_get_softc(dev); + int unit = device_get_unit(dev); + int pnperror; + int error = 0; + + sc->dev = dev; /* link it back */ + sc->unit = unit; + + + Then check for the PnP devices. The check is carried out by + a table containing the list of PnP IDs supported by this + driver and human-readable descriptions of the device models + corresponding to these IDs. + + + + pnperror=ISA_PNP_PROBE(device_get_parent(dev), dev, + xxx_pnp_ids); if(pnperror == ENXIO) return ENXIO; + + + + The logic of ISA_PNP_PROBE is the following: If this card + (device unit) was not detected as PnP then ENOENT will be + returned. If it was detected as PnP but its detected ID does + not match any of the IDs in the table then ENXIO is + returned. Finally, if it has PnP support and it matches on + of the IDs in the table, 0 is returned and the appropriate + description from the table is set by + device_set_desc(). + + + + If a driver supports only PnP devices then the condition + would look like: + + + if(pnperror != 0) + return pnperror; + + + No special treatment is required for the drivers which don't + support PnP because they pass an empty PnP ID table and will + always get ENXIO if called on a PnP card. + + + + The probe routine normally needs at least some minimal set + of resources, such as I/O port number to find the card and + probe it. Depending on the hardware the driver may be able + to discover the other necessary resources automatically. The + PnP devices have all the resources pre-set by the PnP + subsystem, so the driver does not need to discover them by + itself. + + + + Typically the minimal information required to get access to + the device is the I/O port number. Then some devices allow + to get the rest of information from the device configuration + registers (though not all devices do that). So first we try + to get the port start value: + + + sc->port0 = bus_get_resource_start(dev, + SYS_RES_IOPORT, 0 /*rid*/); if(sc->port0 == 0) return ENXIO; + + + + The base port address is saved in the structure softc for + future use. If it will be used very often then calling the + resource function each time would be prohibitively slow. If + we don't get a port we just return an error. Some device + drivers can instead be clever and try to probe all the + possible ports, like this: + + + + /* table of all possible base I/O port addresses for this device */ + static struct xxx_allports { + u_short port; /* port address */ + short used; /* flag: if this port is already used by some unit */ + } xxx_allports = { + { 0x300, 0 }, + { 0x320, 0 }, + { 0x340, 0 }, + { 0, 0 } /* end of table */ + }; + + ... + int port, i; + ... + + port = bus_get_resource_start(dev, SYS_RES_IOPORT, 0 /*rid*/); + if(port !=0 ) { + for(i=0; xxx_allports[i].port!=0; i++) { + if(xxx_allports[i].used || xxx_allports[i].port != port) + continue; + + /* found it */ + xxx_allports[i].used = 1; + /* do probe on a known port */ + return xxx_really_probe(dev, port); + } + return ENXIO; /* port is unknown or already used */ + } + + /* we get here only if we need to guess the port */ + for(i=0; xxx_allports[i].port!=0; i++) { + if(xxx_allports[i].used) + continue; + + /* mark as used - even if we find nothing at this port + * at least we won't probe it in future + */ + xxx_allports[i].used = 1; + + error = xxx_really_probe(dev, xxx_allports[i].port); + if(error == 0) /* found a device at that port */ + return 0; + } + /* probed all possible addresses, none worked */ + return ENXIO; + + + Of course, normally the driver's + identify() routine should be used for + such things. But there may be one valid reason why it may be + better to be done in probe(): if this + probe would drive some other sensitive device crazy. The + probe routines are ordered with consideration of the + "sensitive" flag: the sensitive devices get probed first and + the rest of devices later. But the + identify() routines are called before + any probes, so they show no respect to the sensitive devices + and may upset them. + + + + Now, after we got the starting port we need to set the port + count (except for PnP devices) because the kernel does not + have this information in the configuration file. + + + + if(pnperror /* only for non-PnP devices */ + && bus_set_resource(dev, SYS_RES_IOPORT, 0, sc->port0, + XXX_PORT_COUNT)<0) + return ENXIO; + + + Finally allocate and activate a piece of port address space + (special values of start and end mean "use those we set by + bus_set_resource()"): + + + + sc->port0_rid = 0; + sc->port0_r = bus_alloc_resource(dev, SYS_RES_IOPORT, + &sc->port0_rid, + /*start*/ 0, /*end*/ ~0, /*count*/ 0, RF_ACTIVE); + + if(sc->port0_r == NULL) + return ENXIO; + + + Now having access to the port-mapped registers we can poke + the device in some way and check if it reacts like it is + expected to. If it does not then there is probably some + other device or no device at all at this address. + + + + Normally drivers don't set up the interrupt handlers until + the attach routine. Instead they do probes in the polling + mode using the DELAY() function for + timeout. The probe routine must never hang forever, all the + waits for the device must be done with timeouts. If the + device does not respond within the time it's probably broken + or misconfigured and the driver must return error. When + determining the timeout interval give the device some extra + time to be on the safe side: although + DELAY() is supposed to delay for the + same amount of time on any machine it has some margin of + error, depending on the exact CPU. + + + + If the probe routine really wants to check that the + interrupts really work it may configure and probe the + interrupts too. But that's not recommended. + + + + /* implemented in some very device-specific way */ + if(error = xxx_probe_ports(sc)) + goto bad; /* will deallocate the resources before returning */ + + + + The fucntion xxx_probe_ports() may also + set the device description depending on the exact model of + device it discovers. But if there is only one supported + device model this can be as well done in a hardcoded way. + Of course, for the PnP devices the PnP support sets the + description from the table automatically. + + + + if(pnperror) + device_set_desc(dev, "Our device model 1234"); + + + + Then the probe routine should either discover the ranges of + all the resources by reading the device configuration + registers or make sure that they were set explicitly by the + user. We will consider it with an example of on-board + memory. The probe routine should be as non-intrusive as + possible, so allocation and check of functionality of the + rest of resources (besides the ports) would be better left + to the attach routine. + + + + The memory address may be specified in the kernel + configuration file or on some devices it may be + pre-configured in non-volatile configuration registers. If + both sources are available and different, which one should + be used? Probably if the user bothered to set the address + explicitly in the kernel configuration file they know what + they're doing and this one should take precedence. An + example of implementation could be: + + + /* try to find out the config address first */ + sc->mem0_p = bus_get_resource_start(dev, SYS_RES_MEMORY, 0 /*rid*/); + if(sc->mem0_p == 0) { /* nope, not specified by user */ + sc->mem0_p = xxx_read_mem0_from_device_config(sc); + + + if(sc->mem0_p == 0) + /* can't get it from device config registers either */ + goto bad; + } else { + if(xxx_set_mem0_address_on_device(sc) < 0) + goto bad; /* device does not support that address */ + } + + /* just like the port, set the memory size, + * for some devices the memory size would not be constant + * but should be read from the device configuration registers instead + * to accommodate different models of devices. Another option would + * be to let the user set the memory size as "msize" configuration + * resource which will be automatically handled by the ISA bus. + */ + if(pnperror) { /* only for non-PnP devices */ + sc->mem0_size = bus_get_resource_count(dev, SYS_RES_MEMORY, 0 /*rid*/); + if(sc->mem0_size == 0) /* not specified by user */ + sc->mem0_size = xxx_read_mem0_size_from_device_config(sc); + + if(sc->mem0_size == 0) { + /* suppose this is a very old model of device without + * auto-configuration features and the user gave no preference, + * so assume the minimalistic case + * (of course, the real value will vary with the driver) + */ + sc->mem0_size = 8*1024; + } + + if(xxx_set_mem0_size_on_device(sc) < 0) + goto bad; /* device does not support that size */ + + if(bus_set_resource(dev, SYS_RES_MEMORY, /*rid*/0, + sc->mem0_p, sc->mem0_size)<0) + goto bad; + } else { + sc->mem0_size = bus_get_resource_count(dev, SYS_RES_MEMORY, 0 /*rid*/); + } + + + Resources for IRQ and DRQ are easy to check by analogy. + + + + If all went well then release all the resources and return success. + + + xxx_free_resources(sc); + return 0; + + + Finally, handle the troublesome situations. All the + resources should be deallocated before returning. We make + use of the fact that before the structure softc is passed to + us it gets zeroed out, so we can find out if some resource + was allocated: then its descriptor is non-zero. + + + bad: + + xxx_free_resources(sc); + if(error) + return error; + else /* exact error is unknown */ + return ENXIO; + + + That would be all for the probe routine. Freeing of + resources is done from multiple places, so it's moved to a + function which may look like: + + +static void + xxx_free_resources(sc) + struct xxx_softc *sc; + { + /* check every resource and free if not zero */ + + /* interrupt handler */ + if(sc->intr_r) { + bus_teardown_intr(sc->dev, sc->intr_r, sc->intr_cookie); + bus_release_resource(sc->dev, SYS_RES_IRQ, sc->intr_rid, + sc->intr_r); + sc->intr_r = 0; + } + + /* all kinds of memory maps we could have allocated */ + if(sc->data_p) { + bus_dmamap_unload(sc->data_tag, sc->data_map); + sc->data_p = 0; + } + if(sc->data) { /* sc->data_map may be legitimately equal to 0 */ + /* the map will also be freed */ + bus_dmamem_free(sc->data_tag, sc->data, sc->data_map); + sc->data = 0; + } + if(sc->data_tag) { + bus_dma_tag_destroy(sc->data_tag); + sc->data_tag = 0; + } + + ... free other maps and tags if we have them ... + + if(sc->parent_tag) { + bus_dma_tag_destroy(sc->parent_tag); + sc->parent_tag = 0; + } + + /* release all the bus resources */ + if(sc->mem0_r) { + bus_release_resource(sc->dev, SYS_RES_MEMORY, sc->mem0_rid, + sc->mem0_r); + sc->mem0_r = 0; + } + ... + if(sc->port0_r) { + bus_release_resource(sc->dev, SYS_RES_IOPORT, sc->port0_rid, + sc->port0_r); + sc->port0_r = 0; + } + } + + + + + xxx_isa_attach + + + The attach routine actually connects the driver to the + system if the probe routine returned success and the system + had chosen to attach that driver. If the probe routine + returned 0 then the attach routine may expect to receive the + device structure softc intact, as it was set by the probe + routine. Also if the probe routine returns 0 it may expect + that the attach routine for this device shall be called at + some point in the future. If the probe routine returns a + negative value then the driver may make none of these + assumptions. + + + The attach routine returns 0 if it completed successfully or + error code otherwise. + + + The attach routine starts just like the probe routine, + with getting some frequently used data into more accessible + variables. + + + struct xxx_softc *sc = device_get_softc(dev); + int unit = device_get_unit(dev); + int error = 0; + + Then allocate and activate all the necessary + resources. Because normally the port range will be released + before returning from probe, it has to be allocated + again. We expect that the probe routine had properly set all + the resource ranges, as well as saved them in the structure + softc. If the probe routine had left some resource allocated + then it does not need to be allocated again (which would be + considered an error). + + + sc->port0_rid = 0; + sc->port0_r = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->port0_rid, + /*start*/ 0, /*end*/ ~0, /*count*/ 0, RF_ACTIVE); + + if(sc->port0_r == NULL) + return ENXIO; + + /* on-board memory */ + sc->mem0_rid = 0; + sc->mem0_r = bus_alloc_resource(dev, SYS_RES_MEMORY, &sc->mem0_rid, + /*start*/ 0, /*end*/ ~0, /*count*/ 0, RF_ACTIVE); + + if(sc->mem0_r == NULL) + goto bad; + + /* get its virtual address */ + sc->mem0_v = rman_get_virtual(sc->mem0_r); + + The DMA request channel (DRQ) is allocated likewise. To + initialize it use functions of the + isa_dma*() family. For example: + + + isa_dmacascade(sc->drq0); + + The interrupt request line (IRQ) is a bit + special. Besides allocation the driver's interrupt handler + should be associated with it. Historically in the old ISA + drivers the argument passed by the system to the interrupt + handler was the device unit number. But in modern drivers + the convention suggests passing the pointer to structure + softc. The important reason is that when the structures + softc are allocated dynamically then getting the unit number + from softc is easy while getting softc from unit number is + difficult. Also this convention makes the drivers for + different buses look more uniform and allows them to share + the code: each bus gets its own probe, attach, detach and + other bus-specific routines while the bulk of the driver + code may be shared among them. + + + + sc->intr_rid = 0; + sc->intr_r = bus_alloc_resource(dev, SYS_RES_MEMORY, &sc->intr_rid, + /*start*/ 0, /*end*/ ~0, /*count*/ 0, RF_ACTIVE); + + if(sc->intr_r == NULL) + goto bad; + + /* + * XXX_INTR_TYPE is supposed to be defined depending on the type of + * the driver, for example as INTR_TYPE_CAM for a CAM driver + */ + error = bus_setup_intr(dev, sc->intr_r, XXX_INTR_TYPE, + (driver_intr_t *) xxx_intr, (void *) sc, &sc->intr_cookie); + if(error) + goto bad; + + + + + If the device needs to make DMA to the main memory then + this memory should be allocated like described before: + + + error=bus_dma_tag_create(NULL, /*alignment*/ 4, + /*boundary*/ 0, /*lowaddr*/ BUS_SPACE_MAXADDR_24BIT, + /*highaddr*/ BUS_SPACE_MAXADDR, /*filter*/ NULL, /*filterarg*/ NULL, + /*maxsize*/ BUS_SPACE_MAXSIZE_24BIT, + /*nsegments*/ BUS_SPACE_UNRESTRICTED, + /*maxsegsz*/ BUS_SPACE_MAXSIZE_24BIT, /*flags*/ 0, + &sc->parent_tag); + if(error) + goto bad; + + /* many things get inherited from the parent tag + * sc->data is supposed to point to the structure with the shared data, + * for example for a ring buffer it could be: + * struct { + * u_short rd_pos; + * u_short wr_pos; + * char bf[XXX_RING_BUFFER_SIZE] + * } *data; + */ + error=bus_dma_tag_create(sc->parent_tag, 1, + 0, BUS_SPACE_MAXADDR, 0, /*filter*/ NULL, /*filterarg*/ NULL, + /*maxsize*/ sizeof(* sc->data), /*nsegments*/ 1, + /*maxsegsz*/ sizeof(* sc->data), /*flags*/ 0, + &sc->data_tag); + if(error) + goto bad; + + error = bus_dmamem_alloc(sc->data_tag, &sc->data, /* flags*/ 0, + &sc->data_map); + if(error) + goto bad; + + /* xxx_alloc_callback() just saves the physical address at + * the pointer passed as its argument, in this case &sc->data_p. + * See details in the section on bus memory mapping. + * It can be implemented like: + * + * static void + * xxx_alloc_callback(void *arg, bus_dma_segment_t *seg, + * int nseg, int error) + * { + * *(bus_addr_t *)arg = seg[0].ds_addr; + * } + */ + bus_dmamap_load(sc->data_tag, sc->data_map, (void *)sc->data, + sizeof (* sc->data), xxx_alloc_callback, (void *) &sc->data_p, + /*flags*/0); + + + After all the necessary resources are allocated the + device should be initialized. The initialization may include + testing that all the expected features are functional. + + if(xxx_initialize(sc) < 0) + goto bad; + + + The bus subsystem will automatically print on the + console the device description set by probe. But if the + driver wants to print some extra information about the + device it may do so, for example: + + + device_printf(dev, "has on-card FIFO buffer of %d bytes\n", sc->fifosize); + + + If the initialization routine experiences any problems + then printing messages about them before returning error is + also recommended. + + The final step of the attach routine is attaching the + device to its functional subsystem in the kernel. The exact + way to do it depends on the type of the driver: a character + device, a block device, a network device, a CAM SCSI bus + device and so on. + + If all went well then return success. + + error = xxx_attach_subsystem(sc); + if(error) + goto bad; + + return 0; + + Finally, handle the troublesome situations. All the + resources should be deallocated before returning an + error. We make use of the fact that before the structure + softc is passed to us it gets zeroed out, so we can find out + if some resource was allocated: then its descriptor is + non-zero. + + bad: + + xxx_free_resources(sc); + if(error) + return error; + else /* exact error is unknown */ + return ENXIO; + + That would be all for the attach routine. + + + + + + xxx_isa_detach + + + If this function is present in the driver and the driver is + compiled as a loadable module then the driver gets the + ability to be unloaded. This is an important feature if the + hardware supports hot plug. But the ISA bus does not support + hot plug, so this feature is not particularly important for + the ISA devices. The ability to unload a driver may be + useful when debugging it, but in many cases installation of + the new version of the driver would be required only after + the old version somehow wedges the system and reboot will be + needed anyway, so the efforts spent on writing the detach + routine may not be worth it. Another argument is that + unloading would allow upgrading the drivers on a production + machine seems to be mostly theoretical. Installing a new + version of a driver is a dangerous operation which should + never be performed on a production machine (and which is not + permitted when the system is running in secure mode). Still + the detach routine may be provided for the sake of + completeness. + + + + The detach routine returns 0 if the driver was successfully + detached or the error code otherwise. + + + + The logic of detach is a mirror of the attach. The first + thing to do is to detach the driver from its kernel + subsystem. If the device is currently open then the driver + has two choices: refuse to be detached or forcibly close and + proceed with detach. The choice used depends on the ability + of the particular kernel subsystem to do a forced close and + on the preferences of the driver's author. Generally the + forced close seems to be the preferred alternative. + struct xxx_softc *sc = device_get_softc(dev); + int error; + + error = xxx_detach_subsystem(sc); + if(error) + return error; + + + Next the driver may want to reset the hardware to some + consistent state. That includes stopping any ongoing + transfers, disabling the DMA channels and interrupts to + avoid memory corruption by the device. For most of the + drivers this is exactly what the shutdown routine does, so + if it is included in the driver we can as well just call it. + + xxx_isa_shutdown(dev); + + + And finally release all the resources and return success. + xxx_free_resources(sc); + return 0; + + + + + + xxx_isa_shutdown + + + This routine is called when the system is about to be shut + down. It is expected to bring the hardware to some + consistent state. For most of the ISA devices no special + action is required, so the function is not really necessary + because the device will be re-initialized on reboot + anyway. But some devices have to be shut down with a special + procedure, to make sure that they will be properly detected + after soft reboot (this is especially true for many devices + with proprietary identification protocols). In any case + disabling DMA and interrupts in the device registers and + stopping any ongoing transfers is a good idea. The exact + action depends on the hardware, so we don't consider it here + in any details. + + + + xxx_intr + + + + The interrupt handler is called when an interrupt is + received which may be from this particular device. The ISA + bus does not support interrupt sharing (except some special + cases) so in practice if the interrupt handler is called + then the interrupt almost for sure came from its + device. Still the interrupt handler must poll the device + registers and make sure that the interrupt was generated by + its device. If not it should just return. + + + + The old convention for the ISA drivers was getting the + device unit number as an argument. It is obsolete, and the + new drivers receive whatever argument was specified for them + in the attach routine when calling + bus_setup_intr(). By the new convention + it should be the pointer to the structure softc. So the + interrupt handler commonly starts as: + + + + static void + xxx_intr(struct xxx_softc *sc) + { + + + + + It runs at the interrupt priority level specified by the + interrupt type parameter of + bus_setup_intr(). That means that all + the other interrupts of the same type as well as all the + software interrupts are disabled. + + + + To avoid races it is commonly written as a loop: + + + + while(xxx_interrupt_pending(sc)) { + xxx_process_interrupt(sc); + xxx_acknowledge_interrupt(sc); + } + + + The interrupt handler has to acknowledge interrupt to the + device only but not to the interrupt controller, the system + takes care of the latter. + + + + diff --git a/en_US.ISO8859-1/books/developers-handbook/isa/chapter.sgml b/en_US.ISO8859-1/books/developers-handbook/isa/chapter.sgml new file mode 100644 index 0000000000..c68a55d85c --- /dev/null +++ b/en_US.ISO8859-1/books/developers-handbook/isa/chapter.sgml @@ -0,0 +1,2479 @@ + + + + ISA device drivers + + + + This chapter was written by &a.babkin; Modifications for the + handbook made by &a.murray;, &a.wylie;, and &a.logo;. + + + + + Synopsis + + This chapter introduces the issues relevant to writing a + driver for an ISA device. The pseudo-code presented here is + rather detailed and reminiscent of the real code but is still + only pseudo-code. It avoids the details irrelevant to the + subject of the discussion. The real-life examples can be found + in the source code of real drivers. In particular the drivers + "ep" and "aha" are good sources of information. + + + + Basic information + + A typical ISA driver would need the following include + files: + +#include <sys/module.h> +#include <sys/bus.h> +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <isa/isavar.h> +#include <isa/pnpvar.h> + + They describe the things specific to the ISA and generic + bus subsystem. + + The bus subsystem is implemented in an object-oriented + fashion, its main structures are accessed by associated method + functions. + + The list of bus methods implemented by an ISA driver is like + one for any other bus. For a hypothetical driver named "xxx" + they would be: + + + + static void xxx_isa_identify (driver_t *, + device_t); Normally used for bus drivers, not + device drivers. But for ISA devices this method may have + special use: if the device provides some device-specific + (non-PnP) way to auto-detect devices this routine may + implement it. + + + + static int xxx_isa_probe (device_t + dev); Probe for a device at a known (or PnP) + location. This routine can also accommodate device-specific + auto-detection of parameters for partially configured + devices. + + + + static int xxx_isa_attach (device_t + dev); Attach and initialize device. + + + + static int xxx_isa_detach (device_t + dev); Detach device before unloading the driver + module. + + + + static int xxx_isa_shutdown (device_t + dev); Execute shutdown of the device before + system shutdown. + + + + static int xxx_isa_suspend (device_t + dev); Suspend the device before the system goes + to the power-save state. May also abort transition to the + power-save state. + + + + static int xxx_isa_resume (device_t + dev); Resume the device activity after return + from power-save state. + + + + + xxx_isa_probe() and + xxx_isa_attach() are mandatory, the rest of + the routines are optional, depending on the device's + needs. + + The driver is linked to the system with the following set of + descriptions. + + /* table of supported bus methods */ + static device_method_t xxx_isa_methods[] = { + /* list all the bus method functions supported by the driver */ + /* omit the unsupported methods */ + DEVMETHOD(device_identify, xxx_isa_identify), + DEVMETHOD(device_probe, xxx_isa_probe), + DEVMETHOD(device_attach, xxx_isa_attach), + DEVMETHOD(device_detach, xxx_isa_detach), + DEVMETHOD(device_shutdown, xxx_isa_shutdown), + DEVMETHOD(device_suspend, xxx_isa_suspend), + DEVMETHOD(device_resume, xxx_isa_resume), + + { 0, 0 } + }; + + static driver_t xxx_isa_driver = { + "xxx", + xxx_isa_methods, + sizeof(struct xxx_softc), + }; + + + static devclass_t xxx_devclass; + + DRIVER_MODULE(xxx, isa, xxx_isa_driver, xxx_devclass, + load_function, load_argument); + + Here struct xxx_softc is a + device-specific structure that contains private driver data + and descriptors for the driver's resources. The bus code + automatically allocates one softc descriptor per device as + needed. + + If the driver is implemented as a loadable module then + load_function() is called to do + driver-specific initialization or clean-up when the driver is + loaded or unloaded and load_argument is passed as one of its + arguments. If the driver does not support dynamic loading (in + other words it must always be linked into kernel) then these + values should be set to 0 and the last definition would look + like: + + DRIVER_MODULE(xxx, isa, xxx_isa_driver, + xxx_devclass, 0, 0); + + If the driver is for a device which supports PnP then a + table of supported PnP IDs must be defined. The table + consists of a list of PnP IDs supported by this driver and + human-readable descriptions of the hardware types and models + having these IDs. It looks like: + + static struct isa_pnp_id xxx_pnp_ids[] = { + /* a line for each supported PnP ID */ + { 0x12345678, "Our device model 1234A" }, + { 0x12345679, "Our device model 1234B" }, + { 0, NULL }, /* end of table */ + }; + + If the driver does not support PnP devices it still needs + an empty PnP ID table, like: + + static struct isa_pnp_id xxx_pnp_ids[] = { + { 0, NULL }, /* end of table */ + }; + + + + + Device_t pointer + + Device_t is the pointer type for + the device structure. Here we consider only the methods + interesting from the device driver writer's standpoint. The + methods to manipulate values in the device structure + are: + + + + device_t + device_get_parent(dev) Get the parent bus of a + device. + + driver_t + device_get_driver(dev) Get pointer to its driver + structure. + + char + *device_get_name(dev) Get the driver name, such + as "xxx" for our example. + + int device_get_unit(dev) + Get the unit number (units are numbered from 0 for the + devices associated with each driver). + + char + *device_get_nameunit(dev) Get the device name + including the unit number, such as "xxx0" , "xxx1" and so + on. + + char + *device_get_desc(dev) Get the device + description. Normally it describes the exact model of device + in human-readable form. + + device_set_desc(dev, + desc) Set the description. This makes the device + description point to the string desc which may not be + deallocated or changed after that. + + device_set_desc_copy(dev, + desc) Set the description. The description is + copied into an internal dynamically allocated buffer, so the + string desc may be changed afterwards without adverse + effects. + + void + *device_get_softc(dev) Get pointer to the device + descriptor (struct xxx_softc) + associated with this device. + + u_int32_t + device_get_flags(dev) Get the flags specified for + the device in the configuration file. + + + + A convenience function device_printf(dev, fmt, + ...) may be used to print the messages from the + device driver. It automatically prepends the unitname and + colon to the message. + + The device_t methods are implemented in the file + kern/bus_subr.c. + + + + + Config file and the order of identifying and probing + during auto-configuration + + The ISA devices are described in the kernel config file + like: + + device xxx0 at isa? port 0x300 irq 10 drq 5 + iomem 0xd0000 flags 0x1 sensitive + + The values of port, IRQ and so on are converted to the + resource values associated with the device. They are optional, + depending on the device needs and abilities for + auto-configuration. For example, some devices don't need DRQ + at all and some allow the driver to read the IRQ setting from + the device configuration ports. If a machine has multiple ISA + buses the exact bus may be specified in the configuration + line, like "isa0" or "isa1", otherwise the device would be + searched for on all the ISA buses. + + "sensitive" is a resource requesting that this device must + be probed before all non-sensitive devices. It is supported + but does not seem to be used in any current driver. + + For legacy ISA devices in many cases the drivers are still + able to detect the configuration parameters. But each device + to be configured in the system must have a config line. If two + devices of some type are installed in the system but there is + only one configuration line for the corresponding driver, ie: + device xxx0 at isa? then only + one device will be configured. + + But for the devices supporting automatic identification by + the means of Plug-n-Play or some proprietary protocol one + configuration line is enough to configure all the devices in + the system, like the one above or just simply: + + device xxx at isa? + + If a driver supports both auto-identified and legacy + devices and both kinds are installed at once in one machine + then it's enough to describe in the config file the legacy + devices only. The auto-identified devices will be added + automatically. + + When an ISA bus is auto-configured the events happen as + follows: + + All the drivers' identify routines (including the PnP + identify routine which identifies all the PnP devices) are + called in random order. As they identify the devices they add + them to the list on the ISA bus. Normally the drivers' + identify routines associate their drivers with the new + devices. The PnP identify routine does not know about the + other drivers yet so it does not associate any with the new + devices it adds. + + The PnP devices are put to sleep using the PnP protocol to + prevent them from being probed as legacy devices. + + The probe routines of non-PnP devices marked as + "sensitive" are called. If probe for a device went + successfully, the attach routine is called for it. + + The probe and attach routines of all non-PNP devices are + called likewise. + + The PnP devices are brought back from the sleep state and + assigned the resources they request: I/O and memory address + ranges, IRQs and DRQs, all of them not conflicting with the + attached legacy devices. + + Then for each PnP device the probe routines of all the + present ISA drivers are called. The first one that claims the + device gets attached. It is possible that multiple drivers + would claim the device with different priority, the + highest-priority driver wins. The probe routines must call + ISA_PNP_PROBE() to compare the actual PnP + ID with the list of the IDs supported by the driver and if the + ID is not in the table return failure. That means that + absolutely every driver, even the ones not supporting any PnP + devices must call ISA_PNP_PROBE(), at + least with an empty PnP ID table to return failure on unknown + PnP devices. + + The probe routine returns a positive value (the error + code) on error, zero or negative value on success. + + The negative return values are used when a PnP device + supports multiple interfaces. For example, an older + compatibility interface and a newer advanced interface which + are supported by different drivers. Then both drivers would + detect the device. The driver which returns a higher value in + the probe routine takes precedence (in other words, the driver + returning 0 has highest precedence, returning -1 is next, + returning -2 is after it and so on). In result the devices + which support only the old interface will be handled by the + old driver (which should return -1 from the probe routine) + while the devices supporting the new interface as well will be + handled by the new driver (which should return 0 from the + probe routine). If multiple drivers return the same value then + the one called first wins. So if a driver returns value 0 it + may be sure that it won the priority arbitration. + + The device-specific identify routines can also assign not + a driver but a class of drivers to the device. Then all the + drivers in the class are probed for this device, like the case + with PnP. This feature is not implemented in any existing + driver and is not considered further in this document. + + Because the PnP devices are disabled when probing the + legacy devices they will not be attached twice (once as legacy + and once as PnP). But in case of device-dependent identify + routines it's the responsibility of the driver to make sure + that the same device won't be attached by the driver twice: + once as legacy user-configured and once as + auto-identified. + + Another practical consequence for the auto-identified + devices (both PnP and device-specific) is that the flags can + not be passed to them from the kernel configuration file. So + they must either not use the flags at all or use the flags + from the device unit 0 for all the auto-identified devices or + use the sysctl interface instead of flags. + + Other unusual configurations may be accommodated by + accessing the configuration resources directly with functions + of families resource_query_*() and + resource_*_value(). Their implementations + are located in kern/subr_bus.h. The old IDE disk driver + i386/isa/wd.c contains examples of such use. But the standard + means of configuration must always be preferred. Leave parsing + the configuration resources to the bus configuration + code. + + + + + Resources + + The information that a user enters into the kernel + configuration file is processed and passed to the kernel as + configuration resources. This information is parsed by the bus + configuration code and transformed into a value of structure + device_t and the bus resources associated with it. The drivers + may access the configuration resources directly using + functions resource_* for more complex cases of + configuration. But generally it's not needed nor recommended, + so this issue is not discussed further. + + The bus resources are associated with each device. They + are identified by type and number within the type. For the ISA + bus the following types are defined: + + + + SYS_RES_IRQ - interrupt + number + + + + SYS_RES_DRQ - ISA DMA channel + number + + + + SYS_RES_MEMORY - range of + device memory mapped into the system memory space + + + + + SYS_RES_IOPORT - range of + device I/O registers + + + + The enumeration within types starts from 0, so if a device + has two memory regions if would have resources of type + SYS_RES_MEMORY numbered 0 and 1. The resource type has + nothing to do with the C language type, all the resource + values have the C language type "unsigned long" and must be + cast as necessary. The resource numbers don't have to be + contiguous although for ISA they normally would be. The + permitted resource numbers for ISA devices are: + + IRQ: 0-1 + DRQ: 0-1 + MEMORY: 0-3 + IOPORT: 0-7 + + All the resources are represented as ranges, with a start + value and count. For IRQ and DRQ resources the count would be + normally equal to 1. The values for memory refer to the + physical addresses. + + Three types of activities can be performed on + resources: + + + set/get + allocate/release + activate/deactivate + + + Setting sets the range used by the resource. Allocation + reserves the requested range that no other driver would be + able to reserve it (and checking that no other driver reserved + this range already). Activation makes the resource accessible + to the driver doing whatever is necessary for that (for + example, for memory it would be mapping into the kernel + virtual address space). + + The functions to manipulate resources are: + + + + int bus_set_resource(device_t dev, int type, + int rid, u_long start, u_long count) + + Set a range for a resource. Returns 0 if successful, + error code otherwise. Normally the only reason this + function would return an error is value of type, rid, + start or count out of permitted range. + + + + dev - driver's device + + + type - type of resource, SYS_RES_* + + + rid - resource number (ID) within type + + + start, count - resource range + + + + + + int bus_get_resource(device_t dev, int type, + int rid, u_long *startp, u_long *countp) + + Get the range of resource. Returns 0 if successful, + error code if the resource is not defined yet. + + + + u_long bus_get_resource_start(device_t dev, + int type, int rid) u_long bus_get_resource_count (device_t + dev, int type, int rid) + + Convenience functions to get only the start or + count. Return 0 in case of error, so if the resource start + has 0 among the legitimate values it would be impossible + to tell if the value is 0 or an error occurred. Luckily, + no ISA resources for add-on drivers may have a start value + equal 0. + + + + void bus_delete_resource(device_t dev, int + type, int rid) + Delete a resource, make it undefined. + + + + struct resource * + bus_alloc_resource(device_t dev, int type, int *rid, + u_long start, u_long end, u_long count, u_int + flags) + + Allocate a resource as a range of count values not + allocated by anyone else, somewhere between start and + end. Alas, alignment is not supported. If the resource + was not set yet it's automatically created. The special + values of start 0 and end ~0 (all ones) means that the + fixed values previously set by + bus_set_resource() must be used + instead: start and count as themselves and + end=(start+count), in this case if the resource was not + defined before then an error is returned. Although rid is + passed by reference it's not set anywhere by the resource + allocation code of the ISA bus. (The other buses may use a + different approach and modify it). + + + + Flags are a bitmap, the flags interesting for the caller + are: + + + + RF_ACTIVE - causes the resource + to be automatically activated after allocation. + + + + RF_SHAREABLE - resource may be + shared at the same time by multiple drivers. + + + + RF_TIMESHARE - resource may be + time-shared by multiple drivers, i.e. allocated at the + same time by many but activated only by one at any given + moment of time. + + + + Returns 0 on error. The allocated values may be + obtained from the returned handle using methods + rhand_*(). + + + int bus_release_resource(device_t dev, int + type, int rid, struct resource *r) + + + + Release the resource, r is the handle returned by + bus_alloc_resource(). Returns 0 on + success, error code otherwise. + + + + int bus_activate_resource(device_t dev, int + type, int rid, struct resource *r) + int bus_deactivate_resource(device_t dev, int + type, int rid, struct resource *r) + + + + Activate or deactivate resource. Return 0 on success, + error code otherwise. If the resource is time-shared and + currently activated by another driver then EBUSY is + returned. + + + + int bus_setup_intr(device_t dev, struct + resource *r, int flags, driver_intr_t *handler, void *arg, + void **cookiep) int + bus_teardown_intr(device_t dev, struct resource *r, void + *cookie) + + + + Associate or de-associate the interrupt handler with a + device. Return 0 on success, error code otherwise. + + + + r - the activated resource handler describing the + IRQ + flags - the interrupt priority level, one of: + + + + INTR_TYPE_TTY - terminals and + other likewise character-type devices. To mask them + use spltty(). + + + (INTR_TYPE_TTY | + INTR_TYPE_FAST) - terminal type devices + with small input buffer, critical to the data loss on + input (such as the old-fashioned serial ports). To + mask them use spltty(). + + + INTR_TYPE_BIO - block-type + devices, except those on the CAM controllers. To mask + them use splbio(). + + + INTR_TYPE_CAM - CAM (Common + Access Method) bus controllers. To mask them use + splcam(). + + + INTR_TYPE_NET - network + interface controllers. To mask them use + splimp(). + + + INTR_TYPE_MISC - + miscellaneous devices. There is no other way to mask + them than by splhigh() which + masks all interrupts. + + + + + + When an interrupt handler executes all the other + interrupts matching its priority level will be masked. The + only exception is the MISC level for which no other interrupts + are masked and which is not masked by any other + interrupt. + + + + handler - pointer to the handler + function, the type driver_intr_t is defined as "void + driver_intr_t(void *)" + + + arg - the argument passed to the + handler to identify this particular device. It is cast + from void* to any real type by the handler. The old + convention for the ISA interrupt handlers was to use the + unit number as argument, the new (recommended) convention + is using a pointer to the device softc structure. + + + cookie[p] - the value received + from setup() is used to identify the + handler when passed to + teardown() + + + + A number of methods is defined to operate on the resource + handlers (struct resource *). Those of interest to the device + driver writers are: + + + + u_long rman_get_start(r) u_long + rman_get_end(r) Get the start and end of + allocated resource range. + + + void *rman_get_virtual(r) Get + the virtual address of activated memory resource. + + + + + + + Bus memory mapping + + In many cases data is exchanged between the driver and the + device through the memory. Two variants are possible: + + (a) memory is located on the device card + (b) memory is the main memory of computer + + In the case (a) the driver always copies the data back and + forth between the on-card memory and the main memory as + necessary. To map the on-card memory into the kernel virtual + address space the physical address and length of the on-card + memory must be defined as a SYS_RES_MEMORY resource. That + resource can then be allocated and activated, and its virtual + address obtained using + rman_get_virtual(). The older drivers + used the function pmap_mapdev() for this + purpose, which should not be used directly any more. Now it's + one of the internal steps of resource activation. + + Most of the ISA cards will have their memory configured + for physical location somewhere in range 640KB-1MB. Some of + the ISA cards require larger memory ranges which should be + placed somewhere under 16MB (because of the 24-bit address + limitation on the ISA bus). In that case if the machine has + more memory than the start address of the device memory (in + other words, they overlap) a memory hole must be configured at + the address range used by devices. Many BIOSes allow to + configure a memory hole of 1MB starting at 14MB or + 15MB. FreeBSD can handle the memory holes properly if the BIOS + reports them properly (old BIOSes may have this feature + broken). + + In the case (b) just the address of the data is sent to + the device, and the device uses DMA to actually access the + data in the main memory. Two limitations are present: First, + ISA cards can only access memory below 16MB. Second, the + contiguous pages in virtual address space may not be + contiguous in physical address space, so the device may have + to do scatter/gather operations. The bus subsystem provides + ready solutions for some of these problems, the rest has to be + done by the drivers themselves. + + Two structures are used for DMA memory allocation, + bus_dma_tag_t and bus_dmamap_t. Tag describes the properties + required for the DMA memory. Map represents a memory block + allocated according to these properties. Multiple maps may be + associated with the same tag. + + Tags are organized into a tree-like hierarchy with + inheritance of the properties. A child tag inherits all the + requirements of its parent tag or may make them more strict + but never more loose. + + Normally one top-level tag (with no parent) is created for + each device unit. If multiple memory areas with different + requirements are needed for each device then a tag for each of + them may be created as a child of the parent tag. + + The tags can be used to create a map in two ways. + + First, a chunk of contiguous memory conformant with the + tag requirements may be allocated (and later may be + freed). This is normally used to allocate relatively + long-living areas of memory for communication with the + device. Loading of such memory into a map is trivial: it's + always considered as one chunk in the appropriate physical + memory range. + + Second, an arbitrary area of virtual memory may be loaded + into a map. Each page of this memory will be checked for + conformance to the map requirement. If it conforms then it's + left at it's original location. If it is not then a fresh + conformant "bounce page" is allocated and used as intermediate + storage. When writing the data from the non-conformant + original pages they will be copied to their bounce pages first + and then transferred from the bounce pages to the device. When + reading the data would go from the device to the bounce pages + and then copied to their non-conformant original pages. The + process of copying between the original and bounce pages is + called synchronization. This is normally used on per-transfer + basis: buffer for each transfer would be loaded, transfer done + and buffer unloaded. + + The functions working on the DMA memory are: + + + + int bus_dma_tag_create(bus_dma_tag_t parent, + bus_size_t alignment, bus_size_t boundary, bus_addr_t + lowaddr, bus_addr_t highaddr, bus_dma_filter_t *filter, void + *filterarg, bus_size_t maxsize, int nsegments, bus_size_t + maxsegsz, int flags, bus_dma_tag_t *dmat) + + Create a new tag. Returns 0 on success, the error code + otherwise. + + + + parent - parent tag, or NULL to + create a top-level tag alignment - + required physical alignment of the memory area to be + allocated for this tag. Use value 1 for "no specific + alignment". Applies only to the future + bus_dmamem_alloc() but not + bus_dmamap_create() calls. + boundary - physical address + boundary that must not be crossed when allocating the + memory. Use value 0 for "no boundary". Applies only to + the future bus_dmamem_alloc() but + not bus_dmamap_create() calls. + Must be power of 2. If the memory is planned to be used + in non-cascaded DMA mode (i.e. the DMA addresses will be + supplied not by the device itself but by the ISA DMA + controller) then the boundary must be no larger than + 64KB (64*1024) due to the limitations of the DMA + hardware. + + + + lowaddr, highaddr - the names + are slighlty misleading; these values are used to limit + the permitted range of physical addresses used to + allocate the memory. The exact meaning varies depending + on the planned future use: + + + + For bus_dmamem_alloc() all + the addresses from 0 to lowaddr-1 are considered + permitted, the higher ones are forbidden. + + + + For bus_dmamap_create() all + the addresses outside the inclusive range [lowaddr; + highaddr] are considered accessible. The addresses + of pages inside the range are passed to the filter + function which decides if they are accessible. If no + filter function is supplied then all the range is + considered unaccessible. + + + + For the ISA devices the normal values (with no + filter function) are: + lowaddr = BUS_SPACE_MAXADDR_24BIT + highaddr = BUS_SPACE_MAXADDR + + + + + + + filter, filterarg - the filter + function and its argument. If NULL is passed for filter + then the whole range [lowaddr, highaddr] is considered + unaccessible when doing + bus_dmamap_create(). Otherwise the + physical address of each attempted page in range + [lowaddr; highaddr] is passed to the filter function + which decides if it is accessible. The prototype of the + filter function is: int filterfunc(void *arg, + bus_addr_t paddr) It must return 0 if the + page is accessible, non-zero otherwise. + + + + maxsize - the maximal size of + memory (in bytes) that may be allocated through this + tag. In case it's difficult to estimate or could be + arbitrarily big, the value for ISA devices would be + BUS_SPACE_MAXSIZE_24BIT. + + + + nsegments - maximal number of + scatter-gather segments supported by the device. If + unrestricted then the value BUS_SPACE_UNRESTRICTED + should be used. This value is recommended for the parent + tags, the actual restrictions would then be specified + for the descendant tags. Tags with nsegments equal to + BUS_SPACE_UNRESTRICTED may not be used to actually load + maps, they may be used only as parent tags. The + practical limit for nsegments seems to be about 250-300, + higher values will cause kernel stack overflow. But + anyway the hardware normally can't support that many + scatter-gather buffers. + + + + maxsegsz - maximal size of a + scatter-gather segment supported by the device. The + maximal value for ISA device would be + BUS_SPACE_MAXSIZE_24BIT. + + + + flags - a bitmap of flags. The + only interesting flags are: + + + + BUS_DMA_ALLOCNOW - requests + to allocate all the potentially needed bounce pages + when creating the tag + + + + BUS_DMA_ISA - mysterious + flag used only on Alpha machines. It is not defined + for the i386 machines. Probably it should be used + by all the ISA drivers for Alpha machines but it + looks like there are no such drivers yet. + + + + + + dmat - pointer to the storage + for the new tag to be returned + + + + + + + + int bus_dma_tag_destroy(bus_dma_tag_t + dmat) + + Destroy a tag. Returns 0 on success, the error code + otherwise. + + dmat - the tag to be destroyed + + + + + int bus_dmamem_alloc(bus_dma_tag_t dmat, + void** vaddr, int flags, bus_dmamap_t + *mapp) + + Allocate an area of contiguous memory described by the + tag. The size of memory to be allocated is tag's maxsize. + Returns 0 on success, the error code otherwise. The result + still has to be loaded by + bus_dmamap_load() before used to get + the physical address of the memory. + + + + + + + dmat - the tag + + + + + vaddr - pointer to the storage + for the kernel virtual address of the allocated area + to be returned. + + + + + flags - a bitmap of flags. The only interesting flag is: + + + + + BUS_DMA_NOWAIT - if the + memory is not immediately available return the + error. If this flag is not set then the routine + is allowed to sleep waiting until the memory + will become available. + + + + + + + mapp - pointer to the storage + for the new map to be returned + + + + + + + + void bus_dmamem_free(bus_dma_tag_t dmat, void + *vaddr, bus_dmamap_t map) + + + Free the memory allocated by + bus_dmamem_alloc(). As of now + freeing of the memory allocated with ISA restrictions is + not implemented. Because of this the recommended model + of use is to keep and re-use the allocated areas for as + long as possible. Do not lightly free some area and then + shortly allocate it again. That does not mean that + bus_dmamem_free() should not be + used at all: hopefully it will be properly implemented + soon. + + + + + dmat - the tag + + + + + vaddr - the kernel virtual + address of the memory + + + + + map - the map of the memory (as + returned from + bus_dmamem_alloc()) + + + + + + + + int bus_dmamap_create(bus_dma_tag_t dmat, int + flags, bus_dmamap_t *mapp) + + + Create a map for the tag, to be used in + bus_dmamap_load() later. Returns 0 + on success, the error code otherwise. + + + + + dmat - the tag + + + + + flags - theoretically, a bit map + of flags. But no flags are defined yet, so as of now + it will be always 0. + + + + + mapp - pointer to the storage + for the new map to be returned + + + + + + + + int bus_dmamap_destroy(bus_dma_tag_t dmat, + bus_dmamap_t map) + + + Destroy a map. Returns 0 on success, the error code otherwise. + + + + + + dmat - the tag to which the map is associated + + + + + map - the map to be destroyed + + + + + + + + int bus_dmamap_load(bus_dma_tag_t dmat, + bus_dmamap_t map, void *buf, bus_size_t buflen, + bus_dmamap_callback_t *callback, void *callback_arg, int + flags) + + + Load a buffer into the map (the map must be previously + created by bus_dmamap_create() or + bus_dmamem_alloc()). All the pages + of the buffer are checked for conformance to the tag + requirements and for those not conformant the bounce + pages are allocated. An array of physical segment + descriptors is built and passed to the callback + routine. This callback routine is then expected to + handle it in some way. The number of bounce buffers in + the system is limited, so if the bounce buffers are + needed but not immediately available the request will be + queued and the callback will be called when the bounce + buffers will become available. Returns 0 if the callback + was executed immediately or EINPROGRESS if the request + was queued for future execution. In the latter case the + synchronization with queued callback routine is the + responsibility of the driver. + + + + + + dmat - the tag + + + + + map - the map + + + + + buf - kernel virtual address of + the buffer + + + + + buflen - length of the buffer + + + + + callback, + callback_arg - the callback function and + its argument + + + + + + The prototype of callback function is: + + + void callback(void *arg, bus_dma_segment_t + *seg, int nseg, int error) + + + + + + arg - the same as callback_arg + passed to bus_dmamap_load() + + + + + seg - array of the segment + descriptors + + + + + nseg - number of descriptors in + array + + + + + error - indication of the + segment number overflow: if it's set to EFBIG then + the buffer did not fit into the maximal number of + segments permitted by the tag. In this case only the + permitted number of descriptors will be in the + array. Handling of this situation is up to the + driver: depending on the desired semantics it can + either consider this an error or split the buffer in + two and handle the second part separately + + + + + + Each entry in the segments array contains the fields: + + + + + + + ds_addr - physical bus address + of the segment + + + + + ds_len - length of the segment + + + + + + + + + void bus_dmamap_unload(bus_dma_tag_t dmat, + bus_dmamap_t map) + + unload the map. + + + + + + dmat - tag + + + + + map - loaded map + + + + + + + + + void bus_dmamap_sync (bus_dma_tag_t dmat, + bus_dmamap_t map, bus_dmasync_op_t op) + + + Synchronise a loaded buffer with its bounce pages before + and after physical transfer to or from device. This is + the function that does all the necessary copying of data + between the original buffer and its mapped version. The + buffers must be synchronized both before and after doing + the transfer. + + + + + + dmat - tag + + + + + map - loaded map + + + + + op - type of synchronization + operation to perform: + + + + + + + + BUS_DMASYNC_PREREAD - before + reading from device into buffer + + + + + BUS_DMASYNC_POSTREAD - after + reading from device into buffer + + + + + BUS_DMASYNC_PREWRITE - before + writing the buffer to device + + + + + BUS_DMASYNC_POSTWRITE - after + writing the buffer to device + + + + + + + + + As of now PREREAD and POSTWRITE are null operations but that + may change in the future, so they must not be ignored in the + driver. Synchronization is not needed for the memory + obtained from bus_dmamem_alloc(). + + + Before calling the callback function from + bus_dmamap_load() the segment array is + stored in the stack. And it gets pre-allocated for the + maximal number of segments allowed by the tag. Because of + this the practical limit for the number of segments on i386 + architecture is about 250-300 (the kernel stack is 4KB minus + the size of the user structure, size of a segment array + entry is 8 bytes, and some space must be left). Because the + array is allocated based on the maximal number this value + must not be set higher than really needed. Fortunately, for + most of hardware the maximal supported number of segments is + much lower. But if the driver wants to handle buffers with a + very large number of scatter-gather segments it should do + that in portions: load part of the buffer, transfer it to + the device, load next part of the buffer, and so on. + + + Another practical consequence is that the number of segments + may limit the size of the buffer. If all the pages in the + buffer happen to be physically non-contiguous then the + maximal supported buffer size for that fragmented case would + be (nsegments * page_size). For example, if a maximal number + of 10 segments is supported then on i386 maximal guaranteed + supported buffer size would be 40K. If a higher size is + desired then special tricks should be used in the driver. + + + If the hardware does not support scatter-gather at all or + the driver wants to support some buffer size even if it's + heavily fragmented then the solution is to allocate a + contiguous buffer in the driver and use it as intermediate + storage if the original buffer does not fit. + + + Below are the typical call sequences when using a map depend + on the use of the map. The characters -> are used to show + the flow of time. + + + For a buffer which stays practically fixed during all the + time between attachment and detachment of a device: + + bus_dmamem_alloc -> bus_dmamap_load -> ...use buffer... -> + -> bus_dmamap_unload -> bus_dmamem_free + + + For a buffer that changes frequently and is passed from + outside the driver: + + + bus_dmamap_create -> + -> bus_dmamap_load -> bus_dmamap_sync(PRE...) -> do transfer -> + -> bus_dmamap_sync(POST...) -> bus_dmamap_unload -> + ... + -> bus_dmamap_load -> bus_dmamap_sync(PRE...) -> do transfer -> + -> bus_dmamap_sync(POST...) -> bus_dmamap_unload -> + -> bus_dmamap_destroy + + + + When loading a map created by + bus_dmamem_alloc() the passed address + and size of the buffer must be the same as used in + bus_dmamem_alloc(). In this case it is + guaranteed that the whole buffer will be mapped as one + segment (so the callback may be based on this assumption) + and the request will be executed immediately (EINPROGRESS + will never be returned). All the callback needs to do in + this case is to save the physical address. + + + A typical example would be: + + + static void + alloc_callback(void *arg, bus_dma_segment_t *seg, int nseg, int error) + { + *(bus_addr_t *)arg = seg[0].ds_addr; + } + + ... + int error; + struct somedata { + .... + }; + struct somedata *vsomedata; /* virtual address */ + bus_addr_t psomedata; /* physical bus-relative address */ + bus_dma_tag_t tag_somedata; + bus_dmamap_t map_somedata; + ... + + error=bus_dma_tag_create(parent_tag, alignment, + boundary, lowaddr, highaddr, /*filter*/ NULL, /*filterarg*/ NULL, + /*maxsize*/ sizeof(struct somedata), /*nsegments*/ 1, + /*maxsegsz*/ sizeof(struct somedata), /*flags*/ 0, + &tag_somedata); + if(error) + return error; + + error = bus_dmamem_alloc(tag_somedata, &vsomedata, /* flags*/ 0, + &map_somedata); + if(error) + return error; + + bus_dmamap_load(tag_somedata, map_somedata, (void *)vsomedata, + sizeof (struct somedata), alloc_callback, + (void *) &psomedata, /*flags*/0); + + + Looks a bit long and complicated but that's the way to do + it. The practical consequence is: if multiple memory areas + are allocated always together it would be a really good idea + to combine them all into one structure and allocate as one + (if the alignment and boundary limitations permit). + + + When loading an arbitrary buffer into the map created by + bus_dmamap_create() special measures + must be taken to synchronize with the callback in case it + would be delayed. The code would look like: + + + { + int s; + int error; + + s = splsoftvm(); + error = bus_dmamap_load( + dmat, + dmamap, + buffer_ptr, + buffer_len, + callback, + /*callback_arg*/ buffer_descriptor, + /*flags*/0); + if (error == EINPROGRESS) { + /* + * Do whatever is needed to ensure synchronization + * with callback. Callback is guaranteed not to be started + * until we do splx() or tsleep(). + */ + } + splx(s); + } + + + Two possible approaches for the processing of requests are: + + + 1. If requests are completed by marking them explicitly as + done (such as the CAM requests) then it would be simpler to + put all the further processing into the callback driver + which would mark the request when it's done. Then not much + extra synchronization is needed. For the flow control + reasons it may be a good idea to freeze the request queue + until this request gets completed. + + + 2. If requests are completed when the function returns (such + as classic read or write requests on character devices) then + a synchronization flag should be set in the buffer + descriptor and tsleep() called. Later + when the callback gets called it will do it's processing and + check this synchronization flag. If it's set then the + callback should issue a wakeup. In this approach the + callback function could either do all the needed processing + (just like the previous case) or simply save the segments + array in the buffer descriptor. Then after callback + completes the calling function could use this saved segments + array and do all the processing. + + + + + + + + DMA + + + The Direct Memory Access (DMA) is implemented in the ISA bus + through the DMA controller (actually, two of them but that's + an irrelevant detail). To make the early ISA devices simple + and cheap the logic of the bus control and address + generation was concentrated in the DMA controller. + Fortunately, FreeBSD provides a set of functions that mostly + hide the annoying details of the DMA controller from the + device drivers. + + + + The simplest case is for the fairly intelligent + devices. Like the bus master devices on PCI they can + generate the bus cycles and memory addresses all by + themselves. The only thing they really need from the DMA + controller is bus arbitration. So for this purpose they + pretend to be cascaded slave DMA controllers. And the only + thing needed from the system DMA controller is to enable the + cascaded mode on a DMA channel by calling the following + function when attaching the driver: + + + + void isa_dmacascade(int channel_number) + + + + All the further activity is done by programming the + device. When detaching the driver no DMA-related functions + need to be called. + + + + For the simpler devices things get more complicated. The + functions used are: + + + + + + + int isa_dma_acquire(int chanel_number) + + + Reserve a DMA channel. Returns 0 on success or EBUSY + if the channel was already reserved by this or a + different driver. Most of the ISA devices are not able + to share DMA channels anyway, so normally this + function is called when attaching a device. This + reservation was made redundant by the modern interface + of bus resources but still must be used in addition to + the latter. If not used then later, other DMA routines + will panic. + + + + + + int isa_dma_release(int chanel_number) + + + Release a previously reserved DMA channel. No + transfers must be in progress when the channel is + released (as well as the device must not try to + initiate transfer after the channel is released). + + + + + + void isa_dmainit(int chan, u_int + bouncebufsize) + + + Allocate a bounce buffer for use with the specified + channel. The requested size of the buffer can't exceed + 64KB. This bounce buffer will be automatically used + later if a transfer buffer happens to be not + physically contiguous or outside of the memory + accessible by the ISA bus or crossing the 64KB + boundary. If the transfers will be always done from + buffers which conform to these conditions (such as + those allocated by + bus_dmamem_alloc() with proper + limitations) then isa_dmainit() + does not have to be called. But it's quite convenient + to transfer arbitrary data using the DMA controller. + The bounce buffer will automatically care of the + scatter-gather issues. + + + + + + chan - channel number + + + + + bouncebufsize - size of the + bounce buffer in bytes + + + + + + + + + + void isa_dmastart(int flags, caddr_t addr, u_int + nbytes, int chan) + + + Prepare to start a DMA transfer. This function must be + called to set up the DMA controller before actually + starting transfer on the device. It checks that the + buffer is contiguous and falls into the ISA memory + range, if not then the bounce buffer is automatically + used. If bounce buffer is required but not set up by + isa_dmainit() or too small for + the requested transfer size then the system will + panic. In case of a write request with bounce buffer + the data will be automatically copied to the bounce + buffer. + + + + flags - a bitmask determining the type of operation to + be done. The direction bits B_READ and B_WRITE are mutually + exclusive. + + + + + + B_READ - read from the ISA bus into memory + + + + + B_WRITE - write from the memory to the ISA bus + + + + + B_RAW - if set then the DMA controller will remember + the buffer and after the end of transfer will + automatically re-initialize itself to repeat transfer + of the same buffer again (of course, the driver may + change the data in the buffer before initiating + another transfer in the device). If not set then the + parameters will work only for one transfer, and + isa_dmastart() will have to be + called again before initiating the next + transfer. Using B_RAW makes sense only if the bounce + buffer is not used. + + + + + + + + addr - virtual address of the buffer + + + + + nbytes - length of the buffer. Must be less or equal to + 64KB. Length of 0 is not allowed: the DMA controller will + understand it as 64KB while the kernel code will + understand it as 0 and that would cause unpredictable + effects. For channels number 4 and higher the length must + be even because these channels transfer 2 bytes at a + time. In case of an odd length the last byte will not be + transferred. + + + + + chan - channel number + + + + + + void isa_dmadone(int flags, caddr_t addr, int + nbytes, int chan) + + + Synchronize the memory after device reports that transfer + is done. If that was a read operation with a bounce buffer + then the data will be copied from the bounce buffer to the + original buffer. Arguments are the same as for + isa_dmastart(). Flag B_RAW is + permitted but it does not affect + isa_dmadone() in any way. + + + + + + int isa_dmastatus(int channel_number) + + + Returns the number of bytes left in the current transfer + to be transferred. In case the flag B_READ was set in + isa_dmastart() the number returned + will never be equal to zero. At the end of transfer it + will be automatically reset back to the length of + buffer. The normal use is to check the number of bytes + left after the device signals that the transfer is + completed. If the number of bytes is not 0 then probably + something went wrong with that transfer. + + + + + + int isa_dmastop(int channel_number) + + + Aborts the current transfer and returns the number of + bytes left untransferred. + + + + + + + xxx_isa_probe + + + + This function probes if a device is present. If the driver + supports auto-detection of some part of device configuration + (such as interrupt vector or memory address) this + auto-detection must be done in this routine. + + + + As for any other bus, if the device can not be detected or + is detected but failed the self-test or some other problem + happened then it returns a positive value of error. The + value ENXIO must be returned if the device is not + present. Other error values may mean other conditions. Zero + or negative values mean success. Most of the drivers return + zero as success. + + + + The negative return values are used when a PnP device + supports multiple interfaces. For example, an older + compatibility interface and a newer advanced interface which + are supported by different drivers. Then both drivers would + detect the device. The driver which returns a higher value + in the probe routine takes precedence (in other words, the + driver returning 0 has highest precedence, one returning -1 + is next, one returning -2 is after it and so on). In result + the devices which support only the old interface will be + handled by the old driver (which should return -1 from the + probe routine) while the devices supporting the new + interface as well will be handled by the new driver (which + should return 0 from the probe routine). + + + + The device descriptor struct xxx_softc is allocated by the + system before calling the probe routine. If the probe + routine returns an error the descriptor will be + automatically deallocated by the system. So if a probing + error occurs the driver must make sure that all the + resources it used during probe are deallocated and that + nothing keeps the descriptor from being safely + deallocated. If the probe completes successfully the + descriptor will be preserved by the system and later passed + to the routine xxx_isa_attach(). If a + driver returns a negative value it can't be sure that it + will have the highest priority and its attach routine will + be called. So in this case it also must release all the + resources before returning and if necessary allocate them + again in the attach routine. When + xxx_isa_probe() returns 0 releasing the + resources before returning is also a good idea, a + well-behaved driver should do so. But in case if there is + some problem with releasing the resources the driver is + allowed to keep resources between returning 0 from the probe + routine and execution of the attach routine. + + + + A typical probe routine starts with getting the device + descriptor and unit: + + + struct xxx_softc *sc = device_get_softc(dev); + int unit = device_get_unit(dev); + int pnperror; + int error = 0; + + sc->dev = dev; /* link it back */ + sc->unit = unit; + + + Then check for the PnP devices. The check is carried out by + a table containing the list of PnP IDs supported by this + driver and human-readable descriptions of the device models + corresponding to these IDs. + + + + pnperror=ISA_PNP_PROBE(device_get_parent(dev), dev, + xxx_pnp_ids); if(pnperror == ENXIO) return ENXIO; + + + + The logic of ISA_PNP_PROBE is the following: If this card + (device unit) was not detected as PnP then ENOENT will be + returned. If it was detected as PnP but its detected ID does + not match any of the IDs in the table then ENXIO is + returned. Finally, if it has PnP support and it matches on + of the IDs in the table, 0 is returned and the appropriate + description from the table is set by + device_set_desc(). + + + + If a driver supports only PnP devices then the condition + would look like: + + + if(pnperror != 0) + return pnperror; + + + No special treatment is required for the drivers which don't + support PnP because they pass an empty PnP ID table and will + always get ENXIO if called on a PnP card. + + + + The probe routine normally needs at least some minimal set + of resources, such as I/O port number to find the card and + probe it. Depending on the hardware the driver may be able + to discover the other necessary resources automatically. The + PnP devices have all the resources pre-set by the PnP + subsystem, so the driver does not need to discover them by + itself. + + + + Typically the minimal information required to get access to + the device is the I/O port number. Then some devices allow + to get the rest of information from the device configuration + registers (though not all devices do that). So first we try + to get the port start value: + + + sc->port0 = bus_get_resource_start(dev, + SYS_RES_IOPORT, 0 /*rid*/); if(sc->port0 == 0) return ENXIO; + + + + The base port address is saved in the structure softc for + future use. If it will be used very often then calling the + resource function each time would be prohibitively slow. If + we don't get a port we just return an error. Some device + drivers can instead be clever and try to probe all the + possible ports, like this: + + + + /* table of all possible base I/O port addresses for this device */ + static struct xxx_allports { + u_short port; /* port address */ + short used; /* flag: if this port is already used by some unit */ + } xxx_allports = { + { 0x300, 0 }, + { 0x320, 0 }, + { 0x340, 0 }, + { 0, 0 } /* end of table */ + }; + + ... + int port, i; + ... + + port = bus_get_resource_start(dev, SYS_RES_IOPORT, 0 /*rid*/); + if(port !=0 ) { + for(i=0; xxx_allports[i].port!=0; i++) { + if(xxx_allports[i].used || xxx_allports[i].port != port) + continue; + + /* found it */ + xxx_allports[i].used = 1; + /* do probe on a known port */ + return xxx_really_probe(dev, port); + } + return ENXIO; /* port is unknown or already used */ + } + + /* we get here only if we need to guess the port */ + for(i=0; xxx_allports[i].port!=0; i++) { + if(xxx_allports[i].used) + continue; + + /* mark as used - even if we find nothing at this port + * at least we won't probe it in future + */ + xxx_allports[i].used = 1; + + error = xxx_really_probe(dev, xxx_allports[i].port); + if(error == 0) /* found a device at that port */ + return 0; + } + /* probed all possible addresses, none worked */ + return ENXIO; + + + Of course, normally the driver's + identify() routine should be used for + such things. But there may be one valid reason why it may be + better to be done in probe(): if this + probe would drive some other sensitive device crazy. The + probe routines are ordered with consideration of the + "sensitive" flag: the sensitive devices get probed first and + the rest of devices later. But the + identify() routines are called before + any probes, so they show no respect to the sensitive devices + and may upset them. + + + + Now, after we got the starting port we need to set the port + count (except for PnP devices) because the kernel does not + have this information in the configuration file. + + + + if(pnperror /* only for non-PnP devices */ + && bus_set_resource(dev, SYS_RES_IOPORT, 0, sc->port0, + XXX_PORT_COUNT)<0) + return ENXIO; + + + Finally allocate and activate a piece of port address space + (special values of start and end mean "use those we set by + bus_set_resource()"): + + + + sc->port0_rid = 0; + sc->port0_r = bus_alloc_resource(dev, SYS_RES_IOPORT, + &sc->port0_rid, + /*start*/ 0, /*end*/ ~0, /*count*/ 0, RF_ACTIVE); + + if(sc->port0_r == NULL) + return ENXIO; + + + Now having access to the port-mapped registers we can poke + the device in some way and check if it reacts like it is + expected to. If it does not then there is probably some + other device or no device at all at this address. + + + + Normally drivers don't set up the interrupt handlers until + the attach routine. Instead they do probes in the polling + mode using the DELAY() function for + timeout. The probe routine must never hang forever, all the + waits for the device must be done with timeouts. If the + device does not respond within the time it's probably broken + or misconfigured and the driver must return error. When + determining the timeout interval give the device some extra + time to be on the safe side: although + DELAY() is supposed to delay for the + same amount of time on any machine it has some margin of + error, depending on the exact CPU. + + + + If the probe routine really wants to check that the + interrupts really work it may configure and probe the + interrupts too. But that's not recommended. + + + + /* implemented in some very device-specific way */ + if(error = xxx_probe_ports(sc)) + goto bad; /* will deallocate the resources before returning */ + + + + The fucntion xxx_probe_ports() may also + set the device description depending on the exact model of + device it discovers. But if there is only one supported + device model this can be as well done in a hardcoded way. + Of course, for the PnP devices the PnP support sets the + description from the table automatically. + + + + if(pnperror) + device_set_desc(dev, "Our device model 1234"); + + + + Then the probe routine should either discover the ranges of + all the resources by reading the device configuration + registers or make sure that they were set explicitly by the + user. We will consider it with an example of on-board + memory. The probe routine should be as non-intrusive as + possible, so allocation and check of functionality of the + rest of resources (besides the ports) would be better left + to the attach routine. + + + + The memory address may be specified in the kernel + configuration file or on some devices it may be + pre-configured in non-volatile configuration registers. If + both sources are available and different, which one should + be used? Probably if the user bothered to set the address + explicitly in the kernel configuration file they know what + they're doing and this one should take precedence. An + example of implementation could be: + + + /* try to find out the config address first */ + sc->mem0_p = bus_get_resource_start(dev, SYS_RES_MEMORY, 0 /*rid*/); + if(sc->mem0_p == 0) { /* nope, not specified by user */ + sc->mem0_p = xxx_read_mem0_from_device_config(sc); + + + if(sc->mem0_p == 0) + /* can't get it from device config registers either */ + goto bad; + } else { + if(xxx_set_mem0_address_on_device(sc) < 0) + goto bad; /* device does not support that address */ + } + + /* just like the port, set the memory size, + * for some devices the memory size would not be constant + * but should be read from the device configuration registers instead + * to accommodate different models of devices. Another option would + * be to let the user set the memory size as "msize" configuration + * resource which will be automatically handled by the ISA bus. + */ + if(pnperror) { /* only for non-PnP devices */ + sc->mem0_size = bus_get_resource_count(dev, SYS_RES_MEMORY, 0 /*rid*/); + if(sc->mem0_size == 0) /* not specified by user */ + sc->mem0_size = xxx_read_mem0_size_from_device_config(sc); + + if(sc->mem0_size == 0) { + /* suppose this is a very old model of device without + * auto-configuration features and the user gave no preference, + * so assume the minimalistic case + * (of course, the real value will vary with the driver) + */ + sc->mem0_size = 8*1024; + } + + if(xxx_set_mem0_size_on_device(sc) < 0) + goto bad; /* device does not support that size */ + + if(bus_set_resource(dev, SYS_RES_MEMORY, /*rid*/0, + sc->mem0_p, sc->mem0_size)<0) + goto bad; + } else { + sc->mem0_size = bus_get_resource_count(dev, SYS_RES_MEMORY, 0 /*rid*/); + } + + + Resources for IRQ and DRQ are easy to check by analogy. + + + + If all went well then release all the resources and return success. + + + xxx_free_resources(sc); + return 0; + + + Finally, handle the troublesome situations. All the + resources should be deallocated before returning. We make + use of the fact that before the structure softc is passed to + us it gets zeroed out, so we can find out if some resource + was allocated: then its descriptor is non-zero. + + + bad: + + xxx_free_resources(sc); + if(error) + return error; + else /* exact error is unknown */ + return ENXIO; + + + That would be all for the probe routine. Freeing of + resources is done from multiple places, so it's moved to a + function which may look like: + + +static void + xxx_free_resources(sc) + struct xxx_softc *sc; + { + /* check every resource and free if not zero */ + + /* interrupt handler */ + if(sc->intr_r) { + bus_teardown_intr(sc->dev, sc->intr_r, sc->intr_cookie); + bus_release_resource(sc->dev, SYS_RES_IRQ, sc->intr_rid, + sc->intr_r); + sc->intr_r = 0; + } + + /* all kinds of memory maps we could have allocated */ + if(sc->data_p) { + bus_dmamap_unload(sc->data_tag, sc->data_map); + sc->data_p = 0; + } + if(sc->data) { /* sc->data_map may be legitimately equal to 0 */ + /* the map will also be freed */ + bus_dmamem_free(sc->data_tag, sc->data, sc->data_map); + sc->data = 0; + } + if(sc->data_tag) { + bus_dma_tag_destroy(sc->data_tag); + sc->data_tag = 0; + } + + ... free other maps and tags if we have them ... + + if(sc->parent_tag) { + bus_dma_tag_destroy(sc->parent_tag); + sc->parent_tag = 0; + } + + /* release all the bus resources */ + if(sc->mem0_r) { + bus_release_resource(sc->dev, SYS_RES_MEMORY, sc->mem0_rid, + sc->mem0_r); + sc->mem0_r = 0; + } + ... + if(sc->port0_r) { + bus_release_resource(sc->dev, SYS_RES_IOPORT, sc->port0_rid, + sc->port0_r); + sc->port0_r = 0; + } + } + + + + + xxx_isa_attach + + + The attach routine actually connects the driver to the + system if the probe routine returned success and the system + had chosen to attach that driver. If the probe routine + returned 0 then the attach routine may expect to receive the + device structure softc intact, as it was set by the probe + routine. Also if the probe routine returns 0 it may expect + that the attach routine for this device shall be called at + some point in the future. If the probe routine returns a + negative value then the driver may make none of these + assumptions. + + + The attach routine returns 0 if it completed successfully or + error code otherwise. + + + The attach routine starts just like the probe routine, + with getting some frequently used data into more accessible + variables. + + + struct xxx_softc *sc = device_get_softc(dev); + int unit = device_get_unit(dev); + int error = 0; + + Then allocate and activate all the necessary + resources. Because normally the port range will be released + before returning from probe, it has to be allocated + again. We expect that the probe routine had properly set all + the resource ranges, as well as saved them in the structure + softc. If the probe routine had left some resource allocated + then it does not need to be allocated again (which would be + considered an error). + + + sc->port0_rid = 0; + sc->port0_r = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->port0_rid, + /*start*/ 0, /*end*/ ~0, /*count*/ 0, RF_ACTIVE); + + if(sc->port0_r == NULL) + return ENXIO; + + /* on-board memory */ + sc->mem0_rid = 0; + sc->mem0_r = bus_alloc_resource(dev, SYS_RES_MEMORY, &sc->mem0_rid, + /*start*/ 0, /*end*/ ~0, /*count*/ 0, RF_ACTIVE); + + if(sc->mem0_r == NULL) + goto bad; + + /* get its virtual address */ + sc->mem0_v = rman_get_virtual(sc->mem0_r); + + The DMA request channel (DRQ) is allocated likewise. To + initialize it use functions of the + isa_dma*() family. For example: + + + isa_dmacascade(sc->drq0); + + The interrupt request line (IRQ) is a bit + special. Besides allocation the driver's interrupt handler + should be associated with it. Historically in the old ISA + drivers the argument passed by the system to the interrupt + handler was the device unit number. But in modern drivers + the convention suggests passing the pointer to structure + softc. The important reason is that when the structures + softc are allocated dynamically then getting the unit number + from softc is easy while getting softc from unit number is + difficult. Also this convention makes the drivers for + different buses look more uniform and allows them to share + the code: each bus gets its own probe, attach, detach and + other bus-specific routines while the bulk of the driver + code may be shared among them. + + + + sc->intr_rid = 0; + sc->intr_r = bus_alloc_resource(dev, SYS_RES_MEMORY, &sc->intr_rid, + /*start*/ 0, /*end*/ ~0, /*count*/ 0, RF_ACTIVE); + + if(sc->intr_r == NULL) + goto bad; + + /* + * XXX_INTR_TYPE is supposed to be defined depending on the type of + * the driver, for example as INTR_TYPE_CAM for a CAM driver + */ + error = bus_setup_intr(dev, sc->intr_r, XXX_INTR_TYPE, + (driver_intr_t *) xxx_intr, (void *) sc, &sc->intr_cookie); + if(error) + goto bad; + + + + + If the device needs to make DMA to the main memory then + this memory should be allocated like described before: + + + error=bus_dma_tag_create(NULL, /*alignment*/ 4, + /*boundary*/ 0, /*lowaddr*/ BUS_SPACE_MAXADDR_24BIT, + /*highaddr*/ BUS_SPACE_MAXADDR, /*filter*/ NULL, /*filterarg*/ NULL, + /*maxsize*/ BUS_SPACE_MAXSIZE_24BIT, + /*nsegments*/ BUS_SPACE_UNRESTRICTED, + /*maxsegsz*/ BUS_SPACE_MAXSIZE_24BIT, /*flags*/ 0, + &sc->parent_tag); + if(error) + goto bad; + + /* many things get inherited from the parent tag + * sc->data is supposed to point to the structure with the shared data, + * for example for a ring buffer it could be: + * struct { + * u_short rd_pos; + * u_short wr_pos; + * char bf[XXX_RING_BUFFER_SIZE] + * } *data; + */ + error=bus_dma_tag_create(sc->parent_tag, 1, + 0, BUS_SPACE_MAXADDR, 0, /*filter*/ NULL, /*filterarg*/ NULL, + /*maxsize*/ sizeof(* sc->data), /*nsegments*/ 1, + /*maxsegsz*/ sizeof(* sc->data), /*flags*/ 0, + &sc->data_tag); + if(error) + goto bad; + + error = bus_dmamem_alloc(sc->data_tag, &sc->data, /* flags*/ 0, + &sc->data_map); + if(error) + goto bad; + + /* xxx_alloc_callback() just saves the physical address at + * the pointer passed as its argument, in this case &sc->data_p. + * See details in the section on bus memory mapping. + * It can be implemented like: + * + * static void + * xxx_alloc_callback(void *arg, bus_dma_segment_t *seg, + * int nseg, int error) + * { + * *(bus_addr_t *)arg = seg[0].ds_addr; + * } + */ + bus_dmamap_load(sc->data_tag, sc->data_map, (void *)sc->data, + sizeof (* sc->data), xxx_alloc_callback, (void *) &sc->data_p, + /*flags*/0); + + + After all the necessary resources are allocated the + device should be initialized. The initialization may include + testing that all the expected features are functional. + + if(xxx_initialize(sc) < 0) + goto bad; + + + The bus subsystem will automatically print on the + console the device description set by probe. But if the + driver wants to print some extra information about the + device it may do so, for example: + + + device_printf(dev, "has on-card FIFO buffer of %d bytes\n", sc->fifosize); + + + If the initialization routine experiences any problems + then printing messages about them before returning error is + also recommended. + + The final step of the attach routine is attaching the + device to its functional subsystem in the kernel. The exact + way to do it depends on the type of the driver: a character + device, a block device, a network device, a CAM SCSI bus + device and so on. + + If all went well then return success. + + error = xxx_attach_subsystem(sc); + if(error) + goto bad; + + return 0; + + Finally, handle the troublesome situations. All the + resources should be deallocated before returning an + error. We make use of the fact that before the structure + softc is passed to us it gets zeroed out, so we can find out + if some resource was allocated: then its descriptor is + non-zero. + + bad: + + xxx_free_resources(sc); + if(error) + return error; + else /* exact error is unknown */ + return ENXIO; + + That would be all for the attach routine. + + + + + + xxx_isa_detach + + + If this function is present in the driver and the driver is + compiled as a loadable module then the driver gets the + ability to be unloaded. This is an important feature if the + hardware supports hot plug. But the ISA bus does not support + hot plug, so this feature is not particularly important for + the ISA devices. The ability to unload a driver may be + useful when debugging it, but in many cases installation of + the new version of the driver would be required only after + the old version somehow wedges the system and reboot will be + needed anyway, so the efforts spent on writing the detach + routine may not be worth it. Another argument is that + unloading would allow upgrading the drivers on a production + machine seems to be mostly theoretical. Installing a new + version of a driver is a dangerous operation which should + never be performed on a production machine (and which is not + permitted when the system is running in secure mode). Still + the detach routine may be provided for the sake of + completeness. + + + + The detach routine returns 0 if the driver was successfully + detached or the error code otherwise. + + + + The logic of detach is a mirror of the attach. The first + thing to do is to detach the driver from its kernel + subsystem. If the device is currently open then the driver + has two choices: refuse to be detached or forcibly close and + proceed with detach. The choice used depends on the ability + of the particular kernel subsystem to do a forced close and + on the preferences of the driver's author. Generally the + forced close seems to be the preferred alternative. + struct xxx_softc *sc = device_get_softc(dev); + int error; + + error = xxx_detach_subsystem(sc); + if(error) + return error; + + + Next the driver may want to reset the hardware to some + consistent state. That includes stopping any ongoing + transfers, disabling the DMA channels and interrupts to + avoid memory corruption by the device. For most of the + drivers this is exactly what the shutdown routine does, so + if it is included in the driver we can as well just call it. + + xxx_isa_shutdown(dev); + + + And finally release all the resources and return success. + xxx_free_resources(sc); + return 0; + + + + + + xxx_isa_shutdown + + + This routine is called when the system is about to be shut + down. It is expected to bring the hardware to some + consistent state. For most of the ISA devices no special + action is required, so the function is not really necessary + because the device will be re-initialized on reboot + anyway. But some devices have to be shut down with a special + procedure, to make sure that they will be properly detected + after soft reboot (this is especially true for many devices + with proprietary identification protocols). In any case + disabling DMA and interrupts in the device registers and + stopping any ongoing transfers is a good idea. The exact + action depends on the hardware, so we don't consider it here + in any details. + + + + xxx_intr + + + + The interrupt handler is called when an interrupt is + received which may be from this particular device. The ISA + bus does not support interrupt sharing (except some special + cases) so in practice if the interrupt handler is called + then the interrupt almost for sure came from its + device. Still the interrupt handler must poll the device + registers and make sure that the interrupt was generated by + its device. If not it should just return. + + + + The old convention for the ISA drivers was getting the + device unit number as an argument. It is obsolete, and the + new drivers receive whatever argument was specified for them + in the attach routine when calling + bus_setup_intr(). By the new convention + it should be the pointer to the structure softc. So the + interrupt handler commonly starts as: + + + + static void + xxx_intr(struct xxx_softc *sc) + { + + + + + It runs at the interrupt priority level specified by the + interrupt type parameter of + bus_setup_intr(). That means that all + the other interrupts of the same type as well as all the + software interrupts are disabled. + + + + To avoid races it is commonly written as a loop: + + + + while(xxx_interrupt_pending(sc)) { + xxx_process_interrupt(sc); + xxx_acknowledge_interrupt(sc); + } + + + The interrupt handler has to acknowledge interrupt to the + device only but not to the interrupt controller, the system + takes care of the latter. + + + + diff --git a/en_US.ISO_8859-1/books/developers-handbook/isa/chapter.sgml b/en_US.ISO_8859-1/books/developers-handbook/isa/chapter.sgml new file mode 100644 index 0000000000..c68a55d85c --- /dev/null +++ b/en_US.ISO_8859-1/books/developers-handbook/isa/chapter.sgml @@ -0,0 +1,2479 @@ + + + + ISA device drivers + + + + This chapter was written by &a.babkin; Modifications for the + handbook made by &a.murray;, &a.wylie;, and &a.logo;. + + + + + Synopsis + + This chapter introduces the issues relevant to writing a + driver for an ISA device. The pseudo-code presented here is + rather detailed and reminiscent of the real code but is still + only pseudo-code. It avoids the details irrelevant to the + subject of the discussion. The real-life examples can be found + in the source code of real drivers. In particular the drivers + "ep" and "aha" are good sources of information. + + + + Basic information + + A typical ISA driver would need the following include + files: + +#include <sys/module.h> +#include <sys/bus.h> +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <isa/isavar.h> +#include <isa/pnpvar.h> + + They describe the things specific to the ISA and generic + bus subsystem. + + The bus subsystem is implemented in an object-oriented + fashion, its main structures are accessed by associated method + functions. + + The list of bus methods implemented by an ISA driver is like + one for any other bus. For a hypothetical driver named "xxx" + they would be: + + + + static void xxx_isa_identify (driver_t *, + device_t); Normally used for bus drivers, not + device drivers. But for ISA devices this method may have + special use: if the device provides some device-specific + (non-PnP) way to auto-detect devices this routine may + implement it. + + + + static int xxx_isa_probe (device_t + dev); Probe for a device at a known (or PnP) + location. This routine can also accommodate device-specific + auto-detection of parameters for partially configured + devices. + + + + static int xxx_isa_attach (device_t + dev); Attach and initialize device. + + + + static int xxx_isa_detach (device_t + dev); Detach device before unloading the driver + module. + + + + static int xxx_isa_shutdown (device_t + dev); Execute shutdown of the device before + system shutdown. + + + + static int xxx_isa_suspend (device_t + dev); Suspend the device before the system goes + to the power-save state. May also abort transition to the + power-save state. + + + + static int xxx_isa_resume (device_t + dev); Resume the device activity after return + from power-save state. + + + + + xxx_isa_probe() and + xxx_isa_attach() are mandatory, the rest of + the routines are optional, depending on the device's + needs. + + The driver is linked to the system with the following set of + descriptions. + + /* table of supported bus methods */ + static device_method_t xxx_isa_methods[] = { + /* list all the bus method functions supported by the driver */ + /* omit the unsupported methods */ + DEVMETHOD(device_identify, xxx_isa_identify), + DEVMETHOD(device_probe, xxx_isa_probe), + DEVMETHOD(device_attach, xxx_isa_attach), + DEVMETHOD(device_detach, xxx_isa_detach), + DEVMETHOD(device_shutdown, xxx_isa_shutdown), + DEVMETHOD(device_suspend, xxx_isa_suspend), + DEVMETHOD(device_resume, xxx_isa_resume), + + { 0, 0 } + }; + + static driver_t xxx_isa_driver = { + "xxx", + xxx_isa_methods, + sizeof(struct xxx_softc), + }; + + + static devclass_t xxx_devclass; + + DRIVER_MODULE(xxx, isa, xxx_isa_driver, xxx_devclass, + load_function, load_argument); + + Here struct xxx_softc is a + device-specific structure that contains private driver data + and descriptors for the driver's resources. The bus code + automatically allocates one softc descriptor per device as + needed. + + If the driver is implemented as a loadable module then + load_function() is called to do + driver-specific initialization or clean-up when the driver is + loaded or unloaded and load_argument is passed as one of its + arguments. If the driver does not support dynamic loading (in + other words it must always be linked into kernel) then these + values should be set to 0 and the last definition would look + like: + + DRIVER_MODULE(xxx, isa, xxx_isa_driver, + xxx_devclass, 0, 0); + + If the driver is for a device which supports PnP then a + table of supported PnP IDs must be defined. The table + consists of a list of PnP IDs supported by this driver and + human-readable descriptions of the hardware types and models + having these IDs. It looks like: + + static struct isa_pnp_id xxx_pnp_ids[] = { + /* a line for each supported PnP ID */ + { 0x12345678, "Our device model 1234A" }, + { 0x12345679, "Our device model 1234B" }, + { 0, NULL }, /* end of table */ + }; + + If the driver does not support PnP devices it still needs + an empty PnP ID table, like: + + static struct isa_pnp_id xxx_pnp_ids[] = { + { 0, NULL }, /* end of table */ + }; + + + + + Device_t pointer + + Device_t is the pointer type for + the device structure. Here we consider only the methods + interesting from the device driver writer's standpoint. The + methods to manipulate values in the device structure + are: + + + + device_t + device_get_parent(dev) Get the parent bus of a + device. + + driver_t + device_get_driver(dev) Get pointer to its driver + structure. + + char + *device_get_name(dev) Get the driver name, such + as "xxx" for our example. + + int device_get_unit(dev) + Get the unit number (units are numbered from 0 for the + devices associated with each driver). + + char + *device_get_nameunit(dev) Get the device name + including the unit number, such as "xxx0" , "xxx1" and so + on. + + char + *device_get_desc(dev) Get the device + description. Normally it describes the exact model of device + in human-readable form. + + device_set_desc(dev, + desc) Set the description. This makes the device + description point to the string desc which may not be + deallocated or changed after that. + + device_set_desc_copy(dev, + desc) Set the description. The description is + copied into an internal dynamically allocated buffer, so the + string desc may be changed afterwards without adverse + effects. + + void + *device_get_softc(dev) Get pointer to the device + descriptor (struct xxx_softc) + associated with this device. + + u_int32_t + device_get_flags(dev) Get the flags specified for + the device in the configuration file. + + + + A convenience function device_printf(dev, fmt, + ...) may be used to print the messages from the + device driver. It automatically prepends the unitname and + colon to the message. + + The device_t methods are implemented in the file + kern/bus_subr.c. + + + + + Config file and the order of identifying and probing + during auto-configuration + + The ISA devices are described in the kernel config file + like: + + device xxx0 at isa? port 0x300 irq 10 drq 5 + iomem 0xd0000 flags 0x1 sensitive + + The values of port, IRQ and so on are converted to the + resource values associated with the device. They are optional, + depending on the device needs and abilities for + auto-configuration. For example, some devices don't need DRQ + at all and some allow the driver to read the IRQ setting from + the device configuration ports. If a machine has multiple ISA + buses the exact bus may be specified in the configuration + line, like "isa0" or "isa1", otherwise the device would be + searched for on all the ISA buses. + + "sensitive" is a resource requesting that this device must + be probed before all non-sensitive devices. It is supported + but does not seem to be used in any current driver. + + For legacy ISA devices in many cases the drivers are still + able to detect the configuration parameters. But each device + to be configured in the system must have a config line. If two + devices of some type are installed in the system but there is + only one configuration line for the corresponding driver, ie: + device xxx0 at isa? then only + one device will be configured. + + But for the devices supporting automatic identification by + the means of Plug-n-Play or some proprietary protocol one + configuration line is enough to configure all the devices in + the system, like the one above or just simply: + + device xxx at isa? + + If a driver supports both auto-identified and legacy + devices and both kinds are installed at once in one machine + then it's enough to describe in the config file the legacy + devices only. The auto-identified devices will be added + automatically. + + When an ISA bus is auto-configured the events happen as + follows: + + All the drivers' identify routines (including the PnP + identify routine which identifies all the PnP devices) are + called in random order. As they identify the devices they add + them to the list on the ISA bus. Normally the drivers' + identify routines associate their drivers with the new + devices. The PnP identify routine does not know about the + other drivers yet so it does not associate any with the new + devices it adds. + + The PnP devices are put to sleep using the PnP protocol to + prevent them from being probed as legacy devices. + + The probe routines of non-PnP devices marked as + "sensitive" are called. If probe for a device went + successfully, the attach routine is called for it. + + The probe and attach routines of all non-PNP devices are + called likewise. + + The PnP devices are brought back from the sleep state and + assigned the resources they request: I/O and memory address + ranges, IRQs and DRQs, all of them not conflicting with the + attached legacy devices. + + Then for each PnP device the probe routines of all the + present ISA drivers are called. The first one that claims the + device gets attached. It is possible that multiple drivers + would claim the device with different priority, the + highest-priority driver wins. The probe routines must call + ISA_PNP_PROBE() to compare the actual PnP + ID with the list of the IDs supported by the driver and if the + ID is not in the table return failure. That means that + absolutely every driver, even the ones not supporting any PnP + devices must call ISA_PNP_PROBE(), at + least with an empty PnP ID table to return failure on unknown + PnP devices. + + The probe routine returns a positive value (the error + code) on error, zero or negative value on success. + + The negative return values are used when a PnP device + supports multiple interfaces. For example, an older + compatibility interface and a newer advanced interface which + are supported by different drivers. Then both drivers would + detect the device. The driver which returns a higher value in + the probe routine takes precedence (in other words, the driver + returning 0 has highest precedence, returning -1 is next, + returning -2 is after it and so on). In result the devices + which support only the old interface will be handled by the + old driver (which should return -1 from the probe routine) + while the devices supporting the new interface as well will be + handled by the new driver (which should return 0 from the + probe routine). If multiple drivers return the same value then + the one called first wins. So if a driver returns value 0 it + may be sure that it won the priority arbitration. + + The device-specific identify routines can also assign not + a driver but a class of drivers to the device. Then all the + drivers in the class are probed for this device, like the case + with PnP. This feature is not implemented in any existing + driver and is not considered further in this document. + + Because the PnP devices are disabled when probing the + legacy devices they will not be attached twice (once as legacy + and once as PnP). But in case of device-dependent identify + routines it's the responsibility of the driver to make sure + that the same device won't be attached by the driver twice: + once as legacy user-configured and once as + auto-identified. + + Another practical consequence for the auto-identified + devices (both PnP and device-specific) is that the flags can + not be passed to them from the kernel configuration file. So + they must either not use the flags at all or use the flags + from the device unit 0 for all the auto-identified devices or + use the sysctl interface instead of flags. + + Other unusual configurations may be accommodated by + accessing the configuration resources directly with functions + of families resource_query_*() and + resource_*_value(). Their implementations + are located in kern/subr_bus.h. The old IDE disk driver + i386/isa/wd.c contains examples of such use. But the standard + means of configuration must always be preferred. Leave parsing + the configuration resources to the bus configuration + code. + + + + + Resources + + The information that a user enters into the kernel + configuration file is processed and passed to the kernel as + configuration resources. This information is parsed by the bus + configuration code and transformed into a value of structure + device_t and the bus resources associated with it. The drivers + may access the configuration resources directly using + functions resource_* for more complex cases of + configuration. But generally it's not needed nor recommended, + so this issue is not discussed further. + + The bus resources are associated with each device. They + are identified by type and number within the type. For the ISA + bus the following types are defined: + + + + SYS_RES_IRQ - interrupt + number + + + + SYS_RES_DRQ - ISA DMA channel + number + + + + SYS_RES_MEMORY - range of + device memory mapped into the system memory space + + + + + SYS_RES_IOPORT - range of + device I/O registers + + + + The enumeration within types starts from 0, so if a device + has two memory regions if would have resources of type + SYS_RES_MEMORY numbered 0 and 1. The resource type has + nothing to do with the C language type, all the resource + values have the C language type "unsigned long" and must be + cast as necessary. The resource numbers don't have to be + contiguous although for ISA they normally would be. The + permitted resource numbers for ISA devices are: + + IRQ: 0-1 + DRQ: 0-1 + MEMORY: 0-3 + IOPORT: 0-7 + + All the resources are represented as ranges, with a start + value and count. For IRQ and DRQ resources the count would be + normally equal to 1. The values for memory refer to the + physical addresses. + + Three types of activities can be performed on + resources: + + + set/get + allocate/release + activate/deactivate + + + Setting sets the range used by the resource. Allocation + reserves the requested range that no other driver would be + able to reserve it (and checking that no other driver reserved + this range already). Activation makes the resource accessible + to the driver doing whatever is necessary for that (for + example, for memory it would be mapping into the kernel + virtual address space). + + The functions to manipulate resources are: + + + + int bus_set_resource(device_t dev, int type, + int rid, u_long start, u_long count) + + Set a range for a resource. Returns 0 if successful, + error code otherwise. Normally the only reason this + function would return an error is value of type, rid, + start or count out of permitted range. + + + + dev - driver's device + + + type - type of resource, SYS_RES_* + + + rid - resource number (ID) within type + + + start, count - resource range + + + + + + int bus_get_resource(device_t dev, int type, + int rid, u_long *startp, u_long *countp) + + Get the range of resource. Returns 0 if successful, + error code if the resource is not defined yet. + + + + u_long bus_get_resource_start(device_t dev, + int type, int rid) u_long bus_get_resource_count (device_t + dev, int type, int rid) + + Convenience functions to get only the start or + count. Return 0 in case of error, so if the resource start + has 0 among the legitimate values it would be impossible + to tell if the value is 0 or an error occurred. Luckily, + no ISA resources for add-on drivers may have a start value + equal 0. + + + + void bus_delete_resource(device_t dev, int + type, int rid) + Delete a resource, make it undefined. + + + + struct resource * + bus_alloc_resource(device_t dev, int type, int *rid, + u_long start, u_long end, u_long count, u_int + flags) + + Allocate a resource as a range of count values not + allocated by anyone else, somewhere between start and + end. Alas, alignment is not supported. If the resource + was not set yet it's automatically created. The special + values of start 0 and end ~0 (all ones) means that the + fixed values previously set by + bus_set_resource() must be used + instead: start and count as themselves and + end=(start+count), in this case if the resource was not + defined before then an error is returned. Although rid is + passed by reference it's not set anywhere by the resource + allocation code of the ISA bus. (The other buses may use a + different approach and modify it). + + + + Flags are a bitmap, the flags interesting for the caller + are: + + + + RF_ACTIVE - causes the resource + to be automatically activated after allocation. + + + + RF_SHAREABLE - resource may be + shared at the same time by multiple drivers. + + + + RF_TIMESHARE - resource may be + time-shared by multiple drivers, i.e. allocated at the + same time by many but activated only by one at any given + moment of time. + + + + Returns 0 on error. The allocated values may be + obtained from the returned handle using methods + rhand_*(). + + + int bus_release_resource(device_t dev, int + type, int rid, struct resource *r) + + + + Release the resource, r is the handle returned by + bus_alloc_resource(). Returns 0 on + success, error code otherwise. + + + + int bus_activate_resource(device_t dev, int + type, int rid, struct resource *r) + int bus_deactivate_resource(device_t dev, int + type, int rid, struct resource *r) + + + + Activate or deactivate resource. Return 0 on success, + error code otherwise. If the resource is time-shared and + currently activated by another driver then EBUSY is + returned. + + + + int bus_setup_intr(device_t dev, struct + resource *r, int flags, driver_intr_t *handler, void *arg, + void **cookiep) int + bus_teardown_intr(device_t dev, struct resource *r, void + *cookie) + + + + Associate or de-associate the interrupt handler with a + device. Return 0 on success, error code otherwise. + + + + r - the activated resource handler describing the + IRQ + flags - the interrupt priority level, one of: + + + + INTR_TYPE_TTY - terminals and + other likewise character-type devices. To mask them + use spltty(). + + + (INTR_TYPE_TTY | + INTR_TYPE_FAST) - terminal type devices + with small input buffer, critical to the data loss on + input (such as the old-fashioned serial ports). To + mask them use spltty(). + + + INTR_TYPE_BIO - block-type + devices, except those on the CAM controllers. To mask + them use splbio(). + + + INTR_TYPE_CAM - CAM (Common + Access Method) bus controllers. To mask them use + splcam(). + + + INTR_TYPE_NET - network + interface controllers. To mask them use + splimp(). + + + INTR_TYPE_MISC - + miscellaneous devices. There is no other way to mask + them than by splhigh() which + masks all interrupts. + + + + + + When an interrupt handler executes all the other + interrupts matching its priority level will be masked. The + only exception is the MISC level for which no other interrupts + are masked and which is not masked by any other + interrupt. + + + + handler - pointer to the handler + function, the type driver_intr_t is defined as "void + driver_intr_t(void *)" + + + arg - the argument passed to the + handler to identify this particular device. It is cast + from void* to any real type by the handler. The old + convention for the ISA interrupt handlers was to use the + unit number as argument, the new (recommended) convention + is using a pointer to the device softc structure. + + + cookie[p] - the value received + from setup() is used to identify the + handler when passed to + teardown() + + + + A number of methods is defined to operate on the resource + handlers (struct resource *). Those of interest to the device + driver writers are: + + + + u_long rman_get_start(r) u_long + rman_get_end(r) Get the start and end of + allocated resource range. + + + void *rman_get_virtual(r) Get + the virtual address of activated memory resource. + + + + + + + Bus memory mapping + + In many cases data is exchanged between the driver and the + device through the memory. Two variants are possible: + + (a) memory is located on the device card + (b) memory is the main memory of computer + + In the case (a) the driver always copies the data back and + forth between the on-card memory and the main memory as + necessary. To map the on-card memory into the kernel virtual + address space the physical address and length of the on-card + memory must be defined as a SYS_RES_MEMORY resource. That + resource can then be allocated and activated, and its virtual + address obtained using + rman_get_virtual(). The older drivers + used the function pmap_mapdev() for this + purpose, which should not be used directly any more. Now it's + one of the internal steps of resource activation. + + Most of the ISA cards will have their memory configured + for physical location somewhere in range 640KB-1MB. Some of + the ISA cards require larger memory ranges which should be + placed somewhere under 16MB (because of the 24-bit address + limitation on the ISA bus). In that case if the machine has + more memory than the start address of the device memory (in + other words, they overlap) a memory hole must be configured at + the address range used by devices. Many BIOSes allow to + configure a memory hole of 1MB starting at 14MB or + 15MB. FreeBSD can handle the memory holes properly if the BIOS + reports them properly (old BIOSes may have this feature + broken). + + In the case (b) just the address of the data is sent to + the device, and the device uses DMA to actually access the + data in the main memory. Two limitations are present: First, + ISA cards can only access memory below 16MB. Second, the + contiguous pages in virtual address space may not be + contiguous in physical address space, so the device may have + to do scatter/gather operations. The bus subsystem provides + ready solutions for some of these problems, the rest has to be + done by the drivers themselves. + + Two structures are used for DMA memory allocation, + bus_dma_tag_t and bus_dmamap_t. Tag describes the properties + required for the DMA memory. Map represents a memory block + allocated according to these properties. Multiple maps may be + associated with the same tag. + + Tags are organized into a tree-like hierarchy with + inheritance of the properties. A child tag inherits all the + requirements of its parent tag or may make them more strict + but never more loose. + + Normally one top-level tag (with no parent) is created for + each device unit. If multiple memory areas with different + requirements are needed for each device then a tag for each of + them may be created as a child of the parent tag. + + The tags can be used to create a map in two ways. + + First, a chunk of contiguous memory conformant with the + tag requirements may be allocated (and later may be + freed). This is normally used to allocate relatively + long-living areas of memory for communication with the + device. Loading of such memory into a map is trivial: it's + always considered as one chunk in the appropriate physical + memory range. + + Second, an arbitrary area of virtual memory may be loaded + into a map. Each page of this memory will be checked for + conformance to the map requirement. If it conforms then it's + left at it's original location. If it is not then a fresh + conformant "bounce page" is allocated and used as intermediate + storage. When writing the data from the non-conformant + original pages they will be copied to their bounce pages first + and then transferred from the bounce pages to the device. When + reading the data would go from the device to the bounce pages + and then copied to their non-conformant original pages. The + process of copying between the original and bounce pages is + called synchronization. This is normally used on per-transfer + basis: buffer for each transfer would be loaded, transfer done + and buffer unloaded. + + The functions working on the DMA memory are: + + + + int bus_dma_tag_create(bus_dma_tag_t parent, + bus_size_t alignment, bus_size_t boundary, bus_addr_t + lowaddr, bus_addr_t highaddr, bus_dma_filter_t *filter, void + *filterarg, bus_size_t maxsize, int nsegments, bus_size_t + maxsegsz, int flags, bus_dma_tag_t *dmat) + + Create a new tag. Returns 0 on success, the error code + otherwise. + + + + parent - parent tag, or NULL to + create a top-level tag alignment - + required physical alignment of the memory area to be + allocated for this tag. Use value 1 for "no specific + alignment". Applies only to the future + bus_dmamem_alloc() but not + bus_dmamap_create() calls. + boundary - physical address + boundary that must not be crossed when allocating the + memory. Use value 0 for "no boundary". Applies only to + the future bus_dmamem_alloc() but + not bus_dmamap_create() calls. + Must be power of 2. If the memory is planned to be used + in non-cascaded DMA mode (i.e. the DMA addresses will be + supplied not by the device itself but by the ISA DMA + controller) then the boundary must be no larger than + 64KB (64*1024) due to the limitations of the DMA + hardware. + + + + lowaddr, highaddr - the names + are slighlty misleading; these values are used to limit + the permitted range of physical addresses used to + allocate the memory. The exact meaning varies depending + on the planned future use: + + + + For bus_dmamem_alloc() all + the addresses from 0 to lowaddr-1 are considered + permitted, the higher ones are forbidden. + + + + For bus_dmamap_create() all + the addresses outside the inclusive range [lowaddr; + highaddr] are considered accessible. The addresses + of pages inside the range are passed to the filter + function which decides if they are accessible. If no + filter function is supplied then all the range is + considered unaccessible. + + + + For the ISA devices the normal values (with no + filter function) are: + lowaddr = BUS_SPACE_MAXADDR_24BIT + highaddr = BUS_SPACE_MAXADDR + + + + + + + filter, filterarg - the filter + function and its argument. If NULL is passed for filter + then the whole range [lowaddr, highaddr] is considered + unaccessible when doing + bus_dmamap_create(). Otherwise the + physical address of each attempted page in range + [lowaddr; highaddr] is passed to the filter function + which decides if it is accessible. The prototype of the + filter function is: int filterfunc(void *arg, + bus_addr_t paddr) It must return 0 if the + page is accessible, non-zero otherwise. + + + + maxsize - the maximal size of + memory (in bytes) that may be allocated through this + tag. In case it's difficult to estimate or could be + arbitrarily big, the value for ISA devices would be + BUS_SPACE_MAXSIZE_24BIT. + + + + nsegments - maximal number of + scatter-gather segments supported by the device. If + unrestricted then the value BUS_SPACE_UNRESTRICTED + should be used. This value is recommended for the parent + tags, the actual restrictions would then be specified + for the descendant tags. Tags with nsegments equal to + BUS_SPACE_UNRESTRICTED may not be used to actually load + maps, they may be used only as parent tags. The + practical limit for nsegments seems to be about 250-300, + higher values will cause kernel stack overflow. But + anyway the hardware normally can't support that many + scatter-gather buffers. + + + + maxsegsz - maximal size of a + scatter-gather segment supported by the device. The + maximal value for ISA device would be + BUS_SPACE_MAXSIZE_24BIT. + + + + flags - a bitmap of flags. The + only interesting flags are: + + + + BUS_DMA_ALLOCNOW - requests + to allocate all the potentially needed bounce pages + when creating the tag + + + + BUS_DMA_ISA - mysterious + flag used only on Alpha machines. It is not defined + for the i386 machines. Probably it should be used + by all the ISA drivers for Alpha machines but it + looks like there are no such drivers yet. + + + + + + dmat - pointer to the storage + for the new tag to be returned + + + + + + + + int bus_dma_tag_destroy(bus_dma_tag_t + dmat) + + Destroy a tag. Returns 0 on success, the error code + otherwise. + + dmat - the tag to be destroyed + + + + + int bus_dmamem_alloc(bus_dma_tag_t dmat, + void** vaddr, int flags, bus_dmamap_t + *mapp) + + Allocate an area of contiguous memory described by the + tag. The size of memory to be allocated is tag's maxsize. + Returns 0 on success, the error code otherwise. The result + still has to be loaded by + bus_dmamap_load() before used to get + the physical address of the memory. + + + + + + + dmat - the tag + + + + + vaddr - pointer to the storage + for the kernel virtual address of the allocated area + to be returned. + + + + + flags - a bitmap of flags. The only interesting flag is: + + + + + BUS_DMA_NOWAIT - if the + memory is not immediately available return the + error. If this flag is not set then the routine + is allowed to sleep waiting until the memory + will become available. + + + + + + + mapp - pointer to the storage + for the new map to be returned + + + + + + + + void bus_dmamem_free(bus_dma_tag_t dmat, void + *vaddr, bus_dmamap_t map) + + + Free the memory allocated by + bus_dmamem_alloc(). As of now + freeing of the memory allocated with ISA restrictions is + not implemented. Because of this the recommended model + of use is to keep and re-use the allocated areas for as + long as possible. Do not lightly free some area and then + shortly allocate it again. That does not mean that + bus_dmamem_free() should not be + used at all: hopefully it will be properly implemented + soon. + + + + + dmat - the tag + + + + + vaddr - the kernel virtual + address of the memory + + + + + map - the map of the memory (as + returned from + bus_dmamem_alloc()) + + + + + + + + int bus_dmamap_create(bus_dma_tag_t dmat, int + flags, bus_dmamap_t *mapp) + + + Create a map for the tag, to be used in + bus_dmamap_load() later. Returns 0 + on success, the error code otherwise. + + + + + dmat - the tag + + + + + flags - theoretically, a bit map + of flags. But no flags are defined yet, so as of now + it will be always 0. + + + + + mapp - pointer to the storage + for the new map to be returned + + + + + + + + int bus_dmamap_destroy(bus_dma_tag_t dmat, + bus_dmamap_t map) + + + Destroy a map. Returns 0 on success, the error code otherwise. + + + + + + dmat - the tag to which the map is associated + + + + + map - the map to be destroyed + + + + + + + + int bus_dmamap_load(bus_dma_tag_t dmat, + bus_dmamap_t map, void *buf, bus_size_t buflen, + bus_dmamap_callback_t *callback, void *callback_arg, int + flags) + + + Load a buffer into the map (the map must be previously + created by bus_dmamap_create() or + bus_dmamem_alloc()). All the pages + of the buffer are checked for conformance to the tag + requirements and for those not conformant the bounce + pages are allocated. An array of physical segment + descriptors is built and passed to the callback + routine. This callback routine is then expected to + handle it in some way. The number of bounce buffers in + the system is limited, so if the bounce buffers are + needed but not immediately available the request will be + queued and the callback will be called when the bounce + buffers will become available. Returns 0 if the callback + was executed immediately or EINPROGRESS if the request + was queued for future execution. In the latter case the + synchronization with queued callback routine is the + responsibility of the driver. + + + + + + dmat - the tag + + + + + map - the map + + + + + buf - kernel virtual address of + the buffer + + + + + buflen - length of the buffer + + + + + callback, + callback_arg - the callback function and + its argument + + + + + + The prototype of callback function is: + + + void callback(void *arg, bus_dma_segment_t + *seg, int nseg, int error) + + + + + + arg - the same as callback_arg + passed to bus_dmamap_load() + + + + + seg - array of the segment + descriptors + + + + + nseg - number of descriptors in + array + + + + + error - indication of the + segment number overflow: if it's set to EFBIG then + the buffer did not fit into the maximal number of + segments permitted by the tag. In this case only the + permitted number of descriptors will be in the + array. Handling of this situation is up to the + driver: depending on the desired semantics it can + either consider this an error or split the buffer in + two and handle the second part separately + + + + + + Each entry in the segments array contains the fields: + + + + + + + ds_addr - physical bus address + of the segment + + + + + ds_len - length of the segment + + + + + + + + + void bus_dmamap_unload(bus_dma_tag_t dmat, + bus_dmamap_t map) + + unload the map. + + + + + + dmat - tag + + + + + map - loaded map + + + + + + + + + void bus_dmamap_sync (bus_dma_tag_t dmat, + bus_dmamap_t map, bus_dmasync_op_t op) + + + Synchronise a loaded buffer with its bounce pages before + and after physical transfer to or from device. This is + the function that does all the necessary copying of data + between the original buffer and its mapped version. The + buffers must be synchronized both before and after doing + the transfer. + + + + + + dmat - tag + + + + + map - loaded map + + + + + op - type of synchronization + operation to perform: + + + + + + + + BUS_DMASYNC_PREREAD - before + reading from device into buffer + + + + + BUS_DMASYNC_POSTREAD - after + reading from device into buffer + + + + + BUS_DMASYNC_PREWRITE - before + writing the buffer to device + + + + + BUS_DMASYNC_POSTWRITE - after + writing the buffer to device + + + + + + + + + As of now PREREAD and POSTWRITE are null operations but that + may change in the future, so they must not be ignored in the + driver. Synchronization is not needed for the memory + obtained from bus_dmamem_alloc(). + + + Before calling the callback function from + bus_dmamap_load() the segment array is + stored in the stack. And it gets pre-allocated for the + maximal number of segments allowed by the tag. Because of + this the practical limit for the number of segments on i386 + architecture is about 250-300 (the kernel stack is 4KB minus + the size of the user structure, size of a segment array + entry is 8 bytes, and some space must be left). Because the + array is allocated based on the maximal number this value + must not be set higher than really needed. Fortunately, for + most of hardware the maximal supported number of segments is + much lower. But if the driver wants to handle buffers with a + very large number of scatter-gather segments it should do + that in portions: load part of the buffer, transfer it to + the device, load next part of the buffer, and so on. + + + Another practical consequence is that the number of segments + may limit the size of the buffer. If all the pages in the + buffer happen to be physically non-contiguous then the + maximal supported buffer size for that fragmented case would + be (nsegments * page_size). For example, if a maximal number + of 10 segments is supported then on i386 maximal guaranteed + supported buffer size would be 40K. If a higher size is + desired then special tricks should be used in the driver. + + + If the hardware does not support scatter-gather at all or + the driver wants to support some buffer size even if it's + heavily fragmented then the solution is to allocate a + contiguous buffer in the driver and use it as intermediate + storage if the original buffer does not fit. + + + Below are the typical call sequences when using a map depend + on the use of the map. The characters -> are used to show + the flow of time. + + + For a buffer which stays practically fixed during all the + time between attachment and detachment of a device: + + bus_dmamem_alloc -> bus_dmamap_load -> ...use buffer... -> + -> bus_dmamap_unload -> bus_dmamem_free + + + For a buffer that changes frequently and is passed from + outside the driver: + + + bus_dmamap_create -> + -> bus_dmamap_load -> bus_dmamap_sync(PRE...) -> do transfer -> + -> bus_dmamap_sync(POST...) -> bus_dmamap_unload -> + ... + -> bus_dmamap_load -> bus_dmamap_sync(PRE...) -> do transfer -> + -> bus_dmamap_sync(POST...) -> bus_dmamap_unload -> + -> bus_dmamap_destroy + + + + When loading a map created by + bus_dmamem_alloc() the passed address + and size of the buffer must be the same as used in + bus_dmamem_alloc(). In this case it is + guaranteed that the whole buffer will be mapped as one + segment (so the callback may be based on this assumption) + and the request will be executed immediately (EINPROGRESS + will never be returned). All the callback needs to do in + this case is to save the physical address. + + + A typical example would be: + + + static void + alloc_callback(void *arg, bus_dma_segment_t *seg, int nseg, int error) + { + *(bus_addr_t *)arg = seg[0].ds_addr; + } + + ... + int error; + struct somedata { + .... + }; + struct somedata *vsomedata; /* virtual address */ + bus_addr_t psomedata; /* physical bus-relative address */ + bus_dma_tag_t tag_somedata; + bus_dmamap_t map_somedata; + ... + + error=bus_dma_tag_create(parent_tag, alignment, + boundary, lowaddr, highaddr, /*filter*/ NULL, /*filterarg*/ NULL, + /*maxsize*/ sizeof(struct somedata), /*nsegments*/ 1, + /*maxsegsz*/ sizeof(struct somedata), /*flags*/ 0, + &tag_somedata); + if(error) + return error; + + error = bus_dmamem_alloc(tag_somedata, &vsomedata, /* flags*/ 0, + &map_somedata); + if(error) + return error; + + bus_dmamap_load(tag_somedata, map_somedata, (void *)vsomedata, + sizeof (struct somedata), alloc_callback, + (void *) &psomedata, /*flags*/0); + + + Looks a bit long and complicated but that's the way to do + it. The practical consequence is: if multiple memory areas + are allocated always together it would be a really good idea + to combine them all into one structure and allocate as one + (if the alignment and boundary limitations permit). + + + When loading an arbitrary buffer into the map created by + bus_dmamap_create() special measures + must be taken to synchronize with the callback in case it + would be delayed. The code would look like: + + + { + int s; + int error; + + s = splsoftvm(); + error = bus_dmamap_load( + dmat, + dmamap, + buffer_ptr, + buffer_len, + callback, + /*callback_arg*/ buffer_descriptor, + /*flags*/0); + if (error == EINPROGRESS) { + /* + * Do whatever is needed to ensure synchronization + * with callback. Callback is guaranteed not to be started + * until we do splx() or tsleep(). + */ + } + splx(s); + } + + + Two possible approaches for the processing of requests are: + + + 1. If requests are completed by marking them explicitly as + done (such as the CAM requests) then it would be simpler to + put all the further processing into the callback driver + which would mark the request when it's done. Then not much + extra synchronization is needed. For the flow control + reasons it may be a good idea to freeze the request queue + until this request gets completed. + + + 2. If requests are completed when the function returns (such + as classic read or write requests on character devices) then + a synchronization flag should be set in the buffer + descriptor and tsleep() called. Later + when the callback gets called it will do it's processing and + check this synchronization flag. If it's set then the + callback should issue a wakeup. In this approach the + callback function could either do all the needed processing + (just like the previous case) or simply save the segments + array in the buffer descriptor. Then after callback + completes the calling function could use this saved segments + array and do all the processing. + + + + + + + + DMA + + + The Direct Memory Access (DMA) is implemented in the ISA bus + through the DMA controller (actually, two of them but that's + an irrelevant detail). To make the early ISA devices simple + and cheap the logic of the bus control and address + generation was concentrated in the DMA controller. + Fortunately, FreeBSD provides a set of functions that mostly + hide the annoying details of the DMA controller from the + device drivers. + + + + The simplest case is for the fairly intelligent + devices. Like the bus master devices on PCI they can + generate the bus cycles and memory addresses all by + themselves. The only thing they really need from the DMA + controller is bus arbitration. So for this purpose they + pretend to be cascaded slave DMA controllers. And the only + thing needed from the system DMA controller is to enable the + cascaded mode on a DMA channel by calling the following + function when attaching the driver: + + + + void isa_dmacascade(int channel_number) + + + + All the further activity is done by programming the + device. When detaching the driver no DMA-related functions + need to be called. + + + + For the simpler devices things get more complicated. The + functions used are: + + + + + + + int isa_dma_acquire(int chanel_number) + + + Reserve a DMA channel. Returns 0 on success or EBUSY + if the channel was already reserved by this or a + different driver. Most of the ISA devices are not able + to share DMA channels anyway, so normally this + function is called when attaching a device. This + reservation was made redundant by the modern interface + of bus resources but still must be used in addition to + the latter. If not used then later, other DMA routines + will panic. + + + + + + int isa_dma_release(int chanel_number) + + + Release a previously reserved DMA channel. No + transfers must be in progress when the channel is + released (as well as the device must not try to + initiate transfer after the channel is released). + + + + + + void isa_dmainit(int chan, u_int + bouncebufsize) + + + Allocate a bounce buffer for use with the specified + channel. The requested size of the buffer can't exceed + 64KB. This bounce buffer will be automatically used + later if a transfer buffer happens to be not + physically contiguous or outside of the memory + accessible by the ISA bus or crossing the 64KB + boundary. If the transfers will be always done from + buffers which conform to these conditions (such as + those allocated by + bus_dmamem_alloc() with proper + limitations) then isa_dmainit() + does not have to be called. But it's quite convenient + to transfer arbitrary data using the DMA controller. + The bounce buffer will automatically care of the + scatter-gather issues. + + + + + + chan - channel number + + + + + bouncebufsize - size of the + bounce buffer in bytes + + + + + + + + + + void isa_dmastart(int flags, caddr_t addr, u_int + nbytes, int chan) + + + Prepare to start a DMA transfer. This function must be + called to set up the DMA controller before actually + starting transfer on the device. It checks that the + buffer is contiguous and falls into the ISA memory + range, if not then the bounce buffer is automatically + used. If bounce buffer is required but not set up by + isa_dmainit() or too small for + the requested transfer size then the system will + panic. In case of a write request with bounce buffer + the data will be automatically copied to the bounce + buffer. + + + + flags - a bitmask determining the type of operation to + be done. The direction bits B_READ and B_WRITE are mutually + exclusive. + + + + + + B_READ - read from the ISA bus into memory + + + + + B_WRITE - write from the memory to the ISA bus + + + + + B_RAW - if set then the DMA controller will remember + the buffer and after the end of transfer will + automatically re-initialize itself to repeat transfer + of the same buffer again (of course, the driver may + change the data in the buffer before initiating + another transfer in the device). If not set then the + parameters will work only for one transfer, and + isa_dmastart() will have to be + called again before initiating the next + transfer. Using B_RAW makes sense only if the bounce + buffer is not used. + + + + + + + + addr - virtual address of the buffer + + + + + nbytes - length of the buffer. Must be less or equal to + 64KB. Length of 0 is not allowed: the DMA controller will + understand it as 64KB while the kernel code will + understand it as 0 and that would cause unpredictable + effects. For channels number 4 and higher the length must + be even because these channels transfer 2 bytes at a + time. In case of an odd length the last byte will not be + transferred. + + + + + chan - channel number + + + + + + void isa_dmadone(int flags, caddr_t addr, int + nbytes, int chan) + + + Synchronize the memory after device reports that transfer + is done. If that was a read operation with a bounce buffer + then the data will be copied from the bounce buffer to the + original buffer. Arguments are the same as for + isa_dmastart(). Flag B_RAW is + permitted but it does not affect + isa_dmadone() in any way. + + + + + + int isa_dmastatus(int channel_number) + + + Returns the number of bytes left in the current transfer + to be transferred. In case the flag B_READ was set in + isa_dmastart() the number returned + will never be equal to zero. At the end of transfer it + will be automatically reset back to the length of + buffer. The normal use is to check the number of bytes + left after the device signals that the transfer is + completed. If the number of bytes is not 0 then probably + something went wrong with that transfer. + + + + + + int isa_dmastop(int channel_number) + + + Aborts the current transfer and returns the number of + bytes left untransferred. + + + + + + + xxx_isa_probe + + + + This function probes if a device is present. If the driver + supports auto-detection of some part of device configuration + (such as interrupt vector or memory address) this + auto-detection must be done in this routine. + + + + As for any other bus, if the device can not be detected or + is detected but failed the self-test or some other problem + happened then it returns a positive value of error. The + value ENXIO must be returned if the device is not + present. Other error values may mean other conditions. Zero + or negative values mean success. Most of the drivers return + zero as success. + + + + The negative return values are used when a PnP device + supports multiple interfaces. For example, an older + compatibility interface and a newer advanced interface which + are supported by different drivers. Then both drivers would + detect the device. The driver which returns a higher value + in the probe routine takes precedence (in other words, the + driver returning 0 has highest precedence, one returning -1 + is next, one returning -2 is after it and so on). In result + the devices which support only the old interface will be + handled by the old driver (which should return -1 from the + probe routine) while the devices supporting the new + interface as well will be handled by the new driver (which + should return 0 from the probe routine). + + + + The device descriptor struct xxx_softc is allocated by the + system before calling the probe routine. If the probe + routine returns an error the descriptor will be + automatically deallocated by the system. So if a probing + error occurs the driver must make sure that all the + resources it used during probe are deallocated and that + nothing keeps the descriptor from being safely + deallocated. If the probe completes successfully the + descriptor will be preserved by the system and later passed + to the routine xxx_isa_attach(). If a + driver returns a negative value it can't be sure that it + will have the highest priority and its attach routine will + be called. So in this case it also must release all the + resources before returning and if necessary allocate them + again in the attach routine. When + xxx_isa_probe() returns 0 releasing the + resources before returning is also a good idea, a + well-behaved driver should do so. But in case if there is + some problem with releasing the resources the driver is + allowed to keep resources between returning 0 from the probe + routine and execution of the attach routine. + + + + A typical probe routine starts with getting the device + descriptor and unit: + + + struct xxx_softc *sc = device_get_softc(dev); + int unit = device_get_unit(dev); + int pnperror; + int error = 0; + + sc->dev = dev; /* link it back */ + sc->unit = unit; + + + Then check for the PnP devices. The check is carried out by + a table containing the list of PnP IDs supported by this + driver and human-readable descriptions of the device models + corresponding to these IDs. + + + + pnperror=ISA_PNP_PROBE(device_get_parent(dev), dev, + xxx_pnp_ids); if(pnperror == ENXIO) return ENXIO; + + + + The logic of ISA_PNP_PROBE is the following: If this card + (device unit) was not detected as PnP then ENOENT will be + returned. If it was detected as PnP but its detected ID does + not match any of the IDs in the table then ENXIO is + returned. Finally, if it has PnP support and it matches on + of the IDs in the table, 0 is returned and the appropriate + description from the table is set by + device_set_desc(). + + + + If a driver supports only PnP devices then the condition + would look like: + + + if(pnperror != 0) + return pnperror; + + + No special treatment is required for the drivers which don't + support PnP because they pass an empty PnP ID table and will + always get ENXIO if called on a PnP card. + + + + The probe routine normally needs at least some minimal set + of resources, such as I/O port number to find the card and + probe it. Depending on the hardware the driver may be able + to discover the other necessary resources automatically. The + PnP devices have all the resources pre-set by the PnP + subsystem, so the driver does not need to discover them by + itself. + + + + Typically the minimal information required to get access to + the device is the I/O port number. Then some devices allow + to get the rest of information from the device configuration + registers (though not all devices do that). So first we try + to get the port start value: + + + sc->port0 = bus_get_resource_start(dev, + SYS_RES_IOPORT, 0 /*rid*/); if(sc->port0 == 0) return ENXIO; + + + + The base port address is saved in the structure softc for + future use. If it will be used very often then calling the + resource function each time would be prohibitively slow. If + we don't get a port we just return an error. Some device + drivers can instead be clever and try to probe all the + possible ports, like this: + + + + /* table of all possible base I/O port addresses for this device */ + static struct xxx_allports { + u_short port; /* port address */ + short used; /* flag: if this port is already used by some unit */ + } xxx_allports = { + { 0x300, 0 }, + { 0x320, 0 }, + { 0x340, 0 }, + { 0, 0 } /* end of table */ + }; + + ... + int port, i; + ... + + port = bus_get_resource_start(dev, SYS_RES_IOPORT, 0 /*rid*/); + if(port !=0 ) { + for(i=0; xxx_allports[i].port!=0; i++) { + if(xxx_allports[i].used || xxx_allports[i].port != port) + continue; + + /* found it */ + xxx_allports[i].used = 1; + /* do probe on a known port */ + return xxx_really_probe(dev, port); + } + return ENXIO; /* port is unknown or already used */ + } + + /* we get here only if we need to guess the port */ + for(i=0; xxx_allports[i].port!=0; i++) { + if(xxx_allports[i].used) + continue; + + /* mark as used - even if we find nothing at this port + * at least we won't probe it in future + */ + xxx_allports[i].used = 1; + + error = xxx_really_probe(dev, xxx_allports[i].port); + if(error == 0) /* found a device at that port */ + return 0; + } + /* probed all possible addresses, none worked */ + return ENXIO; + + + Of course, normally the driver's + identify() routine should be used for + such things. But there may be one valid reason why it may be + better to be done in probe(): if this + probe would drive some other sensitive device crazy. The + probe routines are ordered with consideration of the + "sensitive" flag: the sensitive devices get probed first and + the rest of devices later. But the + identify() routines are called before + any probes, so they show no respect to the sensitive devices + and may upset them. + + + + Now, after we got the starting port we need to set the port + count (except for PnP devices) because the kernel does not + have this information in the configuration file. + + + + if(pnperror /* only for non-PnP devices */ + && bus_set_resource(dev, SYS_RES_IOPORT, 0, sc->port0, + XXX_PORT_COUNT)<0) + return ENXIO; + + + Finally allocate and activate a piece of port address space + (special values of start and end mean "use those we set by + bus_set_resource()"): + + + + sc->port0_rid = 0; + sc->port0_r = bus_alloc_resource(dev, SYS_RES_IOPORT, + &sc->port0_rid, + /*start*/ 0, /*end*/ ~0, /*count*/ 0, RF_ACTIVE); + + if(sc->port0_r == NULL) + return ENXIO; + + + Now having access to the port-mapped registers we can poke + the device in some way and check if it reacts like it is + expected to. If it does not then there is probably some + other device or no device at all at this address. + + + + Normally drivers don't set up the interrupt handlers until + the attach routine. Instead they do probes in the polling + mode using the DELAY() function for + timeout. The probe routine must never hang forever, all the + waits for the device must be done with timeouts. If the + device does not respond within the time it's probably broken + or misconfigured and the driver must return error. When + determining the timeout interval give the device some extra + time to be on the safe side: although + DELAY() is supposed to delay for the + same amount of time on any machine it has some margin of + error, depending on the exact CPU. + + + + If the probe routine really wants to check that the + interrupts really work it may configure and probe the + interrupts too. But that's not recommended. + + + + /* implemented in some very device-specific way */ + if(error = xxx_probe_ports(sc)) + goto bad; /* will deallocate the resources before returning */ + + + + The fucntion xxx_probe_ports() may also + set the device description depending on the exact model of + device it discovers. But if there is only one supported + device model this can be as well done in a hardcoded way. + Of course, for the PnP devices the PnP support sets the + description from the table automatically. + + + + if(pnperror) + device_set_desc(dev, "Our device model 1234"); + + + + Then the probe routine should either discover the ranges of + all the resources by reading the device configuration + registers or make sure that they were set explicitly by the + user. We will consider it with an example of on-board + memory. The probe routine should be as non-intrusive as + possible, so allocation and check of functionality of the + rest of resources (besides the ports) would be better left + to the attach routine. + + + + The memory address may be specified in the kernel + configuration file or on some devices it may be + pre-configured in non-volatile configuration registers. If + both sources are available and different, which one should + be used? Probably if the user bothered to set the address + explicitly in the kernel configuration file they know what + they're doing and this one should take precedence. An + example of implementation could be: + + + /* try to find out the config address first */ + sc->mem0_p = bus_get_resource_start(dev, SYS_RES_MEMORY, 0 /*rid*/); + if(sc->mem0_p == 0) { /* nope, not specified by user */ + sc->mem0_p = xxx_read_mem0_from_device_config(sc); + + + if(sc->mem0_p == 0) + /* can't get it from device config registers either */ + goto bad; + } else { + if(xxx_set_mem0_address_on_device(sc) < 0) + goto bad; /* device does not support that address */ + } + + /* just like the port, set the memory size, + * for some devices the memory size would not be constant + * but should be read from the device configuration registers instead + * to accommodate different models of devices. Another option would + * be to let the user set the memory size as "msize" configuration + * resource which will be automatically handled by the ISA bus. + */ + if(pnperror) { /* only for non-PnP devices */ + sc->mem0_size = bus_get_resource_count(dev, SYS_RES_MEMORY, 0 /*rid*/); + if(sc->mem0_size == 0) /* not specified by user */ + sc->mem0_size = xxx_read_mem0_size_from_device_config(sc); + + if(sc->mem0_size == 0) { + /* suppose this is a very old model of device without + * auto-configuration features and the user gave no preference, + * so assume the minimalistic case + * (of course, the real value will vary with the driver) + */ + sc->mem0_size = 8*1024; + } + + if(xxx_set_mem0_size_on_device(sc) < 0) + goto bad; /* device does not support that size */ + + if(bus_set_resource(dev, SYS_RES_MEMORY, /*rid*/0, + sc->mem0_p, sc->mem0_size)<0) + goto bad; + } else { + sc->mem0_size = bus_get_resource_count(dev, SYS_RES_MEMORY, 0 /*rid*/); + } + + + Resources for IRQ and DRQ are easy to check by analogy. + + + + If all went well then release all the resources and return success. + + + xxx_free_resources(sc); + return 0; + + + Finally, handle the troublesome situations. All the + resources should be deallocated before returning. We make + use of the fact that before the structure softc is passed to + us it gets zeroed out, so we can find out if some resource + was allocated: then its descriptor is non-zero. + + + bad: + + xxx_free_resources(sc); + if(error) + return error; + else /* exact error is unknown */ + return ENXIO; + + + That would be all for the probe routine. Freeing of + resources is done from multiple places, so it's moved to a + function which may look like: + + +static void + xxx_free_resources(sc) + struct xxx_softc *sc; + { + /* check every resource and free if not zero */ + + /* interrupt handler */ + if(sc->intr_r) { + bus_teardown_intr(sc->dev, sc->intr_r, sc->intr_cookie); + bus_release_resource(sc->dev, SYS_RES_IRQ, sc->intr_rid, + sc->intr_r); + sc->intr_r = 0; + } + + /* all kinds of memory maps we could have allocated */ + if(sc->data_p) { + bus_dmamap_unload(sc->data_tag, sc->data_map); + sc->data_p = 0; + } + if(sc->data) { /* sc->data_map may be legitimately equal to 0 */ + /* the map will also be freed */ + bus_dmamem_free(sc->data_tag, sc->data, sc->data_map); + sc->data = 0; + } + if(sc->data_tag) { + bus_dma_tag_destroy(sc->data_tag); + sc->data_tag = 0; + } + + ... free other maps and tags if we have them ... + + if(sc->parent_tag) { + bus_dma_tag_destroy(sc->parent_tag); + sc->parent_tag = 0; + } + + /* release all the bus resources */ + if(sc->mem0_r) { + bus_release_resource(sc->dev, SYS_RES_MEMORY, sc->mem0_rid, + sc->mem0_r); + sc->mem0_r = 0; + } + ... + if(sc->port0_r) { + bus_release_resource(sc->dev, SYS_RES_IOPORT, sc->port0_rid, + sc->port0_r); + sc->port0_r = 0; + } + } + + + + + xxx_isa_attach + + + The attach routine actually connects the driver to the + system if the probe routine returned success and the system + had chosen to attach that driver. If the probe routine + returned 0 then the attach routine may expect to receive the + device structure softc intact, as it was set by the probe + routine. Also if the probe routine returns 0 it may expect + that the attach routine for this device shall be called at + some point in the future. If the probe routine returns a + negative value then the driver may make none of these + assumptions. + + + The attach routine returns 0 if it completed successfully or + error code otherwise. + + + The attach routine starts just like the probe routine, + with getting some frequently used data into more accessible + variables. + + + struct xxx_softc *sc = device_get_softc(dev); + int unit = device_get_unit(dev); + int error = 0; + + Then allocate and activate all the necessary + resources. Because normally the port range will be released + before returning from probe, it has to be allocated + again. We expect that the probe routine had properly set all + the resource ranges, as well as saved them in the structure + softc. If the probe routine had left some resource allocated + then it does not need to be allocated again (which would be + considered an error). + + + sc->port0_rid = 0; + sc->port0_r = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->port0_rid, + /*start*/ 0, /*end*/ ~0, /*count*/ 0, RF_ACTIVE); + + if(sc->port0_r == NULL) + return ENXIO; + + /* on-board memory */ + sc->mem0_rid = 0; + sc->mem0_r = bus_alloc_resource(dev, SYS_RES_MEMORY, &sc->mem0_rid, + /*start*/ 0, /*end*/ ~0, /*count*/ 0, RF_ACTIVE); + + if(sc->mem0_r == NULL) + goto bad; + + /* get its virtual address */ + sc->mem0_v = rman_get_virtual(sc->mem0_r); + + The DMA request channel (DRQ) is allocated likewise. To + initialize it use functions of the + isa_dma*() family. For example: + + + isa_dmacascade(sc->drq0); + + The interrupt request line (IRQ) is a bit + special. Besides allocation the driver's interrupt handler + should be associated with it. Historically in the old ISA + drivers the argument passed by the system to the interrupt + handler was the device unit number. But in modern drivers + the convention suggests passing the pointer to structure + softc. The important reason is that when the structures + softc are allocated dynamically then getting the unit number + from softc is easy while getting softc from unit number is + difficult. Also this convention makes the drivers for + different buses look more uniform and allows them to share + the code: each bus gets its own probe, attach, detach and + other bus-specific routines while the bulk of the driver + code may be shared among them. + + + + sc->intr_rid = 0; + sc->intr_r = bus_alloc_resource(dev, SYS_RES_MEMORY, &sc->intr_rid, + /*start*/ 0, /*end*/ ~0, /*count*/ 0, RF_ACTIVE); + + if(sc->intr_r == NULL) + goto bad; + + /* + * XXX_INTR_TYPE is supposed to be defined depending on the type of + * the driver, for example as INTR_TYPE_CAM for a CAM driver + */ + error = bus_setup_intr(dev, sc->intr_r, XXX_INTR_TYPE, + (driver_intr_t *) xxx_intr, (void *) sc, &sc->intr_cookie); + if(error) + goto bad; + + + + + If the device needs to make DMA to the main memory then + this memory should be allocated like described before: + + + error=bus_dma_tag_create(NULL, /*alignment*/ 4, + /*boundary*/ 0, /*lowaddr*/ BUS_SPACE_MAXADDR_24BIT, + /*highaddr*/ BUS_SPACE_MAXADDR, /*filter*/ NULL, /*filterarg*/ NULL, + /*maxsize*/ BUS_SPACE_MAXSIZE_24BIT, + /*nsegments*/ BUS_SPACE_UNRESTRICTED, + /*maxsegsz*/ BUS_SPACE_MAXSIZE_24BIT, /*flags*/ 0, + &sc->parent_tag); + if(error) + goto bad; + + /* many things get inherited from the parent tag + * sc->data is supposed to point to the structure with the shared data, + * for example for a ring buffer it could be: + * struct { + * u_short rd_pos; + * u_short wr_pos; + * char bf[XXX_RING_BUFFER_SIZE] + * } *data; + */ + error=bus_dma_tag_create(sc->parent_tag, 1, + 0, BUS_SPACE_MAXADDR, 0, /*filter*/ NULL, /*filterarg*/ NULL, + /*maxsize*/ sizeof(* sc->data), /*nsegments*/ 1, + /*maxsegsz*/ sizeof(* sc->data), /*flags*/ 0, + &sc->data_tag); + if(error) + goto bad; + + error = bus_dmamem_alloc(sc->data_tag, &sc->data, /* flags*/ 0, + &sc->data_map); + if(error) + goto bad; + + /* xxx_alloc_callback() just saves the physical address at + * the pointer passed as its argument, in this case &sc->data_p. + * See details in the section on bus memory mapping. + * It can be implemented like: + * + * static void + * xxx_alloc_callback(void *arg, bus_dma_segment_t *seg, + * int nseg, int error) + * { + * *(bus_addr_t *)arg = seg[0].ds_addr; + * } + */ + bus_dmamap_load(sc->data_tag, sc->data_map, (void *)sc->data, + sizeof (* sc->data), xxx_alloc_callback, (void *) &sc->data_p, + /*flags*/0); + + + After all the necessary resources are allocated the + device should be initialized. The initialization may include + testing that all the expected features are functional. + + if(xxx_initialize(sc) < 0) + goto bad; + + + The bus subsystem will automatically print on the + console the device description set by probe. But if the + driver wants to print some extra information about the + device it may do so, for example: + + + device_printf(dev, "has on-card FIFO buffer of %d bytes\n", sc->fifosize); + + + If the initialization routine experiences any problems + then printing messages about them before returning error is + also recommended. + + The final step of the attach routine is attaching the + device to its functional subsystem in the kernel. The exact + way to do it depends on the type of the driver: a character + device, a block device, a network device, a CAM SCSI bus + device and so on. + + If all went well then return success. + + error = xxx_attach_subsystem(sc); + if(error) + goto bad; + + return 0; + + Finally, handle the troublesome situations. All the + resources should be deallocated before returning an + error. We make use of the fact that before the structure + softc is passed to us it gets zeroed out, so we can find out + if some resource was allocated: then its descriptor is + non-zero. + + bad: + + xxx_free_resources(sc); + if(error) + return error; + else /* exact error is unknown */ + return ENXIO; + + That would be all for the attach routine. + + + + + + xxx_isa_detach + + + If this function is present in the driver and the driver is + compiled as a loadable module then the driver gets the + ability to be unloaded. This is an important feature if the + hardware supports hot plug. But the ISA bus does not support + hot plug, so this feature is not particularly important for + the ISA devices. The ability to unload a driver may be + useful when debugging it, but in many cases installation of + the new version of the driver would be required only after + the old version somehow wedges the system and reboot will be + needed anyway, so the efforts spent on writing the detach + routine may not be worth it. Another argument is that + unloading would allow upgrading the drivers on a production + machine seems to be mostly theoretical. Installing a new + version of a driver is a dangerous operation which should + never be performed on a production machine (and which is not + permitted when the system is running in secure mode). Still + the detach routine may be provided for the sake of + completeness. + + + + The detach routine returns 0 if the driver was successfully + detached or the error code otherwise. + + + + The logic of detach is a mirror of the attach. The first + thing to do is to detach the driver from its kernel + subsystem. If the device is currently open then the driver + has two choices: refuse to be detached or forcibly close and + proceed with detach. The choice used depends on the ability + of the particular kernel subsystem to do a forced close and + on the preferences of the driver's author. Generally the + forced close seems to be the preferred alternative. + struct xxx_softc *sc = device_get_softc(dev); + int error; + + error = xxx_detach_subsystem(sc); + if(error) + return error; + + + Next the driver may want to reset the hardware to some + consistent state. That includes stopping any ongoing + transfers, disabling the DMA channels and interrupts to + avoid memory corruption by the device. For most of the + drivers this is exactly what the shutdown routine does, so + if it is included in the driver we can as well just call it. + + xxx_isa_shutdown(dev); + + + And finally release all the resources and return success. + xxx_free_resources(sc); + return 0; + + + + + + xxx_isa_shutdown + + + This routine is called when the system is about to be shut + down. It is expected to bring the hardware to some + consistent state. For most of the ISA devices no special + action is required, so the function is not really necessary + because the device will be re-initialized on reboot + anyway. But some devices have to be shut down with a special + procedure, to make sure that they will be properly detected + after soft reboot (this is especially true for many devices + with proprietary identification protocols). In any case + disabling DMA and interrupts in the device registers and + stopping any ongoing transfers is a good idea. The exact + action depends on the hardware, so we don't consider it here + in any details. + + + + xxx_intr + + + + The interrupt handler is called when an interrupt is + received which may be from this particular device. The ISA + bus does not support interrupt sharing (except some special + cases) so in practice if the interrupt handler is called + then the interrupt almost for sure came from its + device. Still the interrupt handler must poll the device + registers and make sure that the interrupt was generated by + its device. If not it should just return. + + + + The old convention for the ISA drivers was getting the + device unit number as an argument. It is obsolete, and the + new drivers receive whatever argument was specified for them + in the attach routine when calling + bus_setup_intr(). By the new convention + it should be the pointer to the structure softc. So the + interrupt handler commonly starts as: + + + + static void + xxx_intr(struct xxx_softc *sc) + { + + + + + It runs at the interrupt priority level specified by the + interrupt type parameter of + bus_setup_intr(). That means that all + the other interrupts of the same type as well as all the + software interrupts are disabled. + + + + To avoid races it is commonly written as a loop: + + + + while(xxx_interrupt_pending(sc)) { + xxx_process_interrupt(sc); + xxx_acknowledge_interrupt(sc); + } + + + The interrupt handler has to acknowledge interrupt to the + device only but not to the interrupt controller, the system + takes care of the latter. + + + +