Re: Generic battery interface

Shem Multinymous
2006-07-29T21:06:21Z

On 7/29/06, Shem Multinymous <multinymous@gmail.com> wrote:

> There's a pattern here. Maybe what we need is a generic scheme for
> publishing continuous readouts, that will work for both hdaps and
> batteries (despite a X100 difference in typical time magnitudes).
> Doubtless there are other uses for such a scheme. We want it to
> support clients with different desired rates, data sources with
> different update frequencies, use minimum CPU and avoid unnecesary
> timer interrupts on tickless kernels.
>
> Here's one approach: use a syscall (e.g., ioctl) saying "block until
> there's new data on this fd, or N milliseconds have passed, whichever
> is *later*". This way each client declares the update rate it wants
> and can change it on the fly. The driver sees all the requests and can
> perform the minimum hardware quering -- for example, it won't query
> the hardware at all if no client has submitted a request with
> parameter N more than N milliseconds go. And there's no excessive work
> or interrupts. Some (simple) kernel code infrastructure is needed to
> help drivers manage the pending requests.

Here's a rough sketch for the userspace side of a continuous function
sampling interface. It handles the blocking a bit better than the
above proposal, in that it lets you easily handle multiple readouts.
It's agnostic about /dev vs. /sys.

As always, we have a file handing out readouts obtained from the
hardware - either a device file or a sysfs attribute. The file has the
following properties:

1. A new ioctl, SYSFS_DELAYED_UPDATE, meaning: "I want an a fresh
readout in N milliseconds, or whenver it's ready, whichever is later.
If I poll this FD with POLLIN, wait until the new readout is ready.
Likewise for select."
2. When the file is opened in O_NONBLOCKing mode, read() always return
the latest cached readout rather than querying the hardware
(unless the latter has negligible cost).
3. When the file is opened in normal (blocking) mode, read() blocks
until a fresh readout is available and returns this readout; see
below.

To illustrte, here's an example of a proper polling loop (sans error
checking). This app wants updates at most every second, but obviously
not more frequently than the hardware has to offer. If no readout ca
be obtained for 20 seconds, it warns the user that the data is stale.

--------------------------
/* Open attribute file with O_NONBLOCK so that all reads will
* return cached values instead of blocking:
*/
int fd = open("/whatever/voltage", O_NONBLOCK|O_RDONLY);

/* Read and process latest cached attribute value: */
read(fd, ...);
...

while (1) {
struct pollfd ufds = { .fd=fd, events=POLLIN };

/* Tell driver we want a fresh readout but no sooner than 1sec
* from now:
*/
ioctl(fd, SYSFS_DELAYED_UPDATE, 1000);

/* Wait for an update for at most 5 seconds. Nominally this will
* block for at least 1 second, because of the above ioct. If we
* were reading multiple attributes, we could poll them all
* simultaneously.
*/
poll(&ufds, 1, 20000);
... warn user if timed out ...

/* Read and process latest cached attribute value: */
read(fd, ...);
... handle readout ...
}
--------------------------

Note that the above does not ensure minimal separation between
readouts: you're guaranteed a fresh readout at every iteration, but
it might have been taken at the beginning of the >1sec poll period. If
you need to ensure that all readouts are at least (700ms apart, add a
sleep(700) at the end of the loop and decrease 1000 to 300 in the
ioctl().I think most apps won't need this.

Legacy and lightweight applications (e.g., cat from shell) will open
the file in blocking mode. In this case, a "read(fd, ...)" has
semantics equivalent to the following O_NONBLOCK-mode code:

--------------------------
/* Tell driver we want an immediate update: */
ioctl(fd, ATTR_DELAY_UPDATE, 0);

/* Wait indefinitely for an update: */
poll(&ufds, 1, -1);

/* Read the latest cached attribute value: */
read(fd, ...);
--------------------------

Similarly, select() and poll() on non-blocking files acts like you did
"ioctl(fd, ATTR_DELAY_UPDATE, 0);" and then wait for a refresh or timeout.

If we want to penalize people not using nonblocking mode and delayed
updates, we can replace two "0" above with a sufficiently nasty
(driver-dependent?) constant.

How does this look?

I'm not getting into the kernel side for now; it's doable, and with
proper infrastructure (e.g., at the sysfs level) can be elegant and
efficient.

Shem