Documentation: port "Device Drivers vs. Bus Drivers and GPIO Drivers" from wiki
port page from wiki: https://cwiki.apache.org/confluence/display/NUTTX/Device+Drivers+vs.+Bus+Drivers+and+GPIO+Drivers
This commit is contained in:
parent
45a53f0079
commit
e7c9e46eb2
3 changed files with 213 additions and 0 deletions
|
|
@ -31,6 +31,9 @@ Drivers in NuttX generally work in two distinct layers:
|
|||
* A "lower half" which is typically hardware-specific. This is
|
||||
usually implemented at the architecture or board level.
|
||||
|
||||
Details about drivers implementation can be found in
|
||||
:doc:`../../implementation/drivers_design` and :doc:`../../implementation/device_drivers`.
|
||||
|
||||
Subdirectories of ``nuttx/drivers``
|
||||
===================================
|
||||
|
||||
|
|
|
|||
209
Documentation/implementation/device_drivers.rst
Normal file
209
Documentation/implementation/device_drivers.rst
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
==============
|
||||
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 the I2C "driver" is another transport similar in many ways to SPI. For I2C,
|
||||
there is an application at ``apps/system/i2c`` alled 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.
|
||||
|
|
@ -7,6 +7,7 @@ Implementation Details
|
|||
:caption: Contents:
|
||||
|
||||
drivers_design.rst
|
||||
device_drivers.rst
|
||||
processes_vs_tasks.rst
|
||||
critical_sections.rst
|
||||
interrupt_controls.rst
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue