# Device Drivers: Functions for Kernel Drivers

The kernel exports a number of functions that device drivers can call. The device driver accesses these functions directly in the kernel, not through a library.

Remember when writing a driver that calls one of these functions to link against either _KERNEL_.LIB (for x86) or a copy of the kernel named _KERNEL_ (for PPC). This will instruct the loader to dynamically locate the symbols in the current kernel when the driver is loaded.

### acquire_spinlock(), release_spinlock(), spinlock

Declared in: be/drivers/KernelExport.h

          typedef vlong spinlock

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

Spinlocks are mutually exclusive locks that are used to protect sections of code that must execute atomically. To create a spinlock, simply declare a `spinlock` variable and initialize it 0:

       spinlock lock = 0;

The functions acquire and release the lock spinlock. When you acquire and release a spinlock, you must have interrupts disabled; the structure of your code will look like this:

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

The spinlock should be held as briefly as possible, and acquisition must not be nested within the critical section.

Spinlocks are designed for use in a multi-processor system (on a single processor system simply turning off interrupts is enough to guarantee that the critical section will be atomic). Nonetheless, you can use spinlocks on a single processor--you don't have to predicate your code based on the number of CPUs in the system.

### add_debugger_command() see kernel_debugger()

### disable_interrupts(), restore_interrupts(), cpu_status

Declared in: be/drivers/KernelExport.h

          typedef ulong cpu_status

          cpu_status disable_interrupts(void)
          void restore_interrupts(cpu_status status)

These functions disable and restore interrupts on the CPU that the caller is currently running on. `disable_interrupts()` returns its previous state (i.e. whether or not interrupts were already disabled). `restore_interrupts()` restores the previous status of the CPU, which should be the value that `disable_interrupts()` returned:

       cpu_status former = disable_interrupts();
       ...
       restore_interrupts(former);

As long as the cpu state is properly restored (as shown here), the disable/restore functions can be nested.

See also: `install_io_interrupt_handler()`

### dprintf(), set_dprintf_enabled(), panic()

Declared in: be/drivers/KernelExport.h

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

          void panic(const char *format, ...)

`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, thus permitting intelligent nesting:

       /* Turn on dprintf */
       bool former = set_dprintf_enabled(true);
       ...
       /* Now restore it to its previous state. */
       set_dprintf_enabled(former);

`panic()` is similar to `dprintf()`, except it hangs the computer after printing the message.

### get_memory_map(), physical_entry

Declared in: be/drivers/KernelExport.h

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

          typedef struct {   void *address;
             ulong size;
             } physical_entry

Returns the physical memory chunks that map to the virtual memory that starts at address and extends for numBytes. Each chunk of physical memory is returned as a `physical_entry` structure; the series of structures is returned in the table array. (which you have to allocate yourself). numEntries is the number of elements in the array that you're passing in. As shown in the example, you should lock the memory that you're about to inspect:

       physical_entry table[count];
       lock_memory(addr, extent, 0);
       get_memory_map(addr, extent, table, count);
       . . .
       unlock_memory(someAddress, someNumberOfBytes, 0);

The end of the table array is indicated by (`size` == 0):

       long k;
       while (table[k].size > 0) {
          /* A legitimate entry */
          if (++k == count) {
             /* Not enough entries */
             break; }
       }

If all of the entries have non-zero sizes, then table wasn't big enough; call `get_memory_map()` again with more table entries.

RETURN CODES

The function always returns `B_NO_ERROR`.

See also: `lock_memory()`, `start_isa_dma()`

### has_signals_pending()

Declared in: <drivers/KernelExport.h>

          int has_signals_pending(struct thread_rec *thr)

Returns a bitmask of the currently pending signals for the current thread. thr should always be `NULL`; passing other values will yield meaningless results. `has_signals_pending()` returns 0 if no signals are pending.

### install_io_interrupt_handler(), remove_io_interrupt_handler()

Declared in: <drivers/KernelExport.h>

          long install_io_interrupt_handler(long interrupt_number, interrupt_handler handler,
             void *data, ulong flags)

          long remove_io_interrupt_handler(long interrupt_number,
             interrupt_handler handler)

`install_io_interrupt_handler()` adds the handler function to the chain of functions that will be called each time the specified interrupt occurs. This function should have the following syntax:

|  | bool `handler(`void *data`)`

The data passed to `install_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`. If the interrupt handler returns `true`, the system bypasses the remaining handlers in the interrupt chain; if it returns `false`, the next handler in the chain is dispatched.

The flags parameter is a bitmask of options. The only option currently defined is `B_NO_ENABLE_COUNTER`. By default, the OS keeps track of the number of functions handling a given interrupt. If this counter changes from 0 to 1, then the system enables the irq for that interrupt. Conversely, if the counter changes from 1 to 0, the system disables the irq. Setting the `B_NO_ENABLE_COUNTER` flag instructs the OS to ignore the handler for the purpose of enabling and disabling the irq.

`install_io_interrupt_handler()` returns `B_NO_ERROR` if successful in installing the handler, and `B_ERROR` if not. An error occurs when either the interrupt_number is out of range or there is not enough room left in the interrupt chain to add the handler.

`remove_io_interrupt()` removes the named interrupt from the interrupt chain. It returns `B_NO_ERROR` if successful in removing the handler, and `B_ERROR` if not.

### io_card_version() see motherboard_version()

### kernel_debugger() see dprintf()

### kernel_debugger(), add_debugger_command(), load_driver_symbols(), kprintf(), parse_expression()

Declared in: <drivers/KernelExport.h>

          void kernel_debugger(const char *string)
          int add_debugger_command(char *name, int (*func)(int, char **), char *help)
          int load_driver_symbols(const char *driverName)
          void kprintf(const char *format, ...)
          ulong parse_expression(const char *string)

`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()`.

`add_debugger_command()` registers a new command with the kernel debugger. When the user types in the command name, the kernel debugger calls func with the remainder of the command line as argc/argv-style arguments. The help string for the command is set to help.

`load_driver_symbols()` loads symbols from the specified kernel driver into the kernel debugger. driver_name is the path-less name of the driver which must be located in one of the standard kernel driver directories. The function returns `B_NO_ERROR` on success and `B_ERROR` on failure.

`kprintf()` outputs messages to the serial port. It should be used instead of `dprintf()` from new debugger commands because `dprintf()` depends too much upon the state of the kernel to be reliable from within the debugger.

`parse_expression()` takes a C expression and returns the result. It only handles integer arithmetic. The logical and relational operations are accepted. It can also supports variables and assignments. This is useful for strings with multiple expressions, which should be separated with semicolons. Finally, the special variable "." refers to the value from the previous expression. This function is designed to help implement new debugger commands.

See also: `debugger()` in the Kernel Kit

### kprintf() see kernel_debugger()

### load_driver_symbols() see kernel_debugger()

### lock_isa_dma_channel(), unlock_isa_dma_channel()

Declared in: <drivers/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 8 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
4 | Reserved by system, cannot be used
5 | Reserved for IDE
6 | Reserved for sound
7 | Reserved for sound

### lock_memory(), unlock_memory()

Declared in: <drivers/KernelExport.h>

          long lock_memory(void *address, ulong numBytes, ulong flags)

          long unlock_memory(const void *address, ulong numBytes, ulong flags)

`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. It is typically used in preparation for a DMA transaction.

The flags field contains a bitmask of options. Currently, two options, `B_DMA_IO` and `B_READ_DEVICE`, are defined. `B_DMA_IO` should be set if any part of the memory range will be modified by something other than the CPU while it's locked, since that change won't otherwise be noticed by the system and the modified pages may not be written to disk by the virtual memory system. Typically, this sort of change is performed through DMA. `B_READ_DEVICE`, if set, indicates that the caller intends to fill the memory (read from the device). If cleared, it indicates the memory will be written to the device and will not be altered.

`unlock_memory()` releases locked memory and should be called with the same flags as passed into the corresponding `lock_memory()` call.

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()

### map_physical_memory()

Declared in: <drivers/KernelExport.h>

          area_id map_physical_memory(const char *areaName, void *physicalAddress,
             size_t numBytes, uint32 spec, uint32 protection,
             void **virtualAddress)

This function allows you to map the memory in physical memory starting at physicalAddress and extending for numBytes bytes into your team's address space. The kernel creates an area named areaName mapped into the memory address virtualAddress and returns its area_id to the caller. numBytes must be a multiple of `B_PAGE_SIZE` (4096).

spec must be either `B_ANY_KERNEL_ADDRESS` or `B_ANY_KERNEL_BLOCK_ADDRESS`. If spec is `B_ANY_KERNEL_ADDRESS`, the memory will begin at an arbitrary location in the kernel address space. If spec is `B_ANY_KERNEL_BLOCK_ADDRESS`, then the memory will be mapped into a memory location aligned on a multiple of `B_PAGE_SIZE`.

protection is a bitmask consisting of the fields `B_READ_AREA` and `B_WRITE_AREA`, as discussed in `create_area()`.

create_area() returns an area_id for the newly-created memory if successful or an error code on failure. The error codes are the same as those for `create_area()`.

See also: `create_area()`

### motherboard_version(), io_card_version()

Declared in: <drivers/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.

### panic() see dprintf()

### parse_expression() see kernel_debugger()

### platform()

Declared in: <drivers/KernelExport.h>

          platform_type platform(void)

Returns the current platform, as defined in <kernel/OS.h>.

### ram_address()

Declared in: <drivers/KernelExport.h>

          void *ram_address(const 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_io_8(), write_io_8, read_io_16, write_io_16, read_io_32, write_io_32

Declared in: <drivers/KernelExport.h>

          uint8 read_io_8(int port)
          void write_io_8(int port, uint8 value)

          uint16 read_io_16(int port)
          void write_io_16(int port, uint16 value)

          uint32 read_io_32(int port)
          void write_io_32(int port, uint32 value)

These functions provide an interface for reading and writing from i/o ports.

### register_kernel_daemon(), unregister_kernel_daemon()

Declared in: <drivers/KernelExport.h>

          int register_kernel_daemon(void (*func)(void *, int), void *arg, int freq)

          int unregister_kernel_daemon(void (*func)(void *, int), void *arg)

Adds or removes daemons from the kernel. A kernel daemon function is executed approximately once every freq/10 seconds. The kernel calls func with the arguments arg and an iteration value that increases by freq on successive calls to the daemon function.

### release_spinlock() see acquire_spinlock()

### remove_io_interrupt_handler() see install_io_interrupt_handler()

### restore_interrupts() see disable_interrupts()

### set_dprintf_enabled() see dprintf()

### spawn_kernel_thread()

Declared in: <drivers/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: <drivers/KernelExport.h>

          void spin(bigtime_t 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(), isa_dma_entry

Declared in: <drivers/KernelExport.h>

          long start_isa_dma(long channel, void *address, long transferCount, uchar mode,
             uchar eMode)

          long start_scattered_isa_dma(long channel, const isa_dma_entry *table, uchar mode,
             uchar eMode)

          long make_isa_dma_table(const void *address, long numBytes,
             ulong numTransferBits, isa_dma_entry *table, long numEntries)

          struct {...} isa_dma_entry;

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()`):

Field | Meaning
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()

### unregister_kernel_daemon() see register_kernel_daemon()

### write_io_8() see read_io_8()

### write_io_16() see read_io_8()

### write_io_32() see read_io_8()

### write_pci_config() see read_pci_config()

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

Declared in: <drivers/CAM.h>

          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.

Although `xpt_init()` is exported by the kernel, your code never needs to call it; the BeOS does this for you at bootup.

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

The Be Book, in lovely HTML, for BeOS Release 3.

Copyright © 1998 Be, Inc. All rights reserved.

Last modified March 27, 1998.