The Device Kit: Functions for Drivers

The kernel exports a number of functions for the benefit of device drivers. These are functions that drivers can call to do their work; they're not functions that are available to applications. Although implemented by the kernel, they're not part of the Kernel Kit. The device driver accesses these functions directly from the kernel, not through a library.


acquire_spinlock(), release_spinlock()

Declared in: <device/KernelExport.h>

      void acquire_spinlock(spinlock *lock)
      void release_spinlock(spinlock *lock)

These functions acquire and release the lock spinlock. Spinlocks, like semaphores, are used to protect critical sections of code that must remain on the same processor for a single path of execution--for example, code that atomically accesses a hardware register or a shared data structure. A common use for spinlocks is to protect data structures that both an interrupt handler and normal driver code must access.

However, spinlocks work quite differently from semaphores. No count is kept of how many times a thread has acquired the lock, for example, so calls to acquire_spinlock() and release_spinlock() should not be nested. More importantly, acquire_spinlock() spins while attempting to acquire the lock; it doesn't block or release its hold on the CPU.

These functions assume that interrupts have been disabled. They should be nested within calls to disable_interrupts() and restore_interrupts() as follows:

   spinlock lock;
   cpu_status former = disable_interrupts();
   acquire_spinlock(&lock);
   /* critical code goes here */
   release_spinlock(&lock);
   restore_interrupts(former);

These two pairs of functions enable the thread to get into the critical code without rescheduling. Disabling interrupts ensures that the thread won't be preemptively rescheduled. Because acquire_spinlock() doesn't block, it provides the additional assurance that the thread won't be voluntarily rescheduled.

Executing the critical code under the protection of the spinlock guarantees that no other thread will execute the same code at the same time on another processor. Spinlocks should be held only as long as necessary and released as quickly as possible.

See also: create_spinlock()


create_spinlock(), delete_spinlock()

Declared in: <device/KernelExport.h>

      spinlock *create_spinlock(void)
      void delete_spinlock(spinlock *lock)

< These functions will, when implemented and exported, produce and destroy spinlocks. Currently, they're declared but not exported. To create a spinlock at present, simply declare a spinlock variable and pass a pointer to it to acquire_spinlock() . >

See also: acquire_spinlock()


disable_interrupts(), restore_interrupts()

Declared in: <device/KernelExport.h>

      cpu_status disable_interrupts(void) 
      void restore_interrupts(cpu_status status)

These functions disable interrupts at the CPU (the one the caller is currently running on) and restore them again. disable_interrupts() prevents the CPU from being interrupted and returns its previous status--whether or not interrupts were already disabled before the disable_interrupts() call. restore_interrupts() restores the previous status of the CPU, which should be the value that disable_interrupts() returned. Passing the status returned by disable_interrupts() to restore_interrupts() allows these functions to be paired and nested.

As diagrammed below, individual interrupts can be enabled and disabled at two other hardware locations. disable_isa_interrupt() and enable_isa_interrupt() work at the ISA standard 8259 interrupt controller, and disable_io_interrupt() and enable_io_interrupt() act at the Be-defined I/O interrupt controller that combines ISA and PCI interrupts.

Interrupts that have been disabled by disable_interrupts() must be reenabled by restore_interrupts().

See also: acquire_spinlock() , set_io_interrupt_handler() , set_isa_interrupt_handler()


disable_io_interrupt() see set_io_interrupt_handler()


disable_isa_interrupt() see set_isa_interrupt_handler()


dprintf(), set_dprintf_enabled() , kernel_debugger()

Declared in: <device/KernelExport.h>

      void dprintf(const char *format, ...)
      bool set_dprintf_enabled(bool enabled)
      void kernel_debugger(const char *string)

dprintf() is a debugging function that has the same syntax and behavior as standard C printf(), except that it writes its output to the fourth serial port ("/dev/serial4") at a data rate of 19,200 bits per second. By default, dprintf() is disabled.

set_dprintf_enabled() enables dprintf() if the enabled flag is TRUE, and disables it if the flag is FALSE. It returns the previous enabled state. Calls to this function can be nested by caching the return value of a call that disables printing and passing it to the paired call that restores the previous state.

kernel_debugger() drops the calling thread into a debugger that writes its output to the fourth serial port at 19,200 bits per second, just as dprintf() does. This debugger produces string as its first message; it's not affected by set_dprintf_enabled() .

kernel_debugger() is identical to the debugger() function documented in the Kernel Kit, except that it works in the kernel and engages a different debugger. Drivers should use it instead of debugger() .

See also: debugger() in the Kernel Kit


enable_io_interrupt() see set_io_interrupt_handler()


enable_isa_interrupt() see set_isa_interrupt_handler()


get_memory_map()

Declared in: <device/KernelExport.h>

      long get_memory_map(void *address, ulong numBytes, 
         physical_entry *table, long numEntries)

Locates the separate pieces of physical memory that correspond to the contiguous buffer of virtual memory beginning at address and extending for numBytes. Each piece of physical memory is described by a physical_entry structure. It has just two fields:

void *address The address of a block of physical memory.
ulong size The number of bytes in the block.

This function is passed a pointer to a table of physical_entry structures. It fills in the table, stopping when the entire buffer of virtual memory has been described or when numEntry entries in the table have been written, whichever comes first.

If the table provided isn't big enough, you'll need to call get_memory_map() again and ask it to describe the rest of the buffer. If the table is too big, this function sets the size field of the entry following the last one it needed to 0. This indicates that it has finished mapping the entire address buffer.

Memory should be locked while it is being mapped. Before calling get_memory_map(), call lock_memory() to make sure that it all stays in place:

   physical_entry table[count];
   lock_memory(someAddress, someNumberOfBytes, FALSE);
   get_memory_map(someAddress, someNumberOfBytes, table, count);
   . . .
   unlock_memory(someAddress, someNumberOfBytes);

< This function consistently returns B_NO_ERROR . >

See also: lock_memory() , start_isa_dma()


get_nth_pci_info()

Declared in: <device/PCI.h>

      long get_nth_pci_info(long index, pci_info *info)

This function looks up the PCI device at index and provides a description of it in the pci_info structure that info refers to. Indices begin at 0 and there are no gaps in the list.

The pci_info structure contains a number of fields that report values found in the configuration register space for the device and it also describes how the device has been mapped into the system. The following fields are common to all devices:

ushort vendor_id An identifier for the manufacturer of the device.
ushort device_id An identifier for the particular device of the vendor, assigned by the vendor.
uchar bus The bus number.
uchar device The number that identifies the location of the device on the bus.
uchar function The function number in the device.
uchar revision A device-specific version number, assigned by the vendor.
uchar class_api The type of specific register-level programming interface for the device (the lower byte of the class code field).
uchar class_sub The specific type of function the device performs (the middle byte of the class code field).
uchar class_base The broadly-defined device type (the upper byte of the class code field).
uchar line_size The size of the system cache line, in units of 32-bit words.
uchar latency The latency timer for the PCI bus master.
uchar header_type The header type.
uchar bist The contents of the register for the built-in self test.
uchar u A union of structures, one for each header type.

Currently, there's only one header (type 0x00), but in the future there may be others. Consequently, header-specific information is recorded in a union of structures, one for each header type. The union (named simply u) at present has just one member, a structure for the current header (named h0):

      typedef struct {
            . . .
            union { 
               struct { 
                  . . .
               } h0;
            } u;
         } pci_info 

The fields of the h0 structure are:

ulong cardbus_cis The CardBus CIS pointer.
ushort subsystem_id The vendor-assigned identifier for the add-in card containing the device.
ushort subsystem_vendor_id The identifier for manufacturer of the add-in card that contains the device.
ulong rom_base The base address for the expansion ROM, as viewed from the host processor.
ulong rom_base_pci The base address for the expansion ROM, as viewed from the PCI bus. This is the address a bus master would use.
ulong rom_size The amount of memory in the expansion ROM, in bytes.
ulong base_registers[6] The base addresses of requested memory spaces and I/O spaces, as viewed from the host processor.
ulong base_registers_pci[6] The base addresses of requested memory spaces and I/O spaces, as viewed from the PCI bus. This is the address a bus master would use.
ulong base_register_sizes[6] The sizes of requested memory spaces and I/O spaces.
uchar base_register_flags[6] The flags from the base-address registers.
uchar interrupt_line The interrupt line. This number identifies the interrupt associated with the device. See set_io_interrupt_handler() .
uchar interrupt_pin The interrupt pin that the device uses.
uchar min_grant The minimum burst period the device needs, assuming a clock rate of 33 MHz.
uchar max_latency The maximum frequency at which the device needs access to the PCI bus.

