[PATCH] drivers: gpio: add virtio-gpio guest driver

Enrico Weigelt, metux IT consult lkml at metux.net
Thu Dec 3 14:00:06 EST 2020


On 02.12.20 15:15, Bartosz Golaszewski wrote:

Hi,

<snip>

> With a third entry (after gpio-mockup and gpio-aggregator) I think
> these deserve a separate submenu for "virtual GPIO drivers" or
> something like that.

just sent a separate patch.

> 
> Please keep headers ordered alphabetically.

fixed in v2.

> Don't use tabs here - not only doesn't it improve readability but
> you're not even consistent.

fixed in v2.

> 
>> +
>> +static void virtio_gpio_prepare_inbuf(struct virtio_gpio_priv *priv)
>> +{
>> +       struct scatterlist rcv_sg;
>> +
>> +       sg_init_one(&rcv_sg, &priv->rcv_buf, sizeof(priv->rcv_buf));
>> +       virtqueue_add_inbuf(priv->vq_rx, &rcv_sg, 1, &priv->rcv_buf,
>> +                           GFP_KERNEL);
>> +       virtqueue_kick(priv->vq_rx);
>> +}
>> +
>> +static int virtio_gpio_xmit(struct virtio_gpio_priv *priv, int type,
>> +                           int pin, int value, struct virtio_gpio_event *ev)
>> +{
>> +       struct scatterlist sg[1];
>> +       int ret;
>> +       unsigned long flags;
>> +
>> +       WARN_ON(!ev);
>> +
>> +       ev->type = type;
>> +       ev->pin = pin;
>> +       ev->value = value;
>> +
>> +       sg_init_table(sg, 1);
>> +       sg_set_buf(&sg[0], ev, sizeof(struct virtio_gpio_event));
>> +
>> +       spin_lock_irqsave(&priv->vq_lock, flags);
>> +       ret = virtqueue_add_outbuf(priv->vq_tx, sg, ARRAY_SIZE(sg),
>> +                                  priv, GFP_KERNEL);
>> +       if (ret < 0) {
>> +               dev_err(&priv->vdev->dev,
>> +                       "virtqueue_add_outbuf() failed: %d\n", ret);
>> +               goto out;
>> +       }
>> +       virtqueue_kick(priv->vq_tx);
>> +
>> +out:
>> +       spin_unlock_irqrestore(&priv->vq_lock, flags);
>> +       return 0;
>> +}
>> +
>> +static inline void wakeup_event(struct virtio_gpio_priv *priv, int id)
>> +{
>> +       set_bit(id, &priv->reply_wait);
>> +}
>> +
>> +static inline int check_event(struct virtio_gpio_priv *priv, int id)
>> +{
>> +       return test_bit(id, &priv->reply_wait);
>> +}
>> +
>> +static inline void clear_event(struct virtio_gpio_priv *priv, int id)
>> +{
>> +       clear_bit(id, &priv->reply_wait);
>> +}
>> +
>> +static int virtio_gpio_req(struct virtio_gpio_priv *priv, int type,
>> +                          int pin, int value)
>> +{
>> +       struct virtio_gpio_event *ev
>> +               = devm_kzalloc(&priv->vdev->dev,
>> +                              sizeof(struct virtio_gpio_event), GFP_KERNEL);
> 
> Just move the allocation to a separate line because this is super ugly.

done.

> 
>> +
>> +       if (!ev)
>> +               return -ENOMEM;
>> +
>> +       clear_event(priv, type);
>> +       virtio_gpio_xmit(priv, type, pin, value, ev);
>> +       wait_event_interruptible(priv->waitq, check_event(priv, type));
>> +
>> +       devm_kfree(&priv->vdev->dev, ev);
> 
> In fact you don't need the managed variant if you're freeing it in the
> same function. Managed resources should live as long as a device is
> attached to a driver.

thx, this was a left from originally global buffer.

>> +static void virtio_gpio_data_rx(struct virtqueue *vq)
>> +{
>> +       struct virtio_gpio_priv *priv = vq->vdev->priv;
>> +       void *data;
>> +       unsigned int len;
>> +       struct virtio_gpio_event *ev;
>> +
>> +       data = virtqueue_get_buf(priv->vq_rx, &len);
>> +       if (!data || !len) {
>> +               dev_warn(&vq->vdev->dev, "RX received no data ! %d\n", len);
>> +               return;
>> +       }
>> +
>> +       ev = data;
>> +       WARN_ON(data != &priv->rcv_buf);
> 
> Is it fine to proceed if this is the case?

Good question :o

Actually, it should never happen - if it does, we might have a bug in
either this driver or even virtio subsystem. But still it *should*
return a valid buffer (unless virtio is broken)

>> +
>> +       memcpy(&priv->last, &priv->rcv_buf, sizeof(struct virtio_gpio_event));
>> +
>> +       switch (ev->type) {
>> +       case VIRTIO_GPIO_EV_HOST_LEVEL:
>> +               virtio_gpio_signal(priv, ev->type, ev->pin, ev->value);
>> +       break;
> 
> Break should be one tab farther.

fixed in v2

>> +       default:
>> +               wakeup_event(priv, ev->type & ~VIRTIO_GPIO_EV_REPLY);
>> +       break;
>> +       }
>> +       virtio_gpio_prepare_inbuf(priv);
>> +       wake_up_all(&priv->waitq);
>> +}
>> +
>> +static int virtio_gpio_alloc_vq(struct virtio_gpio_priv *priv)
>> +{
>> +       struct virtqueue *vqs[2];
>> +       vq_callback_t *cbs[] = {
>> +               NULL,
>> +               virtio_gpio_data_rx,
>> +       };
>> +       static const char * const names[] = { "in", "out", };
>> +       int ret;
>> +
>> +       ret = virtio_find_vqs(priv->vdev, 2, vqs, cbs, names, NULL);
>> +       if (ret) {
>> +               dev_err(&priv->vdev->dev, "failed to alloc vqs: %d\n", ret);
>> +               return ret;
>> +       }
>> +
>> +       priv->vq_rx = vqs[0];
>> +       priv->vq_tx = vqs[1];
>> +
>> +       virtio_gpio_prepare_inbuf(priv);
>> +
>> +       virtio_config_enable(priv->vdev);
>> +       virtqueue_enable_cb(priv->vq_rx);
>> +       virtio_device_ready(priv->vdev);
>> +
>> +       return 0;
>> +}
>> +
>> +static int virtio_gpio_probe(struct virtio_device *vdev)
>> +{
>> +       struct virtio_gpio_priv *priv;
>> +       struct virtio_gpio_config cf = {};
>> +       char *name_buffer;
>> +       const char **gpio_names = NULL;
>> +       struct device *dev = &vdev->dev;
>> +
>> +       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>> +       if (!priv)
>> +               return -ENOMEM;
>> +
>> +       priv->name = devm_kzalloc(dev, sizeof(cf.name)+1, GFP_KERNEL);
> 
> This can fail.

ups!

>> +       spin_lock_init(&priv->vq_lock);
>> +       spin_lock_init(&priv->op_lock);
>> +
>> +       virtio_cread(vdev, struct virtio_gpio_config, version, &cf.version);
>> +       virtio_cread(vdev, struct virtio_gpio_config, num_gpios,
>> +                    &cf.num_gpios);
>> +       virtio_cread(vdev, struct virtio_gpio_config, names_size,
>> +                    &cf.names_size);
>> +       virtio_cread_bytes(vdev, offsetof(struct virtio_gpio_config, name),
>> +                          priv->name, sizeof(cf.name));
>> +
>> +       if (cf.version != 1) {
>> +               dev_err(dev, "unsupported interface version %d\n", cf.version);
>> +               return -EINVAL;
>> +       }
>> +
>> +       priv->num_gpios = cf.num_gpios;
>> +
>> +       if (cf.names_size) {
>> +               char *bufwalk;
>> +               int idx = 0;
>> +
>> +               name_buffer = devm_kzalloc(&vdev->dev, cf.names_size,
>> +                                          GFP_KERNEL)+1;
>> +               virtio_cread_bytes(vdev, sizeof(struct virtio_gpio_config),
>> +                                  name_buffer, cf.names_size);
>> +               name_buffer[cf.names_size] = 0;
>> +
>> +               gpio_names = devm_kzalloc(dev,
>> +                                         sizeof(char *) * priv->num_gpios,
>> +                                         GFP_KERNEL);
> 
> Use devm_kcalloc() for arrays.

ok.

> 
>> +               bufwalk = name_buffer;
>> +
>> +               while (idx < priv->num_gpios &&
>> +                      bufwalk < (name_buffer+cf.names_size)) {
>> +                       gpio_names[idx] = (strlen(bufwalk) ? bufwalk : NULL);
>> +                       bufwalk += strlen(bufwalk)+1;
>> +                       idx++;
> 
> 
> Something's wrong with indentation here.

i dont think so: the "bufwalk ..." line belongs to the while expression
and is right under the "idx", as it should be. I didn't want to break up
at the "<" operator. shall i do this instead ?

>> +               }
>> +       }
>> +
>> +       priv->gc.owner                  = THIS_MODULE;
>> +       priv->gc.parent                 = dev;
>> +       priv->gc.label                  = (priv->name[0] ? priv->name
>> +                                                        : dev_name(dev));
>> +       priv->gc.ngpio                  = priv->num_gpios;
>> +       priv->gc.names                  = gpio_names;
>> +       priv->gc.base                   = -1;
>> +       priv->gc.request                = virtio_gpio_request;
>> +       priv->gc.direction_input        = virtio_gpio_direction_input;
>> +       priv->gc.direction_output       = virtio_gpio_direction_output;
>> +       priv->gc.get_direction          = virtio_gpio_get_direction;
>> +       priv->gc.get                    = virtio_gpio_get;
>> +       priv->gc.set                    = virtio_gpio_set;
>> +
>> +       priv->vdev                      = vdev;
>> +       vdev->priv = priv;
>> +
>> +       priv->irq_base = devm_irq_alloc_descs(dev, -1, 0, priv->num_gpios,
>> +                                             NUMA_NO_NODE);
>> +       if (priv->irq_base < 0) {
>> +               dev_err(&vdev->dev, "failed to alloc irqs\n");
>> +               priv->irq_base = -1;
>> +               return -ENOMEM;
>> +       }
>> +
>> +       init_waitqueue_head(&priv->waitq);
>> +
>> +       priv->reply_wait = 0;
>> +
>> +       virtio_gpio_alloc_vq(priv);
>> +
>> +       return devm_gpiochip_add_data(dev, &priv->gc, priv);
>> +}
> 
> I don't know virtio at all - Michael: could you take a look at the
> code here and see if it looks good to you?
> 
>> +
>> +static void virtio_gpio_remove(struct virtio_device *vdev)
>> +{
>> +}
> 
> Just don't implement it. Or does virtio actually require the remove() callback?

yes, it does, unfortunately -- see: virtio_dev_remove()

I'll propose a patch for virtio for fixing that.


--mtx

-- 
---
Hinweis: unverschlüsselte E-Mails können leicht abgehört und manipuliert
werden ! Für eine vertrauliche Kommunikation senden Sie bitte ihren
GPG/PGP-Schlüssel zu.
---
Enrico Weigelt, metux IT consult
Free software and Linux embedded engineering
info at metux.net -- +49-151-27565287



More information about the linux-riscv mailing list