[Nut-upsdev] Adding drivers to NUT?

Daniele Pezzini
Sat, 21 Jul 2018 00:48:25 -0000

aehm.. the innards of nutdrv_qx are not exactly well documented (and
vars have somewhat confusing names) -- my fault, sorry.
This is another thing that has been on my todo list for a while...

Briefly, at the moment, in nutdrv_qx, there are actually two kinds of
subdrivers:
- 'protocols', which are communication-type-agnostic (i.e. they do not
fiddle with serial or USB operations, and are not restrained by them),
only map the protocol used by the device to the protocol used by NUT,
and are implemented in their own files using (and exposing only) the
`subdriver_t` format (for more info on this type, see the chapter of
the developer guide I mentioned before, 'nutdrv_qx.h' or the various
'nutdrv_qx_<name-of-the-protocol>.c' files),
- 'USB subdrivers', which only deal with USB I/O operations, and,
conversely, are implemented entirely in 'nutdrv_qx.c'.

With regard to 'protocols', nutdrv_qx works like this:
- 'nutdrv_qx.c''s `subdriver_list` lists the various
(#include'd)
'protocols' the driver supports.
- Once nutdrv_qx has set up the communication with a supported device
in `upsdrv_initups()` (see below, for serial or USB devices), it gives
all (unless otherwise instructed by the user) 'protocols' in
`subdriver_list` a few tries by calling their 'claim()' function
(which usually tries to read some vars from the device to see if it's
actually 'speaking' that protocol): the first one to successfully
claim (i.e. the 'claim()' function returns 1) the device wins.
- From then on, nutdrv_qx uses that 'protocol' when communicating
(e.g. updating the vars, sending instant commands, ...) with the
device: the mapping in the 'qx2nut' table, the
'accepted'/'rejected'
strings, etc.

Now, onto communications (entirely in 'nutdrv_qx.c')...

For serial devices:
- given that, so far, all supported devices shared similar settings
and I/O modus operandi, operations are done directly without much
fanfare...
- setup is done directly in `upsdrv_initups()`,
- ditto for subsequent communications, in `qx_command()`.

For USB devices:
- `qx_usb_id` lists the various VID:PID pairs (with, optionally and
only if needed, the iManufacturer and iProduct strings) of each
supported device, coupled with a function ('fun()' from now on) that
will set `subdriver_command` to point to the function ('command()'
from now on) later used to communicate with the device.
- When nutdrv_qx starts, in `upsdrv_initups()`, it will either pick
the USB subdriver (whose name must be mapped to its relative
'command()' in the `usbsubdriver` list) directly if it was explicitly
provided by the user or, for each available USB device, traverse the
`qx_usb_id` lists, until it finds a match (VID:PID +
iManufacturer/iProduct) and then call its 'fun()' function -- in both
cases, the result is that, if a supported device is found,
`subdriver_command` points to the right 'command()' function.
- Later, every time nutdrv_qx needs to send anything to the device and
to get a reply from it (i.e. in the `qx_command()` function), it will
call the function ('command()') pointed to by `subdriver_command`:
this 'command()' function will receive a null-terminated byte string,
a buffer and its size, and will do all the needed things to a) send
the string to the device and b) get the reply from the device and
store it in the buffer (for example, sending the string verbatim to a
control endpoint and then reading the reply, or mapping the string it
gets to the index of a string descriptor, that, once retrieved, will
hold the reply).

A couple of examples worth a thousand words:
- adding a 'protocol':
https://github.com/networkupstools/nut/commit/dc1e0799277008dcd2c2fad6bc3f59455b242d14
- adding a 'USB subdriver':
https://github.com/networkupstools/nut/commit/3370bbf3ca2d8f95346487938895c494e363186f

Clearer, now?