> Date: Fri, 3 Jul 2020 15:52:31 +0200
> From: Rocky Hotas <rockyho...@firemail.cc>
> 
> This works only because REG_A has all 0s except the LSB: when the LSB
> becomes 0, too, the while exits. But I am actually interested only in
> the LSB and would like to disregard the other 7 bits in the register.
> What could it be the most efficient way to accomplish this?

It's quite common to read a whole device register just to get at a
single bit.  Don't worry about the efficiency -- the cost of the I/O
transaction over the PCI bus or similar far exceeds the cost of
pulling one bit out of 32.

> When a bit must be set, there's the macro __BIT(n) in
> 
>  <https://nxr.netbsd.org/xref/src/sys/sys/cdefs.h#640>
> 
> Is there something similar for when just a single bit must be read?

The usual idea for __BIT is that if there's a device register FOO, and
it has various fields BAR, BAZ, and QUUX, with a hardware manual that
says:

   FOO (0x01234):

      BAR   (0:1)  The bar is open.
      BAZ   (2:19) The number of orcs at the bar.
      QUUX (20:23) The number of dwarves hiding in the bathroom.

Then you write in your mumblereg.h file for the mumble(4) driver:

#define FOO     0x01234
#define FOO_BAR         __BIT(0)
#define FOO_BAZ         __BITS(2,19)
#define FOO_QUUX        __BITS(20:23)

and then you get at these by doing:

        uint32_t foo = bus_space_read_4(bst, bsh, FOO);

        if ((foo & FOO_BAR) == 0)
                return ENOENT;
        baz = __SHIFTOUT(foo, FOO_BAZ);
        quux = __SHIFTOUT(foo, FOO_QUUX);
        ...

> This way, if for some reason the LSB in REG_A is never cleared (the
> device is not working, or similar), the while never exits. Is it
> available, inside the kernel, some function like sleep, or wait, so
> that a maximum timeout can be set?

A typical approach is to set a reasonable timeout, either in register
reads or in microseconds, and count down to it:

        unsigned timo = 1000;

        while ((bus_space_read_4(bst, bsh, FOO) & FOO_BAR) == 0) {
                if (--timo == 0)
                        return ETIMEDOUT;
                /* optionally, space the reads out by a microsecond */
                DELAY(1);
        }

If you might need to wait for longer periods of time, like
milliseconds, then you can use kpause with mstohz which lets other
threads run, and if you're working under a lock, you can pass it to
kpause to release the lock while other threads run.

        unsigned timo = 1000;

        mutex_enter(&sc->sc_lock);
        while ((bus_space_read_4(bst, bsh, FOO) & FOO_BAR) == 0) {
                if (--timo == 0)
                        return ETIMEDOUT;
                kpause("foobar", false, mstohz(10), &sc->sc_lock);
        }

However, if you may need to wait for a long period of time, you should
see if there's a way to get an interrupt notification instead of
polling the device register, and use a condvar to signal the
notification from the interrupt handler and to wait for the
notification elsewhere in software.

Reply via email to