Re: [PATCH RFC/RFT 1/1] misc: add simple logic analyzer using polling

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

 



On Tue, Mar 30, 2021 at 11:58 AM Wolfram Sang
<wsa+renesas@xxxxxxxxxxxxxxxxxxxx> wrote:
>
> This is a simple logic analyzer using GPIO polling. It comes with a
> script to isolate a CPU for polling. While this is definately not a

definitely

> production level analyzer, it can be a helpful first view when remote
> debugging. Read the documentation for details.

I would like to look at it closer, but don't have time right now. So,
some kind of a shallow review.

But the idea is, let's say, interesting.

> Signed-off-by: Wolfram Sang <wsa+renesas@xxxxxxxxxxxxxxxxxxxx>

...

> +The binding documentation is in the ``misc`` folder of the Kernel binding
> +documentation.

Can't you give a reference in terms of reST format?

...

> +Note that the process will return you back to the prompt but a sub-process is
> +still sampling in the background. Unless this finished, you will not find a

this is finished

> +result file in the current or specified directory. Please also note that
> +currently this sub-process is not killable! For the above example, we will then
> +need to trigger I2C communication:

Shouldn't you use :: instead of : to mark sections as code excerpts?

> +    i2cdetect -y -r <your bus number>

...

> +config GPIO_LOGIC_ANALYZER
> +       tristate "Simple GPIO logic analyzer"
> +       depends on GPIOLIB || COMPILE_TEST
> +       help
> +         This option enables support for a simple logic analyzer using polled
> +         GPIOs. Use the 'tools/debugging/gpio-logic-analyzer' script with this
> +         driver. The script will make using it easier and can also isolate a
> +         CPU for the polling task. Note that this is still a last resort
> +         analyzer which can be affected by latencies and non-determinant code
> +         paths. However, for e.g. remote development, it may be useful to get
> +         a first view and aid further debugging.

Module name?

...

> +#include <linux/of.h>

Can you switch to use device property API?

...

> +#define GPIO_LA_NAME "gpio-logic-analyzer"
> +#define GPIO_LA_DEFAULT_BUF_SIZE SZ_256K

> +/* can be increased if needed */
> +#define GPIO_LA_MAX_PROBES 8
> +#define GPIO_LA_PROBES_MASK 7

Does this assume the power-of-two number of probes?
Perhaps using BIT(x) and (BIT(x) - 1) will clarify that.

...

> +struct gpio_la_poll_priv {
> +       unsigned long ndelay;
> +       u32 buf_idx;
> +       struct mutex lock;
> +       struct debugfs_blob_wrapper blob;
> +       struct gpio_descs *descs;
> +       struct dentry *debug_dir, *blob_dent;
> +       struct debugfs_blob_wrapper meta;
> +       unsigned long gpio_delay;
> +       unsigned int trigger_len;

> +       u8 trigger_data[PAGE_SIZE];

This is not good for fragmentation (basically you make your struct to
occupy 2 pages, one of which will be almost wasted). Better to have a
pointer here and allocate one page by get_zero_page() or so.

> +};
> +
> +static struct dentry *gpio_la_poll_debug_dir;
> +
> +static int fops_capture_set(void *data, u64 val)
> +{
> +       struct gpio_la_poll_priv *priv = data;
> +       u8 *la_buf = priv->blob.data;
> +       unsigned long state = 0;
> +       int i, ret;
> +
> +       if (!la_buf)
> +               return -ENOMEM;

> +       if (val) {

if (!val)
  return 0;

makes your life easier.

> +               mutex_lock(&priv->lock);
> +               if (priv->blob_dent) {
> +                       debugfs_remove(priv->blob_dent);
> +                       priv->blob_dent = NULL;
> +               }
> +
> +               priv->buf_idx = 0;
> +
> +               local_irq_disable();
> +               preempt_disable_notrace();
> +
> +               for (i = 0; i < priv->trigger_len; i++) {
> +                       u8 data = priv->trigger_data[i];
> +                       do {
> +                               ret = gpiod_get_array_value(priv->descs->ndescs, priv->descs->desc,
> +                                                           priv->descs->info, &state);
> +
> +                               if (ret)
> +                                       goto gpio_err;
> +                       } while (!!(state & BIT(data & GPIO_LA_PROBES_MASK)) != !!(data & 0x80));
> +               }
> +
> +               if (priv->trigger_len) {
> +                       la_buf[priv->buf_idx++] = state;
> +                       ndelay(priv->ndelay);
> +               }
> +
> +               while (priv->buf_idx < priv->blob.size && ret == 0) {
> +                       ret = gpiod_get_array_value(priv->descs->ndescs, priv->descs->desc,
> +                                             priv->descs->info, &state);
> +                       la_buf[priv->buf_idx++] = state;
> +                       ndelay(priv->ndelay);
> +               }
> +gpio_err:
> +               preempt_enable_notrace();
> +               local_irq_enable();
> +               if (ret)

> +                       pr_err("%s: couldn't read GPIOs: %d\n", __func__, ret);

Haven't noticed if you are using pr_fmt(). It may be better than using __func__.

Btw, it seems you have a struct device for that or so. Why don't you
use dev_err()?

> +               priv->blob_dent = debugfs_create_blob("sample_data", 0400, priv->debug_dir, &priv->blob);
> +               mutex_unlock(&priv->lock);
> +       }
> +
> +       return 0;
> +}
> +DEFINE_DEBUGFS_ATTRIBUTE(fops_capture, NULL, fops_capture_set, "%llu\n");


> +static ssize_t trigger_write(struct file *file, const char __user *ubuf,
> +                            size_t count, loff_t *offset)
> +{
> +       struct seq_file *m = file->private_data;
> +       struct gpio_la_poll_priv *priv = m->private;
> +       char *buf;
> +       int i, trigger_len = 0;
> +
> +       priv->trigger_len = 0;
> +
> +       if (count & 1)
> +           return -EINVAL;
> +
> +       buf = memdup_user(ubuf, count);
> +       if (IS_ERR(buf))
> +               return PTR_ERR(buf);
> +
> +       for (i = 0; i < count; i += 2) {
> +               u8 val;
> +
> +               if (buf[i] < '1' || buf[i] > '0' + GPIO_LA_MAX_PROBES)

So, you can't increase the amount of probes without breaking this
entire parser (it will go somewhere to symbols and letters...).

> +                       goto bail_out;
> +
> +               val = buf[i] - '1';
> +
> +               switch (toupper(buf[i + 1])) {
> +               case 'L':
> +                       priv->trigger_data[trigger_len] = val;
> +                       trigger_len++;
> +                       break;
> +               case 'H':
> +                       priv->trigger_data[trigger_len] = val | 0x80;
> +                       trigger_len++;
> +                       break;
> +               case 'R':
> +                       priv->trigger_data[trigger_len] = val;
> +                       priv->trigger_data[trigger_len + 1] = val | 0x80;
> +                       trigger_len += 2;
> +                       break;
> +               case 'F':
> +                       priv->trigger_data[trigger_len] = val | 0x80;
> +                       priv->trigger_data[trigger_len + 1] = val;
> +                       trigger_len += 2;
> +                       break;
> +               default:
> +                       goto bail_out;
> +               }
> +
> +               if (trigger_len > PAGE_SIZE)    /* should never happen */

Shouldn't you return OVERFLOW here or something like that?

> +                       goto bail_out;
> +
> +       }
> +
> +       priv->trigger_len = trigger_len;
> +
> +bail_out:
> +       kfree(buf);
> +       return priv->trigger_len ? count : -EINVAL;
> +}

I'm not a fan of yet another parser in the kernel. Can you provide a
bit of description of the format?

...

> +       priv->debug_dir = debugfs_create_dir(dev_name(dev), gpio_la_poll_debug_dir);

> +       if (IS_ERR(priv->debug_dir))
> +               return PTR_ERR(priv->debug_dir);

Shouldn't be checked AFAIU.

...

> +static const struct of_device_id gpio_la_poll_of_match[] = {
> +       { .compatible = GPIO_LA_NAME, },

> +       { },

No comma needed.

> +};
> +MODULE_DEVICE_TABLE(of, gpio_la_poll_of_match);

...

> +       -t|--trigger <str>: pattern to use as trigger. <str> consists of n two-char pairs. First
> +                           char is channel number starting at "1". Second char is trigger level:
> +                           "L" - low; "H" - high; "R" - rising; "F" - falling
> +Examples:
> +Samples $NUMSAMPLES at 1MHz with already prepared CPU or automatically prepare CPU1 if needed

with an already
prepares

> +       '$0'
> +Samples 50us at 2MHz waiting for falling edge on channel 2. CPU usage as above.
> +       '$0 -d 50 -s 2000000 -t "2F"'
> +
> +Note that the process exits after checking all parameters but a sub-process still works in
> +the background. The result is only available once the sub-process finished. As the time of

finishes

> +writing, the sub-process is not killable, so be extra careful that your triggers work.
> +
> +Result is a .sr file to be consumed with PulseView from the free Sigrok project. It is
> +a zip file which also contains the binary sample data which may be consumed by others.
> +The filename is the logic analyzer instance name plus a since-epoch timestamp.
> +EOF
> +}

-- 
With Best Regards,
Andy Shevchenko



[Index of Archives]     [Linux Samsung SOC]     [Linux Wireless]     [Linux Kernel]     [ATH6KL]     [Linux Bluetooth]     [Linux Netdev]     [Kernel Newbies]     [IDE]     [Security]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux ATA RAID]     [Samba]     [Device Mapper]

  Powered by Linux