In device/PCI.h, you'll find a number of constants that you can use to test various fields of a pci_info structure. See the PCI Local Bus Specification, published by the PCI Special Interest Group (Portland, OR) for more information on the configuration of a PCI device.

get_nth_pci_info() returns B_NO_ERROR if it successfully describes a PCI device, and B_ERROR if it can't find the device (for example, if index is out-of-range).

See also: read_pci_config()


io_card_version() see motherboard_version()


isa_address()

Declared in: <device/KernelExport.h>

      void *isa_address(long offset)

Returns the virtual address corresponding to the specified offset in the ISA I/O address space. By passing an offset of 0, you can find the base address that's mapped to the ISA address space.


kernel_debugger() see dprintf()


lock_isa_dma_channel(), unlock_isa_dma_channel()

Declared in: <device/KernelExport.h>

      long lock_isa_dma_channel(long channel)
      long unlock_isa_dma_channel(long channel)

These functions reserve an ISA DMA channel and release a channel previously reserved. They return B_NO_ERROR if successful, and B_ERROR if not. Like semaphores, these functions work only if all participating parties adhere to the protocol.

There are 7 ISA DMA channels. In general, they're used as follows:

Channel Use
0 Unreserved, available
1 Unreserved, available
2 Reserved for the floppy disk controller
3 Reserved for the parallel port driver
5 Reserved for IDE
6 Reserved for sound
7 Reserved for sound

Channel 4 is taken by the system; it cannot be used.


lock_memory(), unlock_memory()

Declared in: <device/KernelExport.h>

      long lock_memory(void *address, ulong numBytes, bool willChange)
      long unlock_memory(void *address, ulong numBytes)

lock_memory() makes sure that all the memory beginning at the specified virtual address and extending for numBytes is resident in RAM, and locks it so that it won't be paged out until unlock_memory() is called. It pages in any of the memory that isn't resident at the time it's called.

The willChange flag should be TRUE if any part of the memory range will be altered while it's locked--especially if the hardware device will do anything to modify the memory, since that won't otherwise be noticed by the system and the modified pages may not be written. The willChange flag should be FALSE if the memory won't change while it's locked.

Each of these functions returns B_NO_ERROR if successful and B_ERROR if not. The main reason that lock_memory() would fail is that you're attempting to lock more memory than can be paged in.


make_isa_dma_table() see start_isa_dma()


motherboard_version(), io_card_version()

Declared in: <device/KernelExport.h>

      long motherboard_version(void)
      long io_card_version(void)

These functions return the current versions of the motherboard and of the I/O card.


ram_address()

Declared in: <device/KernelExport.h>

      void *ram_address(void *physicalAddress)

Returns the address of a physical block of system memory (RAM) as viewed from the PCI bus. If passed NULL as the physicalAddress, this function returns a pointer to the first byte of RAM; otherwise it returns a pointer to the physicalAddress.

This information is needed by bus masters--components, such as the ethernet and some SCSI controllers, that can perform DMA reads and writes (directly read from and write to system memory without CPU intervention).

Memory must be locked when calling this function. For example:

   physical_entry table[count];
   void *where;
   
   lock_memory(someAddress, someNumberOfBytes, FALSE);
   get_memory_map(someAddress, someNumberOfBytes, table, count);
   where = ram_address(table[i].address)
   . . .
   unlock_memory(someAddress, someNumberOfBytes);

See also: get_memory_map() , lock_memory()


read_pci_config(), write_pci_config()

Declared in: <device/PCI.h>

      long read_pci_config(uchar bus, uchar device, uchar function, 
         long offset, long size)
      void write_pci_config(uchar bus, uchar device, uchar function, 
         long offset, long size, long value)

These functions read from and write to the PCI configuration register space. The bus, device, and function arguments can be read from the bus, device, and function fields of the pci_info structure provided by get_nth_pci_info(). They identify the configuration space that belongs to the device.

