tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: Waiting for a bit in a register to be cleared: which strategy?



> Date: Fri, 3 Jul 2020 15:52:31 +0200
> From: Rocky Hotas <rockyhotas%firemail.cc@localhost>
> 
> 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.


Home | Main Index | Thread Index | Old Index