| ============== |
| Device Drivers |
| ============== |
| |
| Standard Device Drivers |
| ======================= |
| |
| Device drivers should be implemented in the RTOS and used by applications. |
| Drivers provide access to device functionality for applications. This is a |
| necessary part of the modular RTOS design. In the NuttX directory structure, |
| share-able device drivers reside under ``drivers/`` and custom drivers reside in |
| the board-specific directories at ``nuttx/boards/<arch>/<chip>/<board>/src`` or |
| ``nuttx/boards/<arch>/<chip>/drivers`` that are built into the RTOS. |
| |
| Bus Drivers |
| =========== |
| |
| There a many things that get called drivers in OS; NuttX makes a distinction |
| between device drivers and bus drivers. For example, SPI, PCI, PCMCIA, USB, |
| Ethernet, etc. are buses and not devices. You will never find a device driver |
| for a bus in the NuttX architecture. |
| |
| In most devices architectures, devices reside on a bus. A bus is a transport |
| layer that connects the device residing on the bus to a device driver. |
| The bus is managed by a bus driver. The device driver uses the facilities of |
| the bus driver transport layer to interact with the device. |
| |
| Consider SPI. SPI is a bus. It provides a serial bus to which many devices may |
| be connected. An SPI device resides on the SPI bus in the sense that is shares |
| the same MISO, MOSI, and clock lines with other devices on the SPI bus (but in |
| SPI, it will have its own dedicated chip select discrete). |
| |
| Although we typically use the same term driver to refer to both bus drivers and |
| device drivers, there is one big, fundamental difference: applications interact |
| only with devices drivers and never with bus drivers. Applications never talk |
| directly to PCI, PCMCIA, USB, Ethernet, nor with I2C, SPI, or GPIOs. Applications |
| interface through device drivers that use PCI, PCMCIA, USB, Ethernet, I2C, or SPI. |
| Bus drivers only exist to support the communication between the device driver and |
| the device on the bus. |
| |
| Back to SPI... There will never be an application accessible interface to SPI. |
| If your application were to use SPI directly, then you would have have embedded |
| a device driver in your application and would have violated the RTOS functional |
| partition. |
| |
| Test Drivers |
| ============ |
| |
| It would be possible to provide character driver, such as SPI driver, that could |
| perform bus level accesses on behalf of an application. There are not many cases |
| where this would be acceptable, however. One possibility would be to support |
| support testing of bus drivers. |
| There is, an example for I2S here: ``drivers/audio/i2schar.c`` with a test case |
| here ``apps/examples/i2schar``. I2S is, of course, very similar to SPI. |
| This interface exists only for testing purposes and would probably not be |
| possible to build any meaningless application with it. |
| |
| The I2C Tool |
| ------------ |
| |
| Of course, like most rules, there are lots of violations. I2C is another bus and |
| the I2C "driver" is another transport similar in many ways to SPI. For I2C, |
| there is an application at ``apps/system/i2c`` called the "I2C tool" that will allow |
| you access I2C devices from the command line. This is not really just a test tool |
| and not a real part of an application. |
| |
| And there is a fundamental flaw in the I2C tool: it uses NuttX internal interfaces |
| and violates the functional partitioning. NuttX has three build mode: (1) A flat |
| build where there is no enforcement of RTOS boundaries. In that flat build, |
| the I2C tool works fine. And (2) a kernel build mode and (3) a protected build mode. |
| In bothof these latter cases, the OS interfaces are strictly enforced. In the kernel |
| pand protected build modes, the I2C tool is not available because it cannot access |
| those NuttX internal interfaces. |
| |
| User Space Drivers |
| ================== |
| |
| Above, it was stated that if your application were to use a bus directly, then you |
| would have have embedded a device driver in your application and would violate |
| the RTOS functional partition. Such device built into user applications are |
| referred to as user space drivers in some contexts. There is no plan or intent |
| to support user space drivers in NuttX. |
| |
| Communication Devices |
| ===================== |
| |
| What about interface like CAN and UARTs? Why are those exposed as drivers when |
| SPI and I2C are not? |
| |
| Semantics are difficult. The general principles that are maintain in |
| the RTOS are clear, but sometimes applying principles in a black and white way |
| is not easy in a world with shades of grey. (And if the principles get in the |
| way of good design then the principles should change). |
| |
| In the case of true buses that support generic devices, the principle |
| is a good one. But there are grey areas too. |
| |
| CAN seems similar to Ethernet. Both are network interfaces of sorts. You |
| wouldn't interface directly with Ethernet driver because you need to go |
| through a network stack of some type. The OSI model prevents it. |
| |
| UARTs are communication devices. There is no RS-232 bus with devices connected |
| to it. Rather there are peers on the bus that you communicate with. This does not |
| preclude a UART from being used as a low level transfer for a device driver |
| (as with the driver for a wireless modules). Nor does it preclude a stack layer |
| like Modbus from being inserted in the path. |
| |
| CAN differs from Ethernet in that it really is a direct peer-to-peer |
| communication, more like a UART. Although you can support a stack like CANOPen |
| on CAN. Currently CAN can be used as a simple character device, or as a network |
| interface using SocketCAN. |
| |
| Communication devices support a fundamental peer-to-peer model. CAN and UARTs |
| are basically serial interfaces. But so are SPI, I2C, and USB. But those latter |
| serial interfaces clearly have a host/device, master/slave model associated with |
| them. It make perfectly good sense to think of them as buses that support device |
| interfaces. |
| |
| I/O Expander |
| ============ |
| |
| An I/O expander is device that interfaces with the MCU, usually via I2C, and |
| provides additional discrete inputs and outputs. The same rules apply: |
| |
| * **GPIOS are Board-Specific**. Nothing in the system should now about GPIOs |
| except for board specific logic. GPIOs can change from board-toboard. They |
| can come and go. They can be replaced by GPIO expanders. Your (portable) |
| application should not have any knowledge about how any discrete I/O is |
| implemented on the board. There will never be GPIO drivers as a part of |
| the NuttX architecture. |
| |
| * **Common Drivers are Board-Independent**. Nor should common drivers |
| (like those in ``drivers/``) know anything about GPIOs. In ALL cases, |
| the board specific implementation in the board directories creates |
| a "lower half" driver and binds that "lower half" driver with an common |
| "upper half" driver to initialize the driver. Only the board logic has |
| any kind of GPIO knowledge; not the application and not the common |
| "upper half driver". |
| |
| * **I2C and SPI Drivers are Internal Bus Drivers**. Similarly I2C and SPI |
| drivers are not accessible to applications. These are NOT device drivers |
| but are bus drivers. They should not be accessed directly by applications. |
| Rather, again, the board-specific logic generates a "lower half" driver |
| that provides a common I2C or SPI interface and binds that with |
| an "upper half" driver to initialize the driver. |
| |
| None of those rules change if you use an I/O expander, things just get |
| more convoluted. |
| |
| Example Architecture |
| -------------------- |
| |
| Consider this case for some ``<board>``: |
| |
| #. A discrete joystick is implemented as set of buttons: UP, DOWN, LEFT, RIGHT, |
| and CENTER. The state of each the buttons is sensed as a GPIO input. |
| |
| #. The GPIO button inputs go to I2C I/O expander at say, |
| ``drivers/ioexpander/myexpander.c``, and finally to |
| |
| #. The discrete joystick driver "upper half" driver (``drivers/input/djoystick.c``). |
| |
| Implementation Details |
| ---------------------- |
| |
| These should be implemented in the following, flexible, portable, layered architecture: |
| |
| #. In the end, the application would interact only with a joystick driver |
| interface via standard open/close/read/ioctl operations. It would receive |
| pjoystick information as described in ``include/nuttx/input/djoystick.h.`` |
| |
| #. The discrete joystick driver would have been initialized by logic in some |
| file like ``boards/<arch>/xyz/<board>/src/xyz_djoystick.c`` when the system |
| was initialized. ``zyz_joystick.c`` would have created instance of |
| the ``struct djoy_lowerhalf_s`` "lower half" interface as described in |
| ``nuttx/include/nuttx/input/djoystick.h`` and would have passed that |
| interface instance to the ``drivers/input/djoystick.c`` "upper half" driver |
| to initialize it. |
| |
| #. As part of the creation of the ``struct djoy_lowerhalf_s`` "lower half" |
| interface instance, logic in ``xyz_djoystick.c`` would have done the following: |
| It would have created an I2C driver instance by called MCU specific I2C initialization |
| logic then passed this I2C driver instance to the I/O expander initialization interface |
| in ``drivers/ioexpander/myexpander.c`` to create the I/O expander interface instance. |
| |
| Note that the I/O expander interface should NOT be a normal character driver. |
| It should NOT be accessed via open/close/read/write/ioctl. Rather, it should return |
| an instance of a some ``struct ioexpander_s`` interface. That I/O expander interface |
| would be described in ``nuttx/include/ioexpander/ioexpander.h``. It is an internal |
| operating system interface and would never be available to application logic. |
| |
| After receiving the I/O expander interface instance, the "lower half" discrete |
| joystick interface would retain this internally as private data. Nothing in the |
| system other than this "lower half" discrete joystick driver needs to know how |
| the joystick is connected on board. |
| |
| #. After creating the "upper half" discrete joystick interface interface, |
| the "lower half" discrete joystick interface would enable interrupts from |
| the I/O expander device. |
| |
| #. When a key is pressed, the "lower half" discrete joystick driver would receive |
| an interrupt from the I/O expander. It would then interact with the I/O driver |
| to obtain the current discrete button depressions. The I/O expander driver would |
| interact with I2C to obtain those button settings. Then the discrete joystick |
| interface callback will be called, providing the discrete joystick "upper half" |
| driver with the joystick input. |
| |
| #. The "upper half" discrete joystick character driver would then return the encoded |
| joystick input to the application in response to a ``read()`` from application code. |