The offset is an offset to the location in the 256-byte configuration space that is to be read or written and size is the number of bytes to be read from that location or written to it. Permitted sizes are 1, 2, and 4 bytes. read_pci_config() returns the bytes that are read, write_pci_config() writes size bytes of value to the offset location.

See also: get_nth_pci_info()


release_spinlock() see acquire_spinlock()


restore_interrupts() see disable_interrupts()


set_dprintf_enabled() see dprintf()



set_io_interrupt_handler(), disable_io_interrupt(), enable_io_interrupt()

Declared in: <device/KernelExport.h>

      long set_io_interrupt_handler(long interrupt, 
         interrupt_handler function, void *data) 
      long disable_io_interrupt(long interrupt) 
      long enable_io_interrupt(long interrupt) 

These functions manage interrupts at the Be-designed I/O interrupt controller that combines ISA and PCI interrupts. The interrupt can be an ISA IRQ value or the interrupt_line field read from the pci_info structure provided by get_nth_pci_info().

set_io_interrupt_handler() installs the handler function that will be called each time the specified interrupt occurs. This function should have the following syntax:

bool handler(void *data)

The data that's passed to set_io_interrupt_handler() will be passed to the handler function each time it's called. It can be anything that might be of use to the handler, or NULL. This function should always return TRUE.

set_io_interrupt_handler() itself returns B_NO_ERROR if successful in installing the handler, and B_ERROR if not.

disable_io_interrupt() disables the named interrupt , and enable_io_interrupt() reenables it. Both functions return B_ERROR for an invalid interrupt number, and B_NO_ERROR otherwise. Neither function takes into account the disabled or enabled state of the interrupt as it might be affected by other functions, such as disable_isa_interrupt() or restore_interrupts(). An interrupt that has been disabled by disable_io_interrupt() must be reenabled by enable_io_interrupt() .

See also: get_nth_pci_info() , disable_interrupts()


set_isa_interrupt_handler(), disable_isa_interrupt(), enable_isa_interrupt()

Declared in: <device/KernelExport.h>

      long set_isa_interrupt_handler(long interrupt, 
         interrupt_handler function, void *data)
      long disable_isa_interrupt(long interrupt)
      long enable_isa_interrupt(long interrupt)

These functions manage interrupts at the 8259 ISA-compatible interrupt controller. The interrupt is identified by its standard IRQ value.

set_isa_interrupt_handler() installs the handler function for the specified interrupt. This function should take one argument and return a bool:

bool handler(void *data)

The argument is the same data that's passed to set_isa_interrupt_handler() ; it can be any kind of data the function might need, or NULL. The return value indicates whether the interrupt was handled-- TRUE if it was and FALSE if not. By returning FALSE, the handler function can indicate that the device didn't generate the interrupt. The system can then try a different handler installed for a different device at the same interrupt number. < However, this architecture is not currently supported, so the handler function should always return TRUE. >

set_isa_interrupt_handler() returns B_NO_ERROR if it can install the handler for the interrupt, and B_ERROR if not.

disable_isa_interrupt() disables the specified ISA interrupt, and enable_isa_interrupt() reenables it. Both functions return B_ERROR if the interrupt passed is not a valid IRQ value. Neither function considers whether the interrupt might be disabled or enabled by some other function, such as disable_io_interrupt() . An interrupt that has been disabled by disable_isa_interrupt() must be reenabled by enable_isa_interrupt().

ISA interrupts can also be managed at the Be-designed interrupt dispatcher that controls PCI interrupts. The Be interrupt controller is somewhat faster than the edge-sensitive ISA controller. If your device can generate a level-sensitive interrupt, it should use the counterpart set_io_interrupt_handler() function instead of set_isa_interrupt_handler(). However, if it depends on the edge-sensitive ISA interrupt controller widely found in the PC world, it needs to use these ISA functions.

See also: set_io_interrupt_handler() , disable_interrupts()


spawn_kernel_thread()

Declared in: <device/KernelExport.h>

      thread_id spawn_kernel_thread(thread_entry func, const char *name, 
         long priority, void *data)

This function is a counterpart to spawn_thread() in the Kernel Kit, which is not exported for drivers. It has the same syntax as the Kernel Kit function, but is able to spawn threads in the kernel itself.

