In the current release, the driver has a fairly simple architecture, which is described in the following lines.
The driver was written using Sun DDI/DKI interface, which is described elsewhere. The best information source for this is the book Writing Device Drivers from Sun Microsystems, Inc. This one can be downloaded from Sun Documentation Site in PDF format.
Circular queue is a FIFO data buffer with a fixed size. It behaves exactly as a regular queue, until it gets full. When it gets full, insert operation will remove the oldest element from the queue to make room for a new one. This way, data is circulating through the queue making the queue some sort of historical view of the data being inserted in it.
Opto driver implements a circular queue in the form of a ring buffer. Ring buffer can be visualized as a ring with two pointers on its outer surface, one being a read pointer, the other being a write pointer, both having the same position at zero initially. Read operations begin at the position of the read pointer, incrementing the read pointer after each element being read. Write operations being on the position of the write pointer, incrementing the write pointer after each element.
Each time an element is added to the ring buffer, the write pointer increments, walking along the circle and filling the part of the circle between itself and the read pointer (which remains behind) with elements. When the write pointer reaches the read pointer's position (after making a full circle), it will continue, but will increment the read pointer too, pushing it forward it front of itself. This way, the oldest elements are effectively removed, overwriting them with the new incoming elements.
When elements are read form the ring buffer, the read operation begins at the read pointer's position, and the read pointer increments after reading each element. Read pointer increments, walks along the circle chasing the write pointer, leaving the empty area behind, and making the full area between itself and the write pointer smaller. When it reaches the write pointer, it will not continue further, but will stop and indirectly indicate that the ring buffer is empty. Reads can be performed again as soon as write pointer moves forward, means as soon as new data arrives to the buffer.
In the computer, we do not have a linear address space, not circular. For this reason, two additional pointers are needed in a ring buffer implementation, one marking the beginning (begin pointer), the other marking the end of the buffer (end pointer). These are necessary to allocate and deallocate the buffer, and to ensure the required loop behaviour of the read and write pointers. Basically, whenever a read or write pointer reaches the end pointer in its circulating lullabye, it will be teleported to the begin pointer without even noticing it.
Whenever something changes on the hardware input, or when a specific time interval elapses, hardware issues an interrupt. the interrupt handler is the one who fills the circular queue on this occasion. when the interrupt occurs for the first time, the interrupt handler will read from the apropriate device registers and store their contents into the circular queue. During all subsequent interrupts, the interrupt handler will read from the same registers, and store the new data into the circular queue only if it is different from those acquired during the previous interrupt. This assures that the contents of the circular queue is changed, only if the stuff in the device registers has actually changed.
This behaviour ensures that the circular buffer contains the historical view of the changes occured in the device registers, which is what we want to have in the first place. This way, userland has a better view about what is happening on the wire and is able to perform a more effective poll(2) calls.
When a userland process reads from the device, it actually reads the contents of the circular queue. Userspace process cannot read the contents of the device registers without using an ioctl(2) call.
When a userspace process writes to the device, it writes to the circular queue. This is provided for testing purposes.
Driver has a static and dynamic memory usage. Statically allocated data are of fixed size. The following globals belong to this group:
name | resident size |
drv_cb_ops | sizeof(cb_ops) |
drv_dev_ops | sizeof(dev_ops) |
drv_modldrv | sizeof(modldrv) |
drv_modlinkage | sizeof(modlinkage) |
drv_soft_state | sizeof(void*) |
drv_acc_attr | sizeof(ddi_device_acc_attr_t) |
Dynamic data are allocated per attached device. This group remains resident and of fixed size as long the device is attached. This memory is freed after the device is detached from the driver:
type | resident size |
state info | sizeof(devstate_t) |
ring buffer | DEF_RBUF_SIZE + sizeof(rbuffer_t) |
In addition to this, local dynamic data is being allocated in some functions. This data is being freed before the function returns, what makes a very short residence time in all cases.