Re: [PATCH v5 3/3] w1: add UART w1 bus driver

From: Christoph Winklhofer
Date: Thu Feb 01 2024 - 02:29:27 EST


On Wed, Jan 31, 2024 at 02:12:34PM +0100, Krzysztof Kozlowski wrote:
> On 26/01/2024 16:42, Christoph Winklhofer via B4 Relay wrote:
> > From: Christoph Winklhofer <cj.winklhofer@xxxxxxxxx>
> >
> > Add a UART 1-Wire bus driver. The driver utilizes the UART interface via
> > the Serial Device Bus to create the 1-Wire timing patterns. The driver
> > was tested on a "Raspberry Pi 3B" with a DS18B20 and on a "Variscite
> > DART-6UL" with a DS18S20 temperature sensor.
> >
>
> ...
>
> > + * struct w1_uart_config - configuration for 1-Wire operation
> > + *
> > + * @baudrate: baud-rate returned from serdev
> > + * @delay_us: delay to complete a 1-Wire cycle (in us)
> > + * @tx_byte: byte to generate 1-Wire timing pattern
> > + */
> > +struct w1_uart_config {
> > + unsigned int baudrate;
> > + unsigned int delay_us;
> > + u8 tx_byte;
> > +};
> > +
> > +struct w1_uart_device {
> > + struct serdev_device *serdev;
> > + struct w1_bus_master bus;
> > +
> > + struct w1_uart_config cfg_reset;
> > + struct w1_uart_config cfg_touch_0;
> > + struct w1_uart_config cfg_touch_1;
> > +
> > + struct completion rx_byte_received;
> > + int rx_err;
> > + u8 rx_byte;
> > +
>
> Missing documentation of mutex scope. What does it protect?
>

The mutex should protect concurrent access to rx_err and rx_byte. It
would be not be required in the good case: a write is initiated solely
by the w1-callbacks in 'w1_uart_serdev_tx_rx' and a completion is used
to wait for the result of serdev-receive.

However, in case the UART is not configured as a loop, a serdev-receive
may occur when w1_uart_serdev_tx_rx evaluates rx_err and rx_byte in
w1_uart_serdev_tx_rx, so it is protected - however, I will try to find
a better way to detect such an error.

In addition, the w1-callbacks should also return during a 'remove' (with
the mutex_try_lock) - see comment on that below.

> > + struct mutex mutex;
> > +};
> > +
>
> ...
>
> > +/*
> > + * Send one byte (tx_byte) and read one byte (rx_byte) via serdev.
> > + */
> > +static int w1_uart_serdev_tx_rx(struct w1_uart_device *w1dev,
> > + const struct w1_uart_config *w1cfg, u8 *rx_byte)
> > +{
> > + struct serdev_device *serdev = w1dev->serdev;
> > + int ret;
> > +
> > + serdev_device_write_flush(serdev);
> > + serdev_device_set_baudrate(serdev, w1cfg->baudrate);
> > +
> > + /* write and immediately read one byte */
> > + reinit_completion(&w1dev->rx_byte_received);
> > + ret = serdev_device_write_buf(serdev, &w1cfg->tx_byte, 1);
> > + if (ret != 1)
> > + return -EIO;
> > + ret = wait_for_completion_interruptible_timeout(
> > + &w1dev->rx_byte_received, W1_UART_TIMEOUT);
> > + if (ret <= 0)
> > + return -EIO;
> > +
> > + /* locking could fail during driver remove or when serdev is
>
> It's not netdev, so:
> /*
> *
>

Ok.

> > + * unexpectedly in the receive callback.
> > + */
> > + if (!mutex_trylock(&w1dev->mutex))
> > + return -EIO;
> > +
> > + ret = w1dev->rx_err;
> > + if (ret == 0)
> > + *rx_byte = w1dev->rx_byte;
> > +
> > + if (w1cfg->delay_us > 0)
> > + fsleep(w1cfg->delay_us);
> > +
> > + mutex_unlock(&w1dev->mutex);
> > +
> > + return ret;
> > +}
> > +
> > +static ssize_t w1_uart_serdev_receive_buf(struct serdev_device *serdev,
> > + const u8 *buf, size_t count)
> > +{
> > + struct w1_uart_device *w1dev = serdev_device_get_drvdata(serdev);
> > +
> > + mutex_lock(&w1dev->mutex);
> > +
> > + /* sent a single byte and receive one single byte */
> > + if (count == 1) {
> > + w1dev->rx_byte = buf[0];
> > + w1dev->rx_err = 0;
> > + } else {
> > + w1dev->rx_err = -EIO;
> > + }
> > +
> > + mutex_unlock(&w1dev->mutex);
> > + complete(&w1dev->rx_byte_received);
> > +
> > + return count;
> > +}
> > +
> > +static const struct serdev_device_ops w1_uart_serdev_ops = {
> > + .receive_buf = w1_uart_serdev_receive_buf,
> > + .write_wakeup = serdev_device_write_wakeup,
> > +};
> > +
> > +/*
> > + * 1-wire reset and presence detect: A present slave will manipulate
> > + * the received byte by pulling the 1-Wire low.
> > + */
> > +static u8 w1_uart_reset_bus(void *data)
> > +{
> > + struct w1_uart_device *w1dev = data;
> > + const struct w1_uart_config *w1cfg = &w1dev->cfg_reset;
> > + int ret;
> > + u8 val;
> > +
> > + ret = w1_uart_serdev_tx_rx(w1dev, w1cfg, &val);
> > + if (ret < 0)
> > + return -1;
> > +
> > + /* Device present (0) or no device (1) */
> > + return val != w1cfg->tx_byte ? 0 : 1;
> > +}
> > +
> > +/*
> > + * 1-Wire read and write cycle: Only the read-0 manipulates the
> > + * received byte, all others left the line untouched.
> > + */
> > +static u8 w1_uart_touch_bit(void *data, u8 bit)
> > +{
> > + struct w1_uart_device *w1dev = data;
> > + const struct w1_uart_config *w1cfg = bit ? &w1dev->cfg_touch_1 :
> > + &w1dev->cfg_touch_0;
> > + int ret;
> > + u8 val;
> > +
> > + ret = w1_uart_serdev_tx_rx(w1dev, w1cfg, &val);
> > +
> > + /* return inactive bus state on error */
> > + if (ret < 0)
> > + return 1;
> > +
> > + return val == w1cfg->tx_byte ? 1 : 0;
> > +}
> > +
> > +static int w1_uart_probe(struct serdev_device *serdev)
> > +{
> > + struct device *dev = &serdev->dev;
> > + struct w1_uart_device *w1dev;
> > + int ret;
> > +
> > + w1dev = devm_kzalloc(dev, sizeof(*w1dev), GFP_KERNEL);
> > + if (!w1dev)
> > + return -ENOMEM;
> > + w1dev->bus.data = w1dev;
> > + w1dev->bus.reset_bus = w1_uart_reset_bus;
> > + w1dev->bus.touch_bit = w1_uart_touch_bit;
> > + w1dev->serdev = serdev;
> > +
> > + init_completion(&w1dev->rx_byte_received);
> > + mutex_init(&w1dev->mutex);
> > +
> > + ret = w1_uart_serdev_open(w1dev);
> > + if (ret < 0)
> > + return ret;
> > + serdev_device_set_drvdata(serdev, w1dev);
> > + serdev_device_set_client_ops(serdev, &w1_uart_serdev_ops);
> > +
> > + return w1_add_master_device(&w1dev->bus);
> > +}
> > +
> > +static void w1_uart_remove(struct serdev_device *serdev)
> > +{
> > + struct w1_uart_device *w1dev = serdev_device_get_drvdata(serdev);
> > +
> > + mutex_lock(&w1dev->mutex);
> > +
> > + w1_remove_master_device(&w1dev->bus);
> > +
> > + mutex_unlock(&w1dev->mutex);
>
> This is still suspicious. You do not have serdev_device_close and you
> want to protect from concurrent access but it looks insufficient.
>
> This code assumes that:
>
> w1_uart_remove()
> <-- here concurrent read/write might start
> mutex_lock()
> w1_remove_master_device()
> mutex_unlock()
> <-- now w1_uart_serdev_tx_rx() or w1_uart_serdev_receive_buf() can be
> executed, but device is removed. So what's the point of the mutex here?
>
> What exactly is protected by the mutex? So far it looks like only some
> contents of w1dev, but it does not matter, because it that memory is
> still valid at this point.
>
> After describing what is protected we can think whether it is really
> protected...
>
>
> >
>
> Best regards,
> Krzysztof
>

Yes, it is still suspicious, sorry..

After w1_uart_remove, serdev is closed and w1dev is released. Therefore
the w1-callback (w1_uart_serdev_tx_rx) must be finished before returning
from w1_uart_remove. That was the intention with the lock and trylock.

I thought that after w1_remove_master_device, the w1-callback
(w1_uart_serdev_tx_rx) is finished which is not the case. I will check
the working of w1_remove_master_device, probably it requires a lock to
a mutex from w1-bus.

Many thanks for your review and pointing the things out!

Kind regards,
Christoph