See also: spawn_thread() in the Kernel Kit


spin()

Declared in: <device/KernelExport.h>

      void spin(double microseconds)

Executes a delay loop lasting at least the specified number of microseconds. It could last longer, due to rounding errors, interrupts, and context switches.


start_isa_dma(), start_scattered_isa_dma(), make_isa_dma_table()

Declared in: <device/KernelExport.h>

      long start_isa_dma(long channel, void *address, long transferCount, 
         uchar mode, uchar eMode)
      long start_scattered_isa_dma(long channel, isa_dma_entry *table, 
         uchar mode, uchar eMode)
      long make_isa_dma_table(void *address, long numBytes, 
         ulong numTransferBits, 
         isa_dma_entry *table, long numEntries) 

These functions initiate ISA DMA memory transfers for the specified channel. They engage the ISA 8237 DMA controller.

start_isa_dma() starts the transfer of a contiguous block of physical memory beginning at the specified address. It requests transferCount number of transfers, which cannot be greater than B_MAX_ISA_DMA_COUNT. Each transfer will move 8 or 16 bits of memory, depending on the mode and eMode flags. These arguments correspond to the mode and extended mode flags recognized by the DMA controller.

The physical memory address that's passed to start_isa_dma() can be obtained by calling get_memory_map() .

start_scattered_isa_dma() starts the transfer of a memory buffer that's physically scattered in various pieces. The separate pieces of memory are described by the table passed as a second argument and provided by make_isa_dma_table() .

make_isa_dma_table() provides a description of the separate chunks of physical memory that make up the contiguous virtual buffer that begins at address and extends for numBytes. This function anticipates a subsequent call to start_scattered_isa_dma() , which initiates a DMA transfer. It ensures that the information it provides is in the format expected by the 8237 DMA controller. This depends in part on how many bits will be transferred at a time. The third argument, numTransferBits, provides this information. It can be B_8_BIT_TRANSFER or B_16_BIT_TRANSFER.

Each chunk of physical memory is described by a isa_dma_entry structure, which contains the following fields (not that its arcane details matter, since you don't have to do anything with the information except pass it to start_scattered_isa_dma() ):

ulong address A physical memory address (in little endian format).
ushort transfer_count The number of transfers it will take to move all the physical memory at that address, minus 1 (in little endian format). This value won't be greater than B_MAX_ISA_DMA_COUNT .
int flags.end_of_list:1 A flag that's set to mark the last chunk of physical memory corresponding to the virtual buffer.

make_isa_dma_table() is passed a pointer to a table of isa_dma_entry structures. It fills in the table, stopping when the entire buffer of virtual memory has been described or when numEntry entries in the table have been written, whichever comes first. It returns the number of bytes from the virtual address buffer that it was able to account for in the table.

start_isa_dma() and start_scattered_isa_dma() both return B_NO_ERROR if successful in initiating the transfer, and B_ERROR if the channel isn't free.


unlock_isa_dma_channel() see lock_isa_dma_channel()


unlock_memory() see lock_memory()


write_pci_config() see read_pci_config()


xpt_init(), xpt_ccb_alloc() , xpt_ccb_free(), xpt_action() , xpt_bus_register(), xpt_bus_deregister()

Declared in: <device/CAM.h>

      long xpt_init (void)
      CCB_HEADER *xpt_ccb_alloc(void)
      void xpt_ccb_free(void *ccb)
      long xpt_action(CCB_HEADER *ccbHeader)
      long xpt_bus_register(CAM_SIM_ENTRY *entryPoints)
      long xpt_bus_deregister(long pathID)

These functions conform to the SCSI common access method (CAM) specification. See the draft ANSI standard SCSI-2 Common Access Method Transport and SCSI Interface Modules for information.

< The current implementation doesn't support asynchronous callback functions. All CAM requests are executed synchronously in their entirety. >




The Be Book, HTML Edition, for Developer Release 8 of the Be Operating System.

Copyright © 1996 Be, Inc. All rights reserved.

Be, the Be logo, BeBox, BeOS, BeWare, and GeekPort are trademarks of Be, Inc.

Last modified September 6, 1996.