INTRO(9F) Kernel Functions for Drivers INTRO(9F)
NAME
Intro - Introduction to kernel and device driver functions
SYNOPSIS
#include <sys/ddi.h> #include <sys/sunddi.h>DESCRIPTION
Section 9F of the manual page describes functions that are used for
device drivers, kernel modules, and the implementation of the kernel
itself. This first provides an overview for the use of kernel
functions and portions of the manual that are specific to the kernel.
After that, we have grouped together most functions that are available
by use, with some brief commentary and introduction.
Most manual pages are similar to those in other sections. They have
common fields such as the NAME, a SYNOPSIS to show which header files
to include and prototypes, an extended DESCRIPTION discussing its use,
and the common combination of RETURN VALUES and ERRORS. Some manuals
will have examples and additional manuals to reference in the SEE ALSO
section.
RETURN VALUES and ERRORS
One major difference when programming in the kernel versus userland is
that there is no equivalent to
errno. Instead, there are a few common
patterns that are used throughout the kernel that we'll discuss. While
there are common patterns, please be aware that due to the natural
evolution of the system, you will need to read the specifics of the
section.
+o Many functions will return a specific DDI (Device Driver Interface)
value, which is commonly one of DDI_SUCCESS or DDI_FAILURE,
indicating success and failure respectively. Some functions will
return additional error codes to indicate why something failed. In
general, when checking a response code is always preferred to
compare that something equals or does not equal DDI_SUCCESS as
there can be many different error cases and additional ones can be
added over time.
+o Many routines explicitly return
0 on success and will return an
explicit error number.
Intro(2) has a list of error numbers.
+o There are classes of functions that return either a pointer or a
boolean type, either the C99
bool or the system's traditional type
boolean_t. In these cases, sometimes a more detailed error is
provided via an additional argument such as a
int *. Absent such
an argument, there is generally no more detailed information
available.
CONTEXT
The CONTEXT section of a manual page describes the times in which this
function may be called. In generally there are three different
contexts that come up:
User User context implies that the thread of execution is operating
because a user thread has entered the kernel for an operation.
When an application issues a system call such as
open(2),
read(2),
write(2), or
ioctl(2) then we are said to be in user
context. When in user context, one can copy in or out data
from a user's address space. When writing a character or block
device driver, the majority of the time that a character device
operation such as the corresponding
open(9E),
read(9E),
write(9E), and
ioctl(9E) entry point being called, it is
executing in user context. It is possible to call those entry
points through the kernel's layered device interface, so
drivers cannot assume those entry points will always have a
user process present, strictly speaking.
Interrupt Interrupt context refers to when the operating system is
handling an interrupt (See
Interrupt Related Functions) and
executing a registered interrupt handler. Interrupt context is
split into two different sets: high-level and low-level
interrupts. Most device drivers are always going to be
executing low-level interrupts. To determine whether an
interrupt is considered high level or not, you should pass the
interrupt handle to the
ddi_intr_get_pri(9F) function and
compare the resulting priority with
ddi_intr_get_hilevel_pri(9F).
When executing high-level interrupts, the thread may only
execute a limited number of functions. In particular, it may
call
ddi_intr_trigger_softint(9F),
mutex_enter(9F), and
mutex_exit(9F). It is critical that the mutex being used be
properly initialized with the driver's interrupt priority. The
system will transparently pick the correct implementation of a
mutex based on the interrupt type. Aside from the above, one
must not block while in high-level interrupt context.
On the other hand, when a thread is not in high-level interrupt
context, most of these restrictions are lifted. Kernel memory
may be allocated (if using a non-blocking allocation such as
KM_NOSLEEP or KM_NOSLEEP_LAZY), and many of the other
documented functions may be called.
Regardless of whether a thread is in high-level or low-level
interrupt context, it will never have a user context associated
with it and therefore cannot use routines like
ddi_copyin(9F) or
ddi_copyout(9F).
Kernel Kernel context refers to all other times in the kernel.
Whenever the kernel is executing something on a thread that is
not associated with a user process, then one is in kernel
context. The most common situation for writers of kernel
modules are things like timeout callbacks, such as
timeout(9F) or
ddi_periodic_add(9F), cases where the kernel is invoking a
driver's device operation routines such as
attach(9E) and
detach(9E), or many of the device driver's registered callbacks
from frameworks such as the
mac(9E),
usba_hcdi(9E), and various
portions of SCSI, USB, and block devices.
Framework-specific Contexts Some manuals will discuss more specific constraints about when
they can be used. For example, some functions may only be
called while executing a specific entry point like
attach(9E).
Another example of this is that the
mac_transceiver_info_set_present(9F) function is only meant to
be used while executing a networking driver's
mct_info(9E) entry point.
PARAMETERS
In kernel manual pages (section 9), each function and entry point
description generally has a separate list of parameters which are
arguments to the function. The parameters section describes the basic
purpose of each argument and should explain where such things often
come from and any constraints on their values.
INTERFACES
Functions below are organized into categories that describe their
purpose. Individual functions are documented in their own manual
pages. For each of these areas, we discuss high-level concepts behind
each area and provide a brief discussion of how to get started with it.
Note, some deprecated functions or older frameworks are not listed
here.
Every function listed below has its own manual page in section 9F and
can be read with
man(1). In addition, some corresponding concepts are
documented in section 9 and some groups of functions are present to
support a specific type of device driver, which is discussed more in
section 9E .
Logging Functions
Through the kernel there are often needs to log messages that either
make it into the system log or on the console. These kinds of messages
can be performed with the
cmn_err(9F) function or one of its more
specific variants that operate in the context of a device (
dev_err(9F))
or a zone (
zcmn_err(9F)).
The console should be used sparingly. While a notice may be found
there, one should assume that it may be missed either due to overflow,
not being connected to say a serial console at the time, or some other
reason. While the system log is better than the console, folks need to
take care not to spam the log. Imagine if someone logged every time a
network packet was generated or received, you'd quickly potentially run
out of space and make it harder to find useful messages for bizarre
behavior. It's also important to remember that only system
administrators and privileged users can actually see this log. Where
possible and appropriate use programmatic errors in routines that allow
it.
The system also supports a structured event log called a system event
that is processed by
syseventd(8). This is used by the OS to provide
notifications for things like device insertion and removal or the
change of a data link. These are driven by the
ddi_log_sysevent(9F) function and allow arbitrary additional structured metadata in the form
of a
nvlist_t.
cmn_err(9F) dev_err(9F) vcmn_err(9F) vzcmn_err(9F) zcmn_err(9F) ddi_log_sysevent(9F) Memory Allocation
At the heart of most device drivers is memory allocation. The primary
kernel allocator is called "kmem" (kernel memory) and it is based on
the "vmem" (virtual memory) subsystem. Most of the time, device
drivers should use
kmem_alloc(9F) and
kmem_zalloc(9F) to allocate
memory and free it with
kmem_free(9F). Based on the original kmem and
subsequent vmem papers, the kernel is internally using object caches
and magazines to allow high-throughput allocation in a multi-CPU
environment.
When allocating memory, an important choice must be made: whether or
not to block for memory. If one opts to perform a sleeping allocation,
then the caller can be guaranteed that the allocation will succeed, but
it may take some time and the thread will be blocked during that entire
duration. This is the KM_SLEEP flag. On the other hand, there are
many circumstances where this is not appropriate, especially because a
thread that is inside a memory allocation function cannot currently be
cancelled. If the thread corresponds to a user process, then it will
not be killable.
Given that there are many situations where this is not appropriate, the
kernel offers an allocation mode where it will not block for memory to
be available: KM_NOSLEEP and KM_NOSLEEP_LAZY. These allocations can
fail and return NULL when they do fail. Even though these are said to
be no sleep operations, that does not mean that the caller may not end
up temporarily blocked due to mutex contention or due to trying a bit
more aggressively to reclaim memory in the case of KM_NOSLEEP. Unless
operating in special circumstances, using KM_NOSLEEP_LAZY should be
preferred to KM_NOSLEEP.
If a device driver has its own complex object that has more significant
set up and tear down costs, then the kmem cache function family should
be considered. To use a kmem cache, it must first be created using the
kmem_cache_create(9F) function, which requires specifying the size,
alignment, and constructors and destructors. Individual objects are
allocated from the cache with the
kmem_cache_alloc(9F) function. An
important constraint when using the caches is that when an object is
freed with
kmem_cache_free(9F), it is the callers responsibility to
ensure that the object is returned to its constructed state prior to
freeing it. If the object is reused, prior to the kernel reclaiming
the memory for other uses, then the constructor will not be called
again. Most device drivers do not need to create a kmem cache for
their own allocations.
If you are writing a device driver that is trying to interact with the
networking, STREAMS, or USB subsystems, then they are generally using
the
mblk_t data structure which is managed through a different set of
APIs, though they are leveraging kmem under the hood.
The vmem set of interfaces allows for the management of abstract
regions of integers, generally representing memory or some other
object, each with an offset and length. While it is not common that a
device driver needs to do their own such management,
vmem_create(9F) and
vmem_alloc(9F) are what to reach for when the need arises. Rather
than using vmem, if one needs to model a set of integers where each is
a valid identifier, that is you need to allocate every integer between
0 and 1000 as a distinct identifier, instead use
id_space_create(9F) which is discussed in
Identifier Management. For more information on
vmem, see
vmem(9).
kmem_alloc(9F) kmem_cache_alloc(9F) kmem_cache_create(9F) kmem_cache_destroy(9F) kmem_cache_free(9F) kmem_cache_set_move(9F) kmem_free(9F) kmem_zalloc(9F) vmem_add(9F) vmem_alloc(9F) vmem_contains(9F) vmem_create(9F) vmem_destroy(9F) vmem_free(9F) vmem_size(9F) vmem_walk(9F) vmem_xalloc(9F) vmem_xcreate(9F) vmem_xfree(9F) bufcall(9F) esbbcall(9F) qbufcall(9F) qunbufcall(9F) unbufcall(9F) String and libc Analogues
The kernel has many analogues for classic libc functions that deal with
string processing, memory copying, bit manipulation, and related. For
the most part, these behave similarly to their userland analogues, but
there can be some differences in return values and for example, in the
set of supported format characters in the case of
snprintf(9F) and
related.
ASSERT(9F) bcmp(9F) bzero(9F) bcopy(9F) ddi_strdup(9F) ddi_strtol(9F) ddi_strtoll(9F) ddi_strtoul(9F) ddi_strtoull(9F) ddi_ffs(9F) ddi_fls(9F) max(9F) memchr(9F) memcmp(9F) memcpy(9F) memmove(9F) memset(9F) min(9F) numtos(9F) snprintf(9F) sprintf(9F) stoi(9F) stdc_bit_ceil(9F) stdc_bit_floor(9F) stdc_bit_width(9F) stdc_count_ones(9F) stdc_count_zeros(9F) stdc_first_leading_one(9F) stdc_first_leading_zero(9F) stdc_first_trailing_one(9F) stdc_first_trailing_zero(9F) stdc_has_single_bit(9F) stdc_leading_ones(9F) stdc_leading_zeros(9F) stdc_trailing_ones(9F) stdc_trailing_zeros(9F) strcasecmp(9F) strcat(9F) strchr(9F) strcmp(9F) strcpy(9F) strdup(9F) strfree(9F) string(9F) strlcat(9F) strlcpy(9F) strlen(9F) strlog(9F) strncasecmp(9F) strncat(9F) strncmp(9F) strncpy(9F) strnlen(9F) strqget(9F) strqset(9F) strrchr(9F) strspn(9F) swab(9F) vsnprintf(9F) va_arg(9F) va_copy(9F) va_end(9F) va_start(9F) vsprintf(9F) Tree Data Structures
These functions provide access to an intrusive self-balancing binary
tree that is generally used throughout illumos. The primary type here
is the
avl_tree_t. Structures can be present in multiple trees and
there are built-in walkers for the data structure in
mdb(1).
avl_add(9F) avl_create(9F) avl_destroy_nodes(9F) avl_destroy(9F) avl_find(9F) avl_first(9F) avl_insert_here(9F) avl_insert(9F) avl_is_empty(9F) avl_last(9F) avl_nearest(9F) AVL_NEXT(9F) avl_numnodes(9F) AVL_PREV(9F) avl_remove(9F) avl_swap(9F) Linked Lists
These functions provide a standard, intrusive doubly-linked list whose
type is the
list_t. This list implementation is used extensively
throughout illumos, has debugging support through
mdb(1) walkers, and
is generally recommended rather than creating your own list. Due to
its intrusive nature, a given structure can be present on multiple
lists.
list_create(9F) list_destroy(9F) list_head(9F) list_insert_after(9F) list_insert_before(9F) list_insert_head(9F) list_insert_tail(9F) list_is_empty(9F) list_link_active(9F) list_link_init(9F) list_link_replace(9F) list_move_tail(9F) list_next(9F) list_prev(9F) list_remove_head(9F) list_remove_tail(9F) list_remove(9F) list_tail(9F) Name-Value Pairs The kernel often uses the
nvlist_t data structure to pass around a list
of typed name-value pairs. This data structure is used in diverse
areas, particularly because of its ability to be serialized in
different formats that are suitable not only for use between userland
and the kernel, but also persistently to a file.
A
nvlist_t structure is initialized with the
nvlist_alloc(9F) function
and can operate with two different degrees of uniqueness: a mode where
only names are unique or that every name is qualified to a type. The
former means that if I have an integer name "foo" and then add a
string, array, or any other value with the same name, it will be
replaced. However, if were using the name and type as unique, then the
value would only be replaced if both the pair's type and the name "foo"
matched a pair that was already present. Otherwise, the two different
entries would co-exist.
When constructing an nvlist, it is normally backed by the normal kmem
allocator and may either use sleeping or non-sleeping allocations. It
is also possible to use a custom allocator, though that generally has
not been necessary in the kernel.
Specific keys and values can be looked up directly with the
nvlist_lookup family of functions, but the entire list can be iterated
as well, which is especially useful when trying to validate that no
unknown keys are present in the list. The iteration API
nvlist_next_nvpair(9F) allows one to then get both the key's name, the
type of value of the pair, and then the value itself.
nv_alloc_fini(9F) nv_alloc_init(9F) nvlist_add_boolean_array(9F) nvlist_add_boolean_value(9F) nvlist_add_boolean(9F) nvlist_add_byte_array(9F) nvlist_add_byte(9F) nvlist_add_int16_array(9F) nvlist_add_int16(9F) nvlist_add_int32_array(9F) nvlist_add_int32(9F) nvlist_add_int64_array(9F) nvlist_add_int64(9F) nvlist_add_int8_array(9F) nvlist_add_int8(9F) nvlist_add_nvlist_array(9F) nvlist_add_nvlist(9F) nvlist_add_nvpair(9F) nvlist_add_string_array(9F) nvlist_add_string(9F) nvlist_add_uint16_array(9F) nvlist_add_uint16(9F) nvlist_add_uint32_array(9F) nvlist_add_uint32(9F) nvlist_add_uint64_array(9F) nvlist_add_uint64(9F) nvlist_add_uint8_array(9F) nvlist_add_uint8(9F) nvlist_alloc(9F) nvlist_dup(9F) nvlist_exists(9F) nvlist_free(9F) nvlist_lookup_boolean_array(9F) nvlist_lookup_boolean_value(9F) nvlist_lookup_boolean(9F) nvlist_lookup_byte_array(9F) nvlist_lookup_byte(9F) nvlist_lookup_int16_array(9F) nvlist_lookup_int16(9F) nvlist_lookup_int32_array(9F) nvlist_lookup_int32(9F) nvlist_lookup_int64_array(9F) nvlist_lookup_int64(9F) nvlist_lookup_int8_array(9F) nvlist_lookup_int8(9F) nvlist_lookup_nvlist_array(9F) nvlist_lookup_nvlist(9F) nvlist_lookup_nvpair(9F) nvlist_lookup_pairs(9F) nvlist_lookup_string_array(9F) nvlist_lookup_string(9F) nvlist_lookup_uint16_array(9F) nvlist_lookup_uint16(9F) nvlist_lookup_uint32_array(9F) nvlist_lookup_uint32(9F) nvlist_lookup_uint64_array(9F) nvlist_lookup_uint64(9F) nvlist_lookup_uint8_array(9F) nvlist_lookup_uint8(9F) nvlist_merge(9F) nvlist_next_nvpair(9F) nvlist_pack(9F) nvlist_remove_all(9F) nvlist_remove(9F) nvlist_size(9F) nvlist_t(9F) nvlist_unpack(9F) nvlist_xalloc(9F) nvlist_xdup(9F) nvlist_xpack(9F) nvlist_xunpack(9F) nvpair_name(9F) nvpair_type(9F) nvpair_value_boolean_array(9F) nvpair_value_byte_array(9F) nvpair_value_byte(9F) nvpair_value_int16_array(9F) nvpair_value_int16(9F) nvpair_value_int32_array(9F) nvpair_value_int32(9F) nvpair_value_int64_array(9F) nvpair_value_int64(9F) nvpair_value_int8_array(9F) nvpair_value_int8(9F) nvpair_value_nvlist_array(9F) nvpair_value_nvlist(9F) nvpair_value_string_array(9F) nvpair_value_string(9F) nvpair_value_uint16_array(9F) nvpair_value_uint16(9F) nvpair_value_uint32_array(9F) nvpair_value_uint32(9F) nvpair_value_uint64_array(9F) nvpair_value_uint64(9F) nvpair_value_uint8_array(9F) nvpair_value_uint8(9F) Identifier Management
A common challenge in the kernel is the management of a series of
different IDs. There are three different families of routines for
managing identifiers presented here, but we recommend the use of the
id_space_create(9F) and
id_alloc(9F) family for new use cases. The ID
space can cover all or a subset of the 32-bit integer space and
provides different allocation strategies for this.
Due to the current implementation, callers should generally prefer the
non-sleeping variants because the sleeping ones are not cancellable
(currently this is backed by vmem, but this should not be assumed and
may change in the future).
id_alloc_nosleep(9F) id_alloc_specific_nosleep(9F) id_alloc(9F) id_allocff_nosleep(9F) id_allocff(9F) id_free(9F) id_space_create(9F) id_space_destroy(9F) id_space_extend(9F) id_space(9F) id32_alloc(9F) id32_free(9F) id32_lookup(9F) rmalloc_wait(9F) rmalloc(9F) rmallocmap_wait(9F) rmallocmap(9F) rmfree(9F) rmfreemap(9F) Bit Manipulation Routines
Many device drivers that are working with registers often need to get a
specific range of bits out of an integer. These functions provide safe
ways to set (bitset) and extract (bitx) bit ranges, as well as modify
an integer to remove a set of bits entirely (bitdel). Using these
functions is preferred to constructing manual masks and shifts
particularly when a programming manual for a device is specified in
ranges of bits. On debug builds, these provide extra checking to try
and catch programmer error.
bitdel64(9F) bitset8(9F) bitset16(9F) bitset32(9F) bitset64(9F) bitx8(9F) bitx16(9F) bitx32(9F) bitx64(9F) Synchronization Primitives
The kernel provides a set of basic synchronization primitives that can
be used by the system. These include mutexes, condition variables,
reader/writer locks, and semaphores. When creating mutexes and
reader/writer locks, the kernel requires that one pass in the interrupt
priority of a mutex if it will be used in interrupt context. This is
required so the kernel can determine the correct underlying type of
lock to use. This ensures that if for some reason a mutex needs to be
used in high-level interrupt context, the kernel will use a spin lock,
but otherwise can use the standard adaptive mutex that might block.
For developers familiar with other operating systems, this is somewhat
different in that the consumer does not need to generally figure out
this level of detail and this is why this is not present.
In addition, condition variables provide means for waiting and
detecting that a signal has been delivered. These variants are
particularly useful when writing character device operations for device
drivers as it allows users the chance to cancel an operation and not be
blocked indefinitely on something that may not occur. These _sig
variants should generally be preferred where applicable.
The kernel also provides memory barrier primitives. See the
Memory Barriers section for more information. There is no need to use manual
memory barriers when using the synchronization primitives. The
synchronization primitives contain that the appropriate barriers are
present to ensure coherency while the lock is held.
cv_broadcast(9F) cv_destroy(9F) cv_init(9F) cv_reltimedwait_sig(9F) cv_reltimedwait(9F) cv_signal(9F) cv_timedwait_sig(9F) cv_timedwait(9F) cv_wait_sig(9F) cv_wait(9F) ddi_enter_critical(9F) ddi_exit_critical(9F) mutex_destroy(9F) mutex_enter(9F) mutex_exit(9F) mutex_init(9F) mutex_owned(9F) mutex_tryenter(9F) rw_destroy(9F) rw_downgrade(9F) rw_enter(9F) rw_exit(9F) rw_init(9F) rw_read_locked(9F) rw_tryenter(9F) rw_tryupgrade(9F) sema_destroy(9F) sema_init(9F) sema_p_sig(9F) sema_p(9F) sema_tryp(9F) sema_v(9F) semaphore(9F) Atomic Operations
This group of functions provides a general way to perform atomic
operations on integers of different sizes and explicit types. The
atomic_ops(9F) manual page describes the different classes of functions
in more detail, but there are functions that take care of using the
CPU's instructions for addition, compare and swap, and more. If data
is being protected and only accessed under a synchronization primitive
such as a mutex or reader-writer lock, then there isn't a reason to use
an atomic operation for that data, generally speaking.
atomic_add_8_nv(9F) atomic_add_8(9F) atomic_add_16_nv(9F) atomic_add_16(9F) atomic_add_32_nv(9F) atomic_add_32(9F) atomic_add_64_nv(9F) atomic_add_64(9F) atomic_add_char_nv(9F) atomic_add_char(9F) atomic_add_int_nv(9F) atomic_add_int(9F) atomic_add_long_nv(9F) atomic_add_long(9F) atomic_add_ptr_nv(9F) atomic_add_ptr(9F) atomic_add_short_nv(9F) atomic_add_short(9F) atomic_and_8_nv(9F) atomic_and_8(9F) atomic_and_16_nv(9F) atomic_and_16(9F) atomic_and_32_nv(9F) atomic_and_32(9F) atomic_and_64_nv(9F) atomic_and_64(9F) atomic_and_uchar_nv(9F) atomic_and_uchar(9F) atomic_and_uint_nv(9F) atomic_and_uint(9F) atomic_and_ulong_nv(9F) atomic_and_ulong(9F) atomic_and_ushort_nv(9F) atomic_and_ushort(9F) atomic_cas_16(9F) atomic_cas_32(9F) atomic_cas_64(9F) atomic_cas_8(9F) atomic_cas_ptr(9F) atomic_cas_uchar(9F) atomic_cas_uint(9F) atomic_cas_ulong(9F) atomic_cas_ushort(9F) atomic_clear_long_excl(9F) atomic_dec_8_nv(9F) atomic_dec_8(9F) atomic_dec_16_nv(9F) atomic_dec_16(9F) atomic_dec_32_nv(9F) atomic_dec_32(9F) atomic_dec_64_nv(9F) atomic_dec_64(9F) atomic_dec_ptr_nv(9F) atomic_dec_ptr(9F) atomic_dec_uchar_nv(9F) atomic_dec_uchar(9F) atomic_dec_uint_nv(9F) atomic_dec_uint(9F) atomic_dec_ulong_nv(9F) atomic_dec_ulong(9F) atomic_dec_ushort_nv(9F) atomic_dec_ushort(9F) atomic_inc_8_nv(9F) atomic_inc_8(9F) atomic_inc_16_nv(9F) atomic_inc_16(9F) atomic_inc_32_nv(9F) atomic_inc_32(9F) atomic_inc_64_nv(9F) atomic_inc_64(9F) atomic_inc_ptr_nv(9F) atomic_inc_ptr(9F) atomic_inc_uchar_nv(9F) atomic_inc_uchar(9F) atomic_inc_uint_nv(9F) atomic_inc_uint(9F) atomic_inc_ulong_nv(9F) atomic_inc_ulong(9F) atomic_inc_ushort_nv(9F) atomic_inc_ushort(9F) atomic_or_8_nv(9F) atomic_or_8(9F) atomic_or_16_nv(9F) atomic_or_16(9F) atomic_or_32_nv(9F) atomic_or_32(9F) atomic_or_64_nv(9F) atomic_or_64(9F) atomic_or_uchar_nv(9F) atomic_or_uchar(9F) atomic_or_uint_nv(9F) atomic_or_uint(9F) atomic_or_ulong_nv(9F) atomic_or_ulong(9F) atomic_or_ushort_nv(9F) atomic_or_ushort(9F) atomic_set_long_excl(9F) atomic_swap_8(9F) atomic_swap_16(9F) atomic_swap_32(9F) atomic_swap_64(9F) atomic_swap_ptr(9F) atomic_swap_uchar(9F) atomic_swap_uint(9F) atomic_swap_ulong(9F) atomic_swap_ushort(9F) Memory Barriers
The kernel provides general purpose memory barriers that can be used
when required. In general, when using items described in the
Synchronization Primitives section, these are not required.
membar_consumer(9F) membar_enter(9F) membar_exit(9F) membar_producer(9F) Virtual Memory and Pages
All platforms that the operating system supports have some form of
virtual memory which is managed in units of pages. The page size
varies between architectures and platforms. For example, the smallest
x86 page size is 4 KiB while SPARC traditionally used 8 KiB pages.
These functions can be used to convert between pages and bytes.
btop(9F) btopr(9F) ddi_btop(9F) ddi_btopr(9F) ddi_ptob(9F) ptob(9F) Module and Device Framework
These functions are used as part of implementing kernel modules and
register device drivers with the various kernel frameworks. There are
also functions here that are suitable for use in the
dev_ops(9S),
cb_ops(9S), etc. structures and for interrogating module information.
The
mod_install(9F) and
mod_remove(9F) functions are used during a
driver's _
init(9E) and _
fini(9E) functions.
There are two different ways that drivers often manage their instance
state which is created during
attach(9E). The first is the use of
ddi_set_driver_private(9F) and
ddi_get_driver_private(9F). This stores
a driver-specific value on the
dev_info_t structure which allows it to
be used during other operations. Some device driver frameworks may use
this themselves, making this unavailable to the driver.
The other path is to use the soft state suite of functions which
dynamically grows to cover the number of instances of a device that
exist. The soft state is generally initialized in the _
init(9E) entry
point with
ddi_soft_state_init(9F) and then instances are allocated and
freed during
attach(9E) and
detach(9E) with
ddi_soft_state_zalloc(9F) and
ddi_soft_state_free(9F), and then retrieved with
ddi_get_soft_state(9F).
ddi_get_driver_private(9F) ddi_get_soft_state(9F) ddi_modclose(9F) ddi_modopen(9F) ddi_modsym(9F) ddi_no_info(9F) ddi_report_dev(9F) ddi_set_driver_private(9F) ddi_soft_state_fini(9F) ddi_soft_state_free(9F) ddi_soft_state_init(9F) ddi_soft_state_zalloc(9F) mod_info(9F) mod_install(9F) mod_modname(9F) mod_remove(9F) nochpoll(9F) nodev(9F) nulldev(9F) Device Tree Information
Devices are organized into a tree that is partially seeded by the
platform based on information discovered at boot and augmented with
additional information at runtime. Every instance of a device driver
is given a
dev_info_t * (device information) data structure which
corresponds to information about an instance and has a place in the
tree. When a driver requests operations like to allocate memory for
DMA, that request is passed up the tree and modified. The same is true
for other things like interrupts, event notifications, or properties.
There are many different informational properties about a device
driver. For example,
ddi_driver_name(9F) returns the name of the
device driver,
ddi_get_name(9F) returns the name of the node in the
tree,
ddi_get_parent(9F) returns a node's parent, and
ddi_get_instance(9F) returns the instance number of a specific driver.
There are a series of properties that exist on the tree, the exact set
of which depend on the class of the device and are often documented in
a specific device class's manual. For example, the "reg" property is
used for PCI and PCIe devices to describe the various base address
registers, their types, and related, which are documented in
pci(5).
When getting a property one can constrain it to the current instance or
you can ask for a parent to try to look up the property. Which mode is
appropriate depends on the specific class of driver, its parent, and
the property.
Using a
dev_info_t * pointer has to be done carefully. When a device
driver is in any of its
dev_ops(9S),
cb_ops(9S), or similar callback
functions that it has registered with the kernel, then it can always
safely use its own
dev_info_t and those of any parents it discovers
through
ddi_get_parent(9F). However, it cannot assume the validity of
any siblings or children unless there are other circumstances that
guarantee that they will not disappear. In the broader kernel, one
should not assume that it is safe to use a given
dev_info_t * structure
without the appropriate NDI (nexus driver interface) hold having been
applied.
ddi_binding_name(9F) ddi_dev_is_sid(9F) ddi_driver_major(9F) ddi_driver_name(9F) ddi_get_devstate(9F) ddi_get_instance(9F) ddi_get_name(9F) ddi_get_parent(9F) ddi_getlongprop_buf(9F) ddi_getlongprop(9F) ddi_getprop(9F) ddi_getproplen(9F) ddi_node_name(9F) ddi_prop_create(9F) ddi_prop_exists(9F) ddi_prop_free(9F) ddi_prop_get_int(9F) ddi_prop_get_int64(9F) ddi_prop_lookup_byte_array(9F) ddi_prop_lookup_int_array(9F) ddi_prop_lookup_int64_array(9F) ddi_prop_lookup_string_array(9F) ddi_prop_lookup_string(9F) ddi_prop_lookup(9F) ddi_prop_modify(9F) ddi_prop_op(9F) ddi_prop_remove_all(9F) ddi_prop_remove(9F) ddi_prop_undefine(9F) ddi_prop_update_byte_array(9F) ddi_prop_update_int_array(9F) ddi_prop_update_int(9F) ddi_prop_update_int64_array(9F) ddi_prop_update_int64(9F) ddi_prop_update_string_array(9F) ddi_prop_update_string(9F) ddi_prop_update(9F) ddi_root_node(9F) ddi_slaveonly(9F) Copying Data to and from Userland
The kernel operates in a different context from userland. One does not
simply access user memory. This is enforced either by the
architecture's memory model, where user address space isn't even
present in the kernel's virtual address space or by architectural
mechanisms such as Supervisor Mode Access Protect (SMAP) on x86.
To facilitate accessing memory, the kernel provides a few routines that
can be used. In most contexts the main thing to use is
ddi_copyin(9F) and
ddi_copyout(9F). These will safely dereference addresses and
ensure that the address is appropriate depending on whether this is
coming from the user or kernel. When operating with the kernel's
uio_t structure which is for mostly used when processing read and write
requests, instead
uiomove(9F) is the goto function.
When reading data from userland into the kernel, there is another
concern: the data model. The most common place this comes up is in an
ioctl(9E) handler or other places where the kernel is operating on data
that isn't fixed size. Particularly in C, though this applies to other
languages, structures and unions vary in the size and alignment
requirements between 32-bit and 64-bit processes. The same even
applies if one uses pointers or the
long,
size_t, or similar types in
C. In supported 32-bit and 64-bit environments these types are 4 and 8
bytes respectively. To account for this, when data is not fixed size
between all data models, the driver must look at the data model of the
process it is copying data from.
The simplest way to solve this problem is to try to make the data
structure the same across the different models. It's not sufficient to
just use the same structure definition and fixed size types as the
alignment and padding between the two can vary. For example, the
alignment of a 64-bit integer like a
uint64_t can change between a
32-bit and 64-bit data model. One way to check for the data structures
being identical is to leverage the
ctfdiff(1) program, generally with
the
-I option.
However, there are times when a structure simply can't be the same,
such as when we're encoding a pointer into the structure or a type like
the
size_t. When this happens, the most natural way to accomplish this
is to use the
ddi_model_convert_from(9F) function which can determine
the appropriate model from the ioctl's arguments. This provides a
natural way to copy a structure in and out in the appropriate data
model and convert it at those points to the kernel's native form.
An alternate way to approach the data model is to use the
STRUCT_DECL(9F) functions, but as this requires wrapping every access
to every member, often times the
ddi_model_convert_from(9F) approach
and taking care of converting values and ensuring that limits aren't
exceeded at the end is preferred.
bp_copyin(9F) bp_copyout(9F) copyin(9F) copyout(9F) ddi_copyin(9F) ddi_copyout(9F) ddi_model_convert_from(9F) SIZEOF_PTR(9F) SIZEOF_STRUCT(9F) STRUCT_BUF(9F) STRUCT_DECL(9F) STRUCT_FADDR(9F) STRUCT_FGET(9F) STRUCT_FGETP(9F) STRUCT_FSET(9F) STRUCT_FSETP(9F) STRUCT_HANDLE(9F) STRUCT_INIT(9F) STRUCT_SET_HANDLE(9F) STRUCT_SIZE(9F) uiomove(9F) ureadc(9F) uwritec(9F) Device Register Setup and Access
The kernel abstracts out accessing registers on a device on behalf of
drivers. This allows a similar set of interfaces to be used whether
the registers are found within a PCI BAR, utilizing I/O ports, memory
mapped registers, or some other scheme. Devices with registers all
have a "regs" property that is set up by their parent device, generally
a kernel framework as is the case for PCIe devices, and the meaning is
a contract between the two. Register sets are identified by a numeric
ID, which varies on the device type. For example, the first BAR of a
PCI device is defined as register set 1. On the other hand, the AMD
GPIO controller might have three register sets because of how the
hardware design splits them up. The meaning of the registers and their
semantics is still device-specific. The kernel doesn't know how to
interpret the actual registers of a PCIe device say, just that they
exist.
To begin with register setup, one often first looks at the number of
register sets that exist and their size. Most PCI-based device drivers
will skip calling
ddi_dev_nregs(9F) and will just move straight to
calling
ddi_dev_regsize(9F) to determine the size of a register set
that they are interested in. To actually map the registers, a device
driver will call
ddi_regs_map_setup(9F) which requires both a register
set and a series of attributes and returns an access handle that is
used to actually read and write the registers. When setting up
registers, one must have a corresponding
ddi_device_acc_attr_t structure which is used to define what endianness the register set is
in, whether any kind of reordering is allowed (if in doubt specify
DDI_STRICTORDER_ACC), and whether any particular error handling is
being used. The structure and all of its different options are
described in
ddi_device_acc_attr(9S).
Once a register handle is obtained, then it's easy to read and write
the register space. Functions are organized based on the size of the
access. For the most part, most situations call for the use of the
ddi_get8(9F),
ddi_get16(9F),
ddi_get32(9F), and
ddi_get64(9F) functions
to read a register and the
ddi_put8(9F),
ddi_put16(9F),
ddi_put32(9F),
and
ddi_put64(9F) functions to set a register value. While there are
the ddi_io_ and ddi_mem_ families of functions below, these are not
generally needed and are generally present for compatibility. The
kernel will automatically perform the appropriate type of register read
for the device type in question.
Once a register set is no longer being used, the
ddi_regs_map_free(9F) function should be used to release resources. In most cases, this
happens while executing the
detach(9E) entry point.
ddi_dev_nregs(9F) ddi_dev_regsize(9F) ddi_device_copy(9F) ddi_device_zero(9F) ddi_regs_map_free(9F) ddi_regs_map_setup(9F) ddi_get8(9F) ddi_get16(9F) ddi_get32(9F) ddi_get64(9F) ddi_io_get8(9F) ddi_io_get16(9F) ddi_io_get32(9F) ddi_io_put8(9F) ddi_io_put16(9F) ddi_io_put32(9F) ddi_io_rep_get8(9F) ddi_io_rep_get16(9F) ddi_io_rep_get32(9F) ddi_io_rep_put8(9F) ddi_io_rep_put16(9F) ddi_io_rep_put32(9F) ddi_map_regs(9F) ddi_mem_get8(9F) ddi_mem_get16(9F) ddi_mem_get32(9F) ddi_mem_get64(9F) ddi_mem_put8(9F) ddi_mem_put16(9F) ddi_mem_put32(9F) ddi_mem_put64(9F) ddi_mem_rep_get8(9F) ddi_mem_rep_get16(9F) ddi_mem_rep_get32(9F) ddi_mem_rep_get64(9F) ddi_mem_rep_put8(9F) ddi_mem_rep_put16(9F) ddi_mem_rep_put32(9F) ddi_mem_rep_put64(9F) ddi_peek8(9F) ddi_peek16(9F) ddi_peek32(9F) ddi_peek64(9F) ddi_poke8(9F) ddi_poke16(9F) ddi_poke32(9F) ddi_poke64(9F) ddi_put8(9F) ddi_put16(9F) ddi_put32(9F) ddi_put64(9F) ddi_rep_get8(9F) ddi_rep_get16(9F) ddi_rep_get32(9F) ddi_rep_get64(9F) ddi_rep_put8(9F) ddi_rep_put16(9F) ddi_rep_put32(9F) ddi_rep_put64(9F) DMA Related Functions
Most high-performance devices provide first-class support for DMA
(direct memory access). DMA allows a transfer between a device and
memory to occur asynchronously and generally without a thread's
specific involvement. Today, most DMA is provided directly by devices
and the corresponding device scheme. Take PCI and PCI Express for
example. The idea of DMA is built into the PCIe standard and therefore
basic support for it exists and therefore there isn't a lot of special
programming required. However, this hasn't always been true and still
exists in some cases where there is a 3rd party DMA engine. If we
consider the PCIe example, the PCIe device directly performs reads and
writes to main memory on its own. However, in the 3rd party case,
there is a distinct controller that is neither the device nor memory
that facilitates this, which is called a DMA engine. For most part,
DMA engines are not something that needs to be thought about for most
platforms that illumos is present on; however, they still exist in some
embedded and related contexts.
The first thing that a driver needs to do to set up DMA is to
understand the constraints of the device and bus. These constraints
are described in a series of attributes in the
ddi_dma_attr_t structure
which is defined in
ddi_dma_attr(9S). The reason that attributes exist
is because different devices, and sometimes different memory uses with
a device, have different requirements for memory. A simple example of
this is that not all devices can accept memory addresses that are
64-bits wide and may have to be constrained to the lower 32-bits of
memory. Another common constraint is how this memory is chunked up.
Some devices may require that all of the DMA memory be contiguous,
while others can allow that to be broken up into say up to 4 or 8
different regions.
When memory is allocated for DMA it isn't immediately mapped into the
kernel's address space. The addresses that describe a DMA address are
defined in a DMA cookie, several of which may make up a request.
However, those addresses are always physical addresses or addresses
that are virtualized by an IOMMU. There are some cases were the kernel
or a driver needs to be able to access that memory, such as memory that
represents a networking packet. The IP stack will expect to be able to
actually read the data it's given.
To begin with allocating DMA memory, a driver first fills out its
attribute structure. Once that's ready, the DMA allocation process can
begin. This starts off by a driver calling
ddi_dma_alloc_handle(9F).
This handle is used through the lifetime of a given DMA memory buffer,
but it can be used across multiple operations that a device or the
kernel may perform. The next step is to actually request that the
kernel allocate some amount of memory in the kernel for this DMA
request. This phase actually allocates addresses in virtual address
space for the activity and also requires a register attribute object
that is discussed in
Device Register Setup and Access. Armed with this
a driver can now call
ddi_dma_mem_alloc(9F) to specify how much memory
they are looking for. If this is successful, a virtual address, the
actual length of the region, and an access handle will be returned.
At this point, the virtual address region is present. Most drivers
will access this virtual address range directly and will ignore the
register access handle. The side effect of this is that they will
handle all endianness issues with the memory region themselves. If the
driver would prefer to go through the handle, then it can use the
register access functions discussed earlier.
Before the memory can be programmed into the device, it must be bound
to a series of physical addresses or addresses virtualized by an IOMMU.
While the kernel presents the illusion of a single consistent virtual
address range for applications, the physical reality can be quite
different. When the driver is ready it calls
ddi_dma_addr_bind_handle(9F) to create the mapping to well known
physical addresses.
These addresses are stored in a series of cookies. A driver can
determine the number of cookies for a given request by utilizing its
DMA handle and calling
ddi_dma_ncookies(9F) and then pairing that with
ddi_dma_cookie_get(9F). These DMA cookies will not change and can be
used time and time again until
ddi_dma_unbind_handle(9F) is called.
With this information in hand, a physical device can be programmed with
these addresses and let loose to perform I/O.
When performing I/O to and from a device, synchronization is a vitally
important thing which ensures that the actual state in memory is
coherent with the rest of the CPU's internal structures such as caches.
In general, a given DMA request is only going in one direction: for a
device or for the local CPU. In either case, the
ddi_dma_sync(9F) function must be called after the kernel is done writing to a region of
DMA memory and before it triggers the device or the kernel must call it
after the device has told it that some activity has completed that it
is going to check.
Some DMA operations utilize what are called DMA windows. The most
common consumer is something like a disk device where DMA operations to
a given series of sectors can be split up into different chunks where
as long as all the transfers are performed, the intermediate states are
acceptable. Put another way, because of how SCSI and SAS commands are
designed, block devices can basically take a given I/O request and
break it into multiple independent I/Os that will equate to the same
final item.
When a device supports this mode of operation and it is opted into,
then a DMA allocation may result in the use of DMA windows. This
allows for cases where the kernel can't perform a DMA allocation for
the entire request, but instead can allocate a partial region and then
walk through each part one at a time. This is uncommon outside of
block devices and usually also is related to calling
ddi_dma_buf_bind_handle(9F).
ddi_dma_addr_bind_handle(9F) ddi_dma_alloc_handle(9F) ddi_dma_buf_bind_handle(9F) ddi_dma_burstsizes(9F) ddi_dma_cookie_get(9F) ddi_dma_cookie_iter(9F) ddi_dma_cookie_one(9F) ddi_dma_free_handle(9F) ddi_dma_getwin(9F) ddi_dma_mem_alloc(9F) ddi_dma_mem_free(9F) ddi_dma_ncookies(9F) ddi_dma_nextcookie(9F) ddi_dma_numwin(9F) ddi_dma_set_sbus64(9F) ddi_dma_sync(9F) ddi_dma_unbind_handle(9F) ddi_dmae_1stparty(9F) ddi_dmae_alloc(9F) ddi_dmae_disable(9F) ddi_dmae_enable(9F) ddi_dmae_getattr(9F) ddi_dmae_getcnt(9F) ddi_dmae_prog(9F) ddi_dmae_release(9F) ddi_dmae_stop(9F) ddi_dmae(9F) Interrupt Handler Related Functions
Interrupts are a central part of the role of device drivers and one of
the things that's important to get right. Interrupts come in different
types: fixed, MSI, and MSI-X. The kinds that are available depend on
the device and the rest of the system. For example, MSI and MSI-X
interrupts are generally specific to PCI and PCI Express devices. To
begin the interrupt allocation process, the first thing a driver needs
to do is to discover what type of interrupts it supports with
ddi_intr_get_supported_types(9F). Then, the driver should work through
the supported types, preferring MSI-X, then MSI, and finally fixed
interrupts, and try to allocate interrupts.
Drivers first need to know how many interrupts that they require. For
example, a networking driver may want to have an interrupt made
available for each ring that it has. To discover the number of
interrupts available, the driver should call
ddi_intr_get_navail(9F).
If there are sufficient interrupts, it can proceed to actually allocate
the interrupts with
ddi_intr_alloc(9F). When allocating interrupts,
callers need to check to see how many interrupts the system actually
gave them. Just because an interrupt is allocated does not mean that
it will fire or be ready to use, there are a series of additional steps
that the driver must take.
To go through and enable the interrupt, the driver should go through
and get the interrupt capabilities with
ddi_intr_get_cap(9F) and the
priority of the interrupt with
ddi_intr_get_pri(9F). The priority must
be used while creating mutexes and related synchronization primitives
that will be used during the interrupt handler. At this point, the
driver can go ahead and register the functions that will be called with
each allocated interrupt with the
ddi_intr_add_handler(9F) function.
The arguments can vary for each allocated interrupt. It is common to
have an interrupt-specific data structure passed in one of the
arguments or an interrupt number, while the other argument is generally
the driver's instance-specific data structure.
At this point, the last step for the interrupt to be made active from
the kernel's perspective is to enable it. This will use either the
ddi_intr_block_enable(9F) or
ddi_intr_enable(9F) functions depending on
the interrupt's capabilities. The reason that these are different is
because some interrupt types (MSI) require that all interrupts in a
group be enabled and disabled at the same time. This is indicated with
the DDI_INTR_FLAG_BLOCK flag found in the interrupt's capabilities.
Once that is called, interrupts that are generated by a device will be
delivered to the registered function.
It's important to note that there is often device-specific interrupt
setup that is required. While the kernel takes care of updating any
pieces of the processor's interrupt controller, I/O crossbar, or the
PCI MSI and MSI-X capabilities, many devices have device-specific
registers that are used to manage, set up, and acknowledge interrupts.
These registers or other controls are often capable of separately
masking interrupts and are generally what should be used if there are
times that you need to separately enable or disable interrupts such as
to poll an I/O ring.
When unwinding interrupts, one needs to work in the reverse order here.
Until
ddi_intr_block_disable(9F) or
ddi_intr_disable(9F) is called, one
should assume that their interrupt handler will be called. Due to
cases where an interrupt is shared between multiple devices, this can
happen even if the device is quiesced! Only after that is done is it
safe to then free the interrupts with a call to
ddi_intr_free(9F).
ddi_add_intr(9F) ddi_add_softintr(9F) ddi_get_iblock_cookie(9F) ddi_get_soft_iblock_cookie(9F) ddi_intr_add_handler(9F) ddi_intr_add_softint(9F) ddi_intr_alloc(9F) ddi_intr_block_disable(9F) ddi_intr_block_enable(9F) ddi_intr_clr_mask(9F) ddi_intr_disable(9F) ddi_intr_dup_handler(9F) ddi_intr_enable(9F) ddi_intr_free(9F) ddi_intr_get_cap(9F) ddi_intr_get_hilevel_pri(9F) ddi_intr_get_navail(9F) ddi_intr_get_nintrs(9F) ddi_intr_get_pending(9F) ddi_intr_get_pri(9F) ddi_intr_get_softint_pri(9F) ddi_intr_get_supported_types(9F) ddi_intr_hilevel(9F) ddi_intr_remove_handler(9F) ddi_intr_remove_softint(9F) ddi_intr_set_cap(9F) ddi_intr_set_mask(9F) ddi_intr_set_nreq(9F) ddi_intr_set_pri(9F) ddi_intr_set_softint_pri(9F) ddi_intr_trigger_softint(9F) ddi_remove_intr(9F) ddi_remove_softintr(9F) ddi_trigger_softintr(9F) Minor Nodes
For a device driver to be accessed by a program in user space (or with
the kernel layered device interface) then it must create a minor node.
Minor nodes are created under
/devices (
devfs(4FS)) and are tied to the
instance of a device driver via its
dev_info_t. The
devfsadm(8) daemon
and the
/dev file system (sdev,
dev(4FS)) are responsible for creating
a coherent set of names that user programs access. Drivers create
these minor nodes using the
ddi_create_minor_node(9F) function listed
below.
In UNIX tradition, character, block, and STREAMS device special files
are identified by a major and minor number. All instances of a given
driver share the same major number, which means that a device driver
must coordinate the minor number space across
all instances. While a
minor node is created with a fixed minor number, it is possible to
change the minor number while processing an
open(9E) call, allowing
subsequent character device operations to uniquely identify a
particular caller. This is usually referred to as a driver that
"clones".
When drivers aren't performing cloning, then usually the minor number
used when creating the minor node is some fixed offset or multiple of
the driver's instance number. When cloning and a driver needs to
allocate and manage a minor number space, usually an ID space is
leveraged whose IDs are usually in the range from 0 through MAXMIN32.
There are several different strategies for tracking data structures as
they relate to minor numbers. Sometimes, the soft state functionality
is used. Others might keep an AVL tree around or tie the data to some
other data structure. The method chosen often varies on the specifics
of the implementation and its broader context.
The
dev_t structure represents the combined major and minor number. It
can be taken apart with the
getmajor(9F) and
getminor(9F) functions and
then reconstructed with the
makedevice(9F) function.
ddi_create_minor_node(9F) ddi_remove_minor_node(9F) getmajor(9F) getminor(9F) devfs_clean(9F) makedevice(9F) Accessing Time, Delays, and Periodic Events The kernel provides a number of ways to understand time in the system.
In particular it provides a few different clocks and time measurements:
High-resolution monotonic time
The kernel provides access to a high-resolution monotonic clock
that is tracked in nanoseconds. This clock is perfect for
measuring durations and is accessed via
gethrtime(9F). Unlike
the real-time clock, this clock is not subject to adjustments
by a time synchronization daemon and is the preferred clock
that drivers should be using for tracking events. The high-
resolution clock is consistent across CPUs, meaning that you
may call
gethrtime(9F) on one CPU and the value will be
consistent with what is returned, even if a thread is migrated
to another CPU.
The high-resolution clock is implemented using an architecture
and platform-specific means. For example, on x86 it is
generally backed by the TSC (time stamp counter).
Real-time
The real-time clock tracks time as humans perceive it. This
clock is accessed using
ddi_get_time(9F). If the system is
running a time synchronization daemon that leverages the
network time protocol, then this time may be in sync with other
systems (subject to some amount of variance); however, it is
critical that this is not assumed.
In general, this time should not be used by drivers for any
purpose. It can jump around, drift, and most aspects in the
kernel are not based on the real-time clock. For any device
timing activities, the high-resolution clock should be used.
Tick-based monotonic time
The kernel has a running periodic function that fires based on
the rate dictated by the
hz variable, generally operating at
100 or 1000 kHz. The current number of ticks since boot is
accessible through the
ddi_get_lbolt(9F) function. When
functions operate in units of ticks, this is what they are
tracking. This value can be converted to and from microseconds
using the
drv_usectohz(9F) and
drv_hztousec(9F) functions.
In general, drivers should prefer the high-resolution monotonic
clock for tracking events internally.
With these different timing mechanisms, the kernel provides a few
different ways to delay execution or to get a callback after some
amount of time passes.
The
delay(9F) and
drv_usecwait(9F) functions are used to block the
execution of the current thread.
delay(9F) can be used in conditions
where sleeping and blocking is allowed where as
drv_usecwait(9F) is a
busy-wait, which is appropriate for some device drivers, particularly
when in high-level interrupt context.
The kernel also allows a function to be called after some time has
elapsed. This callback occurs on a different thread and will be
executed in
kernel context. A timeout can be scheduled in the future
with the
timeout(9F) function and cancelled with the
untimeout(9F) function. There is also a STREAMs-specific version that can be used if
the circumstances are required with the
qtimeout(9F) function.
These are all considered one-shot events. That is, they will only
happen once after being scheduled. If instead, a driver requires
periodic behavior, such as needing something to occur every second,
then it should use the
ddi_periodic_add(9F) function to establish that.
delay(9F) ddi_get_lbolt(9F) ddi_get_lbolt64(9F) ddi_get_time(9F) ddi_periodic_add(9F) ddi_periodic_delete(9F) drv_hztousec(9F) drv_usectohz(9F) drv_usecwait(9F) gethrtime(9F) qtimeout(9F) quntimeout(9F) timeout(9F) untimeout(9F) Task Queues
A task queue provides an asynchronous processing mechanism that can be
used by drivers and the broader system. A task queue can be created
with
ddi_taskq_create(9F) and sized with a given number of threads and
a relative priority of those threads. Once created, tasks can be
dispatched to the queue with
ddi_taskq_dispatch(9F). The different
functions and arguments dispatched do not need to be the same and can
vary from invocation to invocation. However, it is the caller's
responsibility to ensure that any reference memory is valid until the
task queue is done processing. It is possible to create a barrier for
a task queue by using the
ddi_taskq_wait(9F) function.
While task queues are a flexible mechanism for handling and processing
events that occur in a well defined context, they do not have an
inherent backpressure mechanism built in. This means it is possible to
add events to a task queue faster than they can be processed. For
high-volume events, this must be considered before just dispatching an
event. Do not rely on a non-sleeping allocation in the task queue
dispatch context.
ddi_taskq_create(9F) ddi_taskq_destroy(9F) ddi_taskq_dispatch(9F) ddi_taskq_resume(9F) ddi_taskq_suspend(9F) ddi_taskq_suspended(9F) ddi_taskq_wait
Credential Management and Privileges
Not everything in the system has the same power to impact it. To
determine the permissions and context of a caller, the
cred_t data
structure encapsulates a number of different things including the
traditional user and group IDs, but also the zone that one is operating
in the context of and the associated privileges that the caller has.
While this concept is more often thought of due to userland processes
being associated with specific users, these same principles apply to
different threads in the kernel. Not all kernel threads are allowed to
indiscriminately do what they want, they can be constrained by the same
privilege model that processes are, which is discussed in
privileges(7).
Most operations that device drivers implement are given a credential.
However, from within the kernel, a credential can be obtained that
refers to a specific zone, the current process, or a generic kernel
credential.
It is up to drivers and the kernel writ-large to check whether a given
credential is authorized to perform a given operation. This is
encapsulated by the various privilege checks that exist. The most
common check used is
drv_priv(9F) which checks for PRIV_SYS_DEVICES.
CRED(9F) crdup(9F) crfree(9F) crget(9F) crgetgid(9F) crgetgroups(9F) crgetngroups(9F) crgetrgid(9F) crgetruid(9F) crgetsgid(9F) crgetsuid(9F) crgetuid(9F) crgetzoneid(9F) crhold(9F) ddi_get_cred(9F) drv_priv(9F) kcred(9F) priv_getbyname(9F) priv_policy_choice(9F) priv_policy_only(9F) priv_policy(9F) zone_kcred(9F) Device ID Management
Device IDs are a means of establishing a unique ID for a device in the
kernel. These unique IDs are generally tied to something from the
device's hardware such as a serial number or related, but can also be
fabricated and stored on the device. These device IDs are used by
other subsystems like ZFS to record information about a device as the
actual
/devices path that a device resides at may change because it is
moved around in the system.
For device drivers, particularly those that represent block devices,
they should first call
ddi_devid_init(9F) to initialize the device ID
data structure. After that is done, it is then safe to call
ddi_devid_register(9F) to notify the kernel about the ID.
ddi_devid_compare(9F) ddi_devid_free(9F) ddi_devid_get(9F) ddi_devid_init(9F) ddi_devid_register(9F) ddi_devid_sizeof(9F) ddi_devid_str_decode(9F) ddi_devid_str_encode(9F) ddi_devid_str_free(9F) ddi_devid_unregister(9F) ddi_devid_valid(9F) Message Block Functions
The
mblk_t data structure is used to chain together messages which are
used through the kernel for different subsystems including all of
networking, terminals, STREAMS, USB, and more.
Message blocks are chained together by a series of two different
pointers:
b_cont and
b_next. When a message is split across multiple
data buffers, they are linked by the
b_cont pointer. However, multiple
distinct messages can be chained together and linked by the
b_next pointer. Let's look at this in the context of a series of networking
packets. If we had a chain of say 10 UDP packets that we were given,
each UDP packet is considered an independent message and would be
linked from one to the next based on the order they should be
transmitted with the
b_next pointer. However, an individual message
may be entirely in one message block, in which case its
b_cont pointer
would be NULL, but if say the packet were split into a 100 byte data
buffer that contained the headers and then a 1000 byte data buffer that
contained the actual packet data, those two would be linked together by
b_cont. A continued message would never have its next pointer used to
link it to a wholly different message. Visually you might see this as:
+---------------+
| UDP Message 0 |
| Bytes 0-1100 |
| b_cont ---+--> NULL
| b_next + |
+---------|-----+
|
v
+---------------+ +----------------+
| UDP Message 1 | | UDP Message 1+ |
| Bytes 0-100 | | Bytes 100-1100 |
| b_cont ---+--> | b_cont ----+->NULL
| b_next + | | b_next ----+->NULL
+---------|-----+ +----------------+
|
...
|
v
+---------------+
| UDP Message 9 |
| Bytes 0-1100 |
| b_cont ---+--> NULL
| b_next ---+--> NULL
+---------------+
Message blocks all have an associated data block which contains the
actual data that is present. Multiple message blocks can share the
same data block as well. The data block has a notion of a type, which
is generally M_DATA which signifies that they operate on data.
To allocate message blocks, one generally uses the
allocb(9F) function
to create one; however, you can also create message blocks using your
own source of data through functions like
desballoc(9F). This is
generally used when one wants to use memory that was originally used
for DMA to pass data back into the kernel, such as in a networking
device driver. When this happens, a callback function will be called
once the last user of the data block is done with it.
The functions listed below often end in either "msg" or "b" to indicate
that they will operate on an entire message and follow the
b_cont pointer or they will not respectively.
adjmsg(9F) allocb(9F) copyb(9F) copymsg(9F) datamsg(9F) desballoc(9F) desballoca(9F) dupb(9F) dupmsg(9F) esballoc(9F) esballoca(9F) freeb(9F) freemsg(9F) linkb(9F) mcopymsg(9F) msgdsize(9F) msgpullup(9F) msgsize(9F) pullupmsg(9F) rmvb(9F) testb(9F) unlinkb(9F) Upgradable Firmware Modules
The UFM (Upgradable Firmware Module) subsystem is used to grant the
system observability into firmware that exists persistently on a
device. These functions are intended for use by drivers that are
participating in the kernel's UFM framework, which is discussed in
ddi_ufm(9E).
The
ddi_ufm_init(9F) and
ddi_ufm_fini(9F) functions are used to
indicate support of the subsystem to the kernel. The driver is
required to use the
ddi_ufm_update(9F) function to indicate both that
it is ready to receive UFM requests and to indicate that any data that
the kernel may have previously received has changed. Once that's
completed, then the other functions listed here are generally used as
part of implementing specific callback functions that are registered.
ddi_ufm_fini(9F) ddi_ufm_image_set_desc(9F) ddi_ufm_image_set_misc(9F) ddi_ufm_image_set_nslots(9F) ddi_ufm_init(9F) ddi_ufm_slot_set_attrs(9F) ddi_ufm_slot_set_imgsize(9F) ddi_ufm_slot_set_misc(9F) ddi_ufm_slot_set_version(9F) ddi_ufm_update(9F) Firmware Loading
Some hardware devices have firmware that is not stored as part of the
device itself and must instead be sent to the device each time it is
powered on. These routines help drivers that need to perform this read
such data from the file system from well-known locations in the
operating system. To begin with, a driver should call
firmware_open(9F) to open a handle to the firmware file. At that
point, one can determine the size of the file with the
firmware_get_size(9F) function and allocate the appropriate sized
memory buffer to read it in. Callers should always check what the size
of the returned file is and should not just blindly pass that size off
to the kernel memory allocator. For example, if a file was over 100
MiB in size, then one should not assume that they're going to just
blindly allocate 100 MiB of kernel memory and should instead perform
incremental reads and sends to a device that are smaller in size.
A driver can then go through and perform arbitrary reads of the
firmware file through the
firmware_read(9F) interface until they have
read everything that they need. Once complete, the corresponding
handle needs to be released through the
firmware_close(9F) function.
firmware_close(9F) firmware_get_size(9F) firmware_open(9F) firmware_read(9F) Fault Management Handling
These functions allow device drivers to harden themselves against
errors that might occur while interfacing with devices and tie into the
broader fault management architecture.
To begin, a driver must declare which capabilities it implements during
its
attach(9E) function by calling
ddi_fm_init(9F). The set of
capabilities it receives back may be less than what was requested
because the capabilities are dependent on the overall chain of drivers
present.
If DDI_FM_EREPORT_CAPABLE was negotiated, then the driver is expected
to generate error events when certain conditions occur using the
ddi_fm_ereport_post(9F) function or the more specific
pci_ereport_post(9F) function. If a caller has negotiated
DDI_FM_ACCCHK_CAPABLE, then it is allowed to set up its register
attributes to indicate that it will check for errors on the register
handle after using functions like
ddi_get8(9F) and
ddi_put8(9F) by
calling
ddi_fm_acc_err_get(9F) and reacting accordingly. Similarly, if
a driver has negotiated DDI_FM_DMACHK_CAPABLE, then it will use
ddi_check_dma_handle(9F) to check the results of DMA activity and
handle the results appropriately. Similar to register accesses, the
DMA attributes must be updated to set that error handling is
anticipated on this handle. The
ddi_fm_init(9F) manual page has an
overview of the other types of flags that can be negotiated and how
they are used.
ddi_check_acc_handle(9F) ddi_check_dma_handle(9F) ddi_dev_report_fault(9F) ddi_fm_acc_err_clear(9F) ddi_fm_acc_err_get(9F) ddi_fm_capable(9F) ddi_fm_dma_err_clear(9F) ddi_fm_dma_err_get(9F) ddi_fm_ereport_post(9F) ddi_fm_fini(9F) ddi_fm_handler_register(9F) ddi_fm_handler_unregister(9F) ddi_fm_init(9F) ddi_fm_service_impact(9F) pci_ereport_post(9F) pci_ereport_setup(9F) pci_ereport_teardown(9F) SCSI and SAS Device Driver Functions
These functions are for use by SCSI and SAS device drivers that
leverage the kernel's frameworks. Other device drivers should not use
these. For more background on these, some of the general concepts are
discussed in
iport(9),
phymap(9), and
tgtmap(9).
Device drivers register initially with the kernel by using the
scsi_hba_init(9F) function and then, in their attach routine, register
specific instances, using functions like
scsi_hba_iport_register(9F) or
instead
scsi_hba_tran_alloc(9F) and
scsi_hba_attach_setup(9F). New
drivers are encouraged to use the target map and iports framework to
simplify the device driver writing process.
makecom_g0_s(9F) makecom_g0(9F) makecom_g1(9F) makecom_g5(9F) makecom(9F) sas_phymap_create(9F) sas_phymap_destroy(9F) sas_phymap_lookup_ua(9F) sas_phymap_lookup_uapriv(9F) sas_phymap_phy_add(9F) sas_phymap_phy_rem(9F) sas_phymap_phy2ua(9F) sas_phymap_phys_free(9F) sas_phymap_phys_next(9F) sas_phymap_ua_free(9F) sas_phymap_ua2phys(9F) sas_phymap_uahasphys(9F) scsi_abort(9F) scsi_address_device(9F) scsi_alloc_consistent_buf(9F) scsi_cname(9F) scsi_destroy_pkt(9F) scsi_device_hba_private_get(9F) scsi_device_hba_private_set(9F) scsi_device_unit_address(9F) scsi_dmafree(9F) scsi_dmaget(9F) scsi_dname(9F) scsi_errmsg(9F) scsi_ext_sense_fields(9F) scsi_find_sense_descr(9F) scsi_free_consistent_buf(9F) scsi_free_wwnstr(9F) scsi_get_device_type_scsi_options(9F) scsi_get_device_type_string(9F) scsi_hba_attach_setup(9F) scsi_hba_detach(9F) scsi_hba_fini(9F) scsi_hba_init(9F) scsi_hba_iport_exist(9F) scsi_hba_iport_find(9F) scsi_hba_iport_register(9F) scsi_hba_iport_unit_address(9F) scsi_hba_iportmap_create(9F) scsi_hba_iportmap_destroy(9F) scsi_hba_iportmap_iport_add(9F) scsi_hba_iportmap_iport_remove(9F) scsi_hba_lookup_capstr(9F) scsi_hba_pkt_alloc(9F) scsi_hba_pkt_comp(9F) scsi_hba_pkt_free(9F) scsi_hba_probe(9F) scsi_hba_tgtmap_create(9F) scsi_hba_tgtmap_destroy(9F) scsi_hba_tgtmap_scan_luns(9F) scsi_hba_tgtmap_set_add(9F) scsi_hba_tgtmap_set_begin(9F) scsi_hba_tgtmap_set_end(9F) scsi_hba_tgtmap_set_flush(9F) scsi_hba_tgtmap_tgt_add(9F) scsi_hba_tgtmap_tgt_remove(9F) scsi_hba_tran_alloc(9F) scsi_hba_tran_free(9F) scsi_ifgetcap(9F) scsi_ifsetcap(9F) scsi_init_pkt(9F) scsi_log(9F) scsi_mname(9F) scsi_pktalloc(9F) scsi_pktfree(9F) scsi_poll(9F) scsi_probe(9F) scsi_resalloc(9F) scsi_reset_notify(9F) scsi_reset(9F) scsi_resfree(9F) scsi_rname(9F) scsi_sense_asc(9F) scsi_sense_ascq(9F) scsi_sense_cmdspecific_uint64(9F) scsi_sense_info_uint64(9F) scsi_sense_key(9F) scsi_setup_cdb(9F) scsi_slave(9F) scsi_sname(9F) scsi_sync_pkt(9F) scsi_transport(9F) scsi_unprobe(9F) scsi_unslave(9F) scsi_validate_sense(9F) scsi_vu_errmsg(9F) scsi_wwn_to_wwnstr(9F) scsi_wwnstr_to_wwn
Block Device Buffer Handling
Block devices operate with a data structure called the
struct buf which
is described in
buf(9S). This structure is used to represent a given
block request and is used heavily in block devices, the SCSI/SAS
framework, and the blkdev framework. The functions described here are
used to manipulate these structures in various ways such as copying
them around, indicating error conditions, or indicating when the I/O
operation is done. By default, this memory is not mapped into the
kernel's address space so several functions such as
bp_mapin(9F) are
present to allow for that to happen when required.
To initially obtain a
struct buf, drivers should begin by calling
getrbuf(9F) at which point, the caller can fill in the structure. Once
that's done, the
physio(9F) function can be used to actually perform
the I/O and wait until it's complete.
bioclone(9F) biodone(9F) bioerror(9F) biofini(9F) bioinit(9F) biomodified(9F) bioreset(9F) biosize(9F) biowait(9F) bp_mapin(9F) bp_mapout(9F) clrbuf(9F) disksort(9F) freerbuf(9F) geterror(9F) getrbuf(9F) minphys(9F) physio(9F) Networking Device Driver Functions
These functions are for networking device drivers that implant the MAC,
GLDv3 interfaces. The full framework and how to use it is described in
mac(9E).
mac_alloc(9F) mac_fini_ops(9F) mac_free(9F) mac_hcksum_get(9F) mac_hcksum_set(9F) mac_init_ops(9F) mac_link_update(9F) mac_lso_get(9F) mac_maxsdu_update(9F) mac_prop_info_set_default_fec(9F) mac_prop_info_set_default_link_flowctrl(9F) mac_prop_info_set_default_str(9F) mac_prop_info_set_default_uint32(9F) mac_prop_info_set_default_uint64(9F) mac_prop_info_set_default_uint8(9F) mac_prop_info_set_perm(9F) mac_prop_info_set_range_uint32(9F) mac_prop_info(9F) mac_register(9F) mac_rx(9F) mac_rx_ring(9F) mac_transceiver_info_set_present(9F) mac_transceiver_info_set_usable(9F) mac_transceiver_info(9F) mac_tx_ring_update(9F) mac_tx_update(9F) mac_unregister(9F) USB Device Driver Functions
These functions are designed for USB device drivers. To first
initialize with the kernel, a device driver must call
usb_client_attach(9F) and then
usb_get_dev_data(9F). The latter call
is required to get access to the USB-level descriptors about the device
which describe what kinds of USB endpoints (control, bulk, interrupt,
or isochronous) exist on the device as well as how many different
interfaces and configurations are present.
Once a given configuration, sometimes the default, is selected, then
the driver can proceed to opening up what the USB architecture calls a
pipe, which provides a way to send requests to a specific USB endpoint.
First, specific endpoints can be looked up using the
usb_lookup_ep_data(9F) function which gets information from the parsed
descriptors and then that gets filled into an extended descriptor with
usb_ep_xdescr_fill(9F). With that in hand, a pipe can be opened with
usb_pipe_xopen(9F).
Once a pipe has been opened, which most often happens in a driver's
attach(9E) entry point, then requests can be allocated and submitted.
There is a different allocation for each type of request (e.g.
usb_alloc_bulk_req(9F)) and a different submission function for each
type as well. Each request structure has a corresponding page in
section 9S that describes the structure, its members, and how to work
with it.
One other major concern for USB devices, which isn't as common with
other types of devices, is that they can be yanked out and reinserted
at any time. To help determine when this happens, the kernel offers
the
usb_register_event_cbs(9F) function which allows a driver to
register for callbacks when a device is disconnected, reconnected, or
around checkpoint suspend/resume behavior.
usb_alloc_bulk_req(9F) usb_alloc_ctrl_req(9F) usb_alloc_intr_req(9F) usb_alloc_isoc_req(9F) usb_alloc_request(9F) usb_client_attach(9F) usb_client_detach(9F) usb_clr_feature(9F) usb_create_pm_components(9F) usb_ep_xdescr_fill(9F) usb_free_bulk_req(9F) usb_free_ctrl_req(9F) usb_free_descr_tree(9F) usb_free_dev_data(9F) usb_free_intr_req(9F) usb_free_isoc_req(9F) usb_get_addr(9F) usb_get_alt_if(9F) usb_get_cfg(9F) usb_get_current_frame_number(9F) usb_get_dev_data(9F) usb_get_if_number(9F) usb_get_max_pkts_per_isoc_request(9F) usb_get_status(9F) usb_get_string_descr(9F) usb_handle_remote_wakeup(9F) usb_lookup_ep_data(9F) usb_owns_device(9F) usb_parse_data(9F) usb_pipe_bulk_xfer(9F) usb_pipe_close(9F) usb_pipe_ctrl_xfer_wait(9F) usb_pipe_ctrl_xfer(9F) usb_pipe_drain_reqs(9F) usb_pipe_get_max_bulk_transfer_size(9F) usb_pipe_get_private(9F) usb_pipe_get_state(9F) usb_pipe_intr_xfer(9F) usb_pipe_isoc_xfer(9F) usb_pipe_open(9F) usb_pipe_reset(9F) usb_pipe_set_private(9F) usb_pipe_stop_intr_polling(9F) usb_pipe_stop_isoc_polling(9F) usb_pipe_xopen(9F) usb_print_descr_tree(9F) usb_register_hotplug_cbs(9F) usb_reset_device(9F) usb_set_alt_if(9F) usb_set_cfg(9F) usb_unregister_hotplug_cbs(9F) PCI Device Driver Functions
These functions are specific for PCI and PCI Express based device
drivers and are intended to be used to get access to PCI configuration
space. For normal PCI base address registers (BARs) instead see
Register Setup and Access.
To access PCI configuration space, a device driver should first call
pci_config_setup(9F). Generally, drivers will call this in their
attach(9E) entry point and then tear down the configuration space
access with the
pci_config_teardown(9F) entry point in
detach(9E).
After setting up access to configuration space, the returned handle can
be used in all of the various configuration space routines to get and
set specific sized values in configuration space.
pci_config_get8(9F) pci_config_get16(9F) pci_config_get32(9F) pci_config_get64(9F) pci_config_put8(9F) pci_config_put16(9F) pci_config_put32(9F) pci_config_put64(9F) pci_config_setup(9F) pci_config_teardown(9F) pci_report_pmcap(9F) pci_restore_config_regs(9F) pci_save_config_regs(9F) USB Host Controller Interface Functions
These routines are used for device drivers which implement the USB host
controller interfaces described in
usba_hcdi(9E). Other types of
devices drivers and modules should not call these functions. In
particular, if one is writing a device driver for a USB device, these
are not the routines you're looking for and you want to see
USB Device Driver Functions. These are what the
ehci(4D) or
xhci(4D) drivers use
to provide services that USB drivers use via the kernel USB
architecture.
usba_alloc_hcdi_ops(9F) usba_free_hcdi_ops(9F) usba_hcdi_cb(9F) usba_hcdi_dup_intr_req(9F) usba_hcdi_dup_isoc_req(9F) usba_hcdi_get_device_private(9F) usba_hcdi_register(9F) usba_hcdi_unregister(9F) usba_hubdi_bind_root_hub(9F) usba_hubdi_cb_ops(9F) usba_hubdi_close(9F) usba_hubdi_dev_ops(9F) usba_hubdi_ioctl(9F) usba_hubdi_open(9F) usba_hubdi_root_hub_power(9F) usba_hubdi_unbind_root_hub(9F) Functions for PCMCIA Drivers
These functions exist for older PCMCIA device drivers. These should
not otherwise be used by the system.
csx_AccessConfigurationRegister(9F) csx_ConvertSize(9F) csx_ConvertSpeed(9F) csx_CS_DDI_Info(9F) csx_DeregisterClient(9F) csx_DupHandle(9F) csx_Error2Text(9F) csx_Event2Text(9F) csx_FreeHandle(9F) csx_Get16(9F) csx_Get32(9F) csx_Get64(9F) csx_Get8(9F) csx_GetEventMask(9F) csx_GetFirstClient(9F) csx_GetFirstTuple(9F) csx_GetHandleOffset(9F) csx_GetMappedAddr(9F) csx_GetNextClient(9F) csx_GetNextTuple(9F) csx_GetStatus(9F) csx_GetTupleData(9F) csx_MakeDeviceNode(9F) csx_MapLogSocket(9F) csx_MapMemPage(9F) csx_ModifyConfiguration(9F) csx_ModifyWindow(9F) csx_Parse_CISTPL_BATTERY(9F) csx_Parse_CISTPL_BYTEORDER(9F) csx_Parse_CISTPL_CFTABLE_ENTRY(9F) csx_Parse_CISTPL_CONFIG(9F) csx_Parse_CISTPL_DATE(9F) csx_Parse_CISTPL_DEVICE_A(9F) csx_Parse_CISTPL_DEVICE_OA(9F) csx_Parse_CISTPL_DEVICE_OC(9F) csx_Parse_CISTPL_DEVICE(9F) csx_Parse_CISTPL_DEVICEGEO_A(9F) csx_Parse_CISTPL_DEVICEGEO(9F) csx_Parse_CISTPL_FORMAT(9F) csx_Parse_CISTPL_FUNCE(9F) csx_Parse_CISTPL_FUNCID(9F) csx_Parse_CISTPL_GEOMETRY(9F) csx_Parse_CISTPL_JEDEC_A(9F) csx_Parse_CISTPL_JEDEC_C(9F) csx_Parse_CISTPL_LINKTARGET(9F) csx_Parse_CISTPL_LONGLINK_A(9F) csx_Parse_CISTPL_LONGLINK_C(9F) csx_Parse_CISTPL_LONGLINK_MFC(9F) csx_Parse_CISTPL_MANFID(9F) csx_Parse_CISTPL_ORG(9F) csx_Parse_CISTPL_SPCL(9F) csx_Parse_CISTPL_SWIL(9F) csx_Parse_CISTPL_VERS_1(9F) csx_Parse_CISTPL_VERS_2(9F) csx_ParseTuple(9F) csx_Put16(9F) csx_Put32(9F) csx_Put64(9F) csx_Put8(9F) csx_RegisterClient(9F) csx_ReleaseConfiguration(9F) csx_ReleaseIO(9F) csx_ReleaseIRQ(9F) csx_ReleaseSocketMask(9F) csx_ReleaseWindow(9F) csx_RemoveDeviceNode(9F) csx_RepGet16(9F) csx_RepGet32(9F) csx_RepGet64(9F) csx_RepGet8(9F) csx_RepPut16(9F) csx_RepPut32(9F) csx_RepPut64(9F) csx_RepPut8(9F) csx_RequestConfiguration(9F) csx_RequestIO(9F) csx_RequestIRQ(9F) csx_RequestSocketMask(9F) csx_RequestWindow(9F) csx_ResetFunction(9F) csx_SetEventMask(9F) csx_SetHandleOffset(9F) csx_ValidateCIS(9F) STREAMS related functions
These functions are meant to be used when interacting with STREAMS
devices or when implementing one. When a STREAMS driver is opened, it
receives messages on a queue which are then processed and can be sent
back. As different queues are often linked together, the most common
thing is to process a message and then pass the message onto the next
queue using the
putnext(9F) function.
STREAMS messages are passed around using message blocks, which use the
mblk_t type. See
Message Block Functions for more about how the data
structure and functions that manipulate message blocks.
These functions should generally not be used when implementing a
networking device driver today. See
mac(9E) instead.
backq(9F) bcanput(9F) bcanputnext(9F) canput(9F) canputnext(9F) enableok(9F) flushband(9F) flushq(9F) freezestr(9F) getq(9F) insq(9F) merror(9F) mexchange(9F) noenable(9F) put(9F) putbq(9F) putctl(9F) putctl1(9F) putnext(9F) putnextctl(9F) putnextctl1(9F) putq(9F) mt-streams(9F) qassociate(9F) qenable(9F) qprocsoff(9F) qprocson(9F) qreply(9F) qsize(9F) qwait_sig(9F) qwait(9F) qwriter(9F) OTHERQ(9F) RD(9F) rmvq(9F) SAMESTR(9F) unfreezestr(9F) WR(9F) STREAMS ioctls
The following functions are used when a STREAMS-based device driver is
processing its
ioctl(9E) entry point. Unlike character and block
devices, STREAMS ioctls are passed around in message blocks and copying
data in and out of userland as STREAMS ioctls are generally always
processed in
kernel context. This means that the normal functions like
ddi_copyin(9F) and
ddi_copyout(9F) cannot be used. Instead, when a
message block has a type of M_IOCTL, then these routines can often be
used to convert the structure into one that asks for data to be copied
in, copied out, or to finally acknowledge the ioctl as successful or to
terminate the processing in error.
mcopyin(9F) mcopyout(9F) mioc2ack(9F) miocack(9F) miocnak(9F) miocpullup(9F) mkiocb(9F) chpoll(9E) Related Functions These functions are present in service of the
chpoll(9E) interface
which is used to support the traditional
poll(2), and
select(3C) interfaces as well as event ports through the
port_get(3C) interface.
See
chpoll(9E) for the specific cases this should be called. If a
device driver does not implement the
chpoll(9E) character device entry
point, then these functions should not be used.
pollhead_clean(9F) pollwakeup(9F) Kernel Statistics
The kernel statistics or kstat framework provides an easy way of
exporting statistic information to be consumed outside of the kernel.
Users can interface with this data via
kstat(8) and the corresponding
kstat library discussed in
kstat(3KSTAT).
Kernel statistics are grouped using a tuple of four identifiers,
separated by colons when using
kstat(8). These are, in order, the
statistic module name, instance, a name which covers a group of
statistics, and an individual name for a statistic. In addition,
kernel statistics have a class which is used to group similar named
groups of statistics together across devices. When using
kstat_create(9F), drivers specify the first three parts of the tuple
and the class. The naming of individual statistics, the last part of
the tuple, varies based upon the type of the statistic. For the most
part, drivers will use the kstat type KSTAT_TYPE_NAMED, which allows
multiple name-value pairs to exist within the statistic. For example,
the kernel's layer 2 networking framework,
mac(9E), creates a kstat
with the driver's name and instance and names it "mac". Within this
named group, there are statistics for all of the different individual
stats that the kernel and devices track such as bytes transmitted and
received, the state and speed of the link, and advertised and enabled
capabilities.
A device driver can initialize a kstat with the
kstat_create(9F) function. It will not be made accessible to users until the
kstat_install(9F) function is called. The device driver must perform
additional initialization of the kstat before proceeding and calling
kstat_install(9F). The kstat structure that drivers see is discussed
in
kstat(9S).
kstat_create(9F) kstat_delete(9F) kstat_install(9F) kstat_named_init(9F) kstat_named_setstr(9F) kstat_queue(9F) kstat_runq_back_to_waitq(9F) kstat_runq_enter(9F) kstat_runq_exit(9F) kstat_waitq_enter(9F) kstat_waitq_exit(9F) kstat_waitq_to_runq(9F) NDI Events
These functions are used to allow a device driver to register for
certain events that might occur to its device or a parent in the tree
and receive a callback function when they occur. A good example of
this is when a device has been removed from the system such as someone
just pulling out a USB device or NVMe U.2 device. The event handlers
work by first getting a cookie that names the type of event with
ddi_get_eventcookie(9F) and then registering the callback with
ddi_add_event_handler(9F).
The
ddi_cb_register(9F) function is used to collect over classes of
events such as when participating in dynamic interrupt sharing.
ddi_add_event_handler(9F) ddi_cb_register(9F) ddi_cb_unregister(9F) ddi_get_eventcookie(9F) ddi_remove_event_handler(9F) Layered Device Interfaces
The LDI (Layered Device Interface) provides a mechanism for a driver to
open up another device in the kernel and begin calling basic operations
on the device as though the calling driver were a normal user process.
Through the LDI, drivers can perform equivalents to the basic file
read(2) and
write(2) calls, look up properties on the device, perform
networking style calls ala
getmsg(2) and
putmsg(2), and register
callbacks to be called when something happens to the underlying device.
For example, the ZFS file system uses the LDI to open and operate on
block devices.
Before opening a device itself, callers must obtain a notion of their
identity which is used when making subsequent calls. The simplest form
is often to use the device's
dev_info_t and call
ldi_ident_from_dip(9F); however, there are also methods available based
upon having a
dev_t or a STREAMS
struct queue.
Once that identity is established, there are several ways to open a
device such as
ldi_open_by_dev(9F),
ldi_open_by_devid(9F), or
ldi_open_by_name(9F). Once an LDI device has been opened, then all of
the other functions may be used to operate on the device; however,
consumers of the LDI must think carefully about what kind of device
they are opening. While a kernel pseudo-device driver cannot disappear
while it is open, when the device represents an actual piece of
hardware, it is possible for it to be physically removed and no longer
be accessible. Consumers should not assume that a layered device will
always be present.
ldi_add_event_handler(9F) ldi_aread(9F) ldi_awrite(9F) ldi_close(9F) ldi_devmap(9F) ldi_dump(9F) ldi_ev_finalize(9F) ldi_ev_get_cookie(9F) ldi_ev_get_type(9F) ldi_ev_notify(9F) ldi_ev_register_callbacks(9F) ldi_ev_remove_callbacks(9F) ldi_get_dev(9F) ldi_get_devid(9F) ldi_get_eventcookie(9F) ldi_get_minor_name(9F) ldi_get_otyp(9F) ldi_get_size(9F) ldi_getmsg(9F) ldi_ident_from_dev(9F) ldi_ident_from_dip(9F) ldi_ident_from_stream(9F) ldi_ident_release(9F) ldi_ioctl(9F) ldi_open_by_dev(9F) ldi_open_by_devid(9F) ldi_open_by_name(9F) ldi_poll(9F) ldi_prop_exists(9F) ldi_prop_get_int(9F) ldi_prop_get_int64(9F) ldi_prop_lookup_byte_array(9F) ldi_prop_lookup_int_array(9F) ldi_prop_lookup_int64_array(9F) ldi_prop_lookup_string_array(9F) ldi_prop_lookup_string(9F) ldi_putmsg(9F) ldi_read(9F) ldi_remove_event_handler(9F) ldi_strategy(9F) ldi_write(9F) Signal Manipulation
These utility functions all relate to understanding whether or not a
process can receive a signal an actually delivering one to a process
from a driver. This interface is specific to device drivers and should
not be used by the broader kernel. These interfaces are not
recommended and should only be used after consultation.
ddi_can_receive_sig(9F) proc_ref(9F) proc_signal(9F) proc_unref(9F) Getting at Surrounding Context
These functions allow a driver to better understand its current
context. For example, some drivers have to deal with providing polled
I/O or take special care as part of creating a kernel crash dump.
These cases may need to call the
ddi_in_panic(9F) function. The other
functions generally provide a way to get at information such as the
process ID or other information from the system; however, this
generally should not be needed or used. Almost all values exposed by
say
drv_getparm(9F) have more usable first-class methods of getting at
the data.
ddi_get_kt_did(9F) ddi_get_pid(9F) ddi_in_panic(9F) drv_getparm(9F) Driver Memory Mapping
These functions are present for device drivers that implement the
devmap(9E) or
segmap(9E) entry points. The
ddi_umem_alloc(9F) routines
are used to allocate and lock memory that can later be used as part of
passing this memory to userland through the mapping entry points.
ddi_devmap_segmap(9F) ddi_mmap_get_model(9F) ddi_segmap_setup(9F) ddi_segmap(9F) ddi_umem_alloc(9F) ddi_umem_free(9F) ddi_umem_iosetup(9F) ddi_umem_lock(9F) ddi_umem_unlock(9F) ddi_unmap_regs(9F) devmap_default_access(9F) devmap_devmem_setup(9F) devmap_do_ctxmgt(9F) devmap_load(9F) devmap_set_ctx_timeout(9F) devmap_setup(9F) devmap_umem_setup(9F) devmap_unload(9F) UTF-8, UTF-16, UTF-32, and Code Set Utilities These routines provide the ability to work with and deal with text in
different encodings and code sets. Generally the kernel does not
assume that much about the type of the text that it is operating in,
though some subsystems will require that the names of things be ASCII
only.
The primary other locales that the system supports are generally UTF-8
based and so the kernel provides a set of routines to deal with UTF-8
and Unicode normalization. However, there are still cases where
different character encodings are required or conversation between
UTF-8 and some other type is required. This is provided by the kernel
iconv framework, which provides a subset of the traditional userland
iconv conversions.
kiconv_close(9F) kiconv_open(9F) kiconv(9F) kiconvstr(9F) u8_strcmp(9F) u8_textprep_str(9F) u8_validate(9F) uconv_u16tou32(9F) uconv_u16tou8(9F) uconv_u32tou16(9F) uconv_u32tou8(9F) uconv_u8tou16(9F) uconv_u8tou32(9F) Raw I/O Port Access This group of functions provides raw access to I/O ports on
architecture that support them. These functions do not allow any
coordination with other callers nor is the validity of the port assured
in any way. In general, device drivers should use the normal register
access routines to access I/O ports. See
Device Register Setup and Access for more information on the preferred way to setup and access
registers.
inb(9F) inw(9F) inl(9F) outb(9F) outw(9F) outl(9F) Power Management
These functions are used to raise and lower the internal power levels
of a device driver or to indicate to the kernel that the device is busy
and therefore cannot have its power changed. See
power(9E) for
additional information.
ddi_removing_power(9F) pm_busy_component(9F) pm_idle_component(9F) pm_lower_power(9F) pm_power_has_changed(9F) pm_raise_power(9F) pm_trans_check(9F) Network Packet Hooks
These functions are intended to be used by device drivers that wish to
inspect and potentially modify packets along their path through the
networking stack. The most common use case is for implementing
something like a network firewall. Otherwise, if looking to add
support for a new protocol or other network processing feature, one is
better off more directly integrating with the networking stack.
To get started, drivers generally will need to first use
net_protocol_lookup(9F) to get a handle to say that they're interested
in looking at IPv4 or IPv6 traffic and then can allocate an actual hook
object with
hook_alloc(9F). After filling out the hook, the hook can
be inserted into the actual system with
net_hook_register(9F).
Hooks operate in the context of a networking stack. Every networking
stack in the system is independent and therefore has its own set of
interfaces, routing tables, settings, and related. Most zones have
their own networking stack. This is the exclusive-IP option that is
described in
zoneadm(8).
Drivers can register to get a callback for every netstack in the system
and be notified when they are created and destroyed. This is done by
calling the
net_instance_alloc(9F) function, filling out its data
structure, and then finally calling
net_instance_register(9F). Like
other callback interfaces, the moment the callback functions are
registered, drivers need to expect that they're going to be called.
hook_alloc(9F) hook_free(9F) net_event_notify_register(9F) net_event_notify_unregister(9F) net_getifname(9F) net_getlifaddr(9F) net_getmtu(9F) net_getnetid(9F) net_getpmtuenabled(9F) net_hook_register(9F) net_hook_unregister(9F) net_inject_alloc(9F) net_inject_free(9F) net_inject(9F) net_instance_alloc(9F) net_instance_free(9F) net_instance_notify_register(9F) net_instance_notify_unregister(9F) net_instance_protocol_unregister(9F) net_instance_register(9F) net_instance_unregister(9F) net_ispartialchecksum(9F) net_isvalidchecksum(9F) net_kstat_create(9F) net_kstat_delete(9F) net_lifgetnext(9F) net_netidtozonid(9F) net_phygetnext(9F) net_phylookup(9F) net_protocol_lookup(9F) net_protocol_notify_register(9F) net_protocol_release(9F) net_protocol_walk(9F) net_routeto(9F) net_zoneidtonetid(9F) netinfo(9F)SEE ALSO
Intro(2),
Intro(9),
Intro(9E),
Intro(9S) illumos Developer's Guide, https://www.illumos.org/books/dev/.
Writing Device Drivers, https://www.illumos.org/books/wdd/.
illumos October 27, 2024 illumos