Hello Bernd,
On 15/10/2020 16:03, Der Alte wrote:
> Hi all,
>
> ok. This is the verbose u-diff for the smaller one of the 2 drivers
> /usr/src/linux-4.12.14-lp151.28.67/drivers/staging/comedi/drivers/adl_pci_7x3x.c
>
> for commenting. It was taken from openSuse leap 15.1. It also fits for
> leap15.2 ( 5.3.18 kernel) with some lines offset.
>
> I already have silent versions and diffs but perhaps some folks here
> have the HW and can test it.
> The larger adv_pci_dio.c is modified in the same style and I can also
> post it here if desired.
>
> Hope it helps and have fun, Bernd Harries
>
> BtW: my name may already / still be in the Linux source tree through the
> atyfb driver for ATI-Mach64 etc.
> graphics cards in Atari and PPC boxes I worked on with Geert
> Uytterhoeven back in the "last millenium". :-)
You've been around, then. :)
Most email clients screw up patches posted inline (rather than as an
attachment), so I can't actually use the patch as posted, but at least I
can review its contents.
>
> --- /usr/src/linux/drivers/staging/comedi/drivers/adl_pci7x3x.c
> 2020-09-04 17:23:21.000000000 +0200
> +++ ./adl_pci7x3x.c 2020-10-14 12:30:06.537000000 +0200
> @@ -56,13 +56,54 @@
>
> #include "../comedi_pci.h"
>
> +#include "plx9052.h"
> +
> +
> /*
> * Register I/O map (32-bit access only)
> */
> -#define PCI7X3X_DIO_REG 0x00
> -#define PCI743X_DIO_REG 0x04
> +#define PCI7X3X_DIO_REG 0x0000 /* im DigIO Port Bereich */
> +#define PCI743X_DIO_REG 0x0004
> +
> +#define ADL_PT_CLRIRQ 0x0040 /* im DigIO Port Bereich */
> +
> +#define ADL_OP_LINTCSR 0x004C /* im LCR Bereich */
> +#define ADL_OP_CNTRL 0x0050 /* im LCR Bereich */
> +#define ADL_OP_EEPROM 0x0053 /* im LCR Bereich */
You could use the existing PLX9052_INTCSR and PLX9052_CNTRL from
"plx9052.h" instead of ADL_OP_LINTCSR and ADL_OP_CNTRL.
Also, best to stick to English in the kernel sources. I'm not just
saying that because I happen to be English. :) You can think of it as
the official language of the Linux kernel development process.
> +
> +#define LINTi1_EN_HI (PLX9052_INTCSR_LI1ENAB | PLX9052_INTCSR_LI1POL)
> +#define LINTi2_EN_HI (PLX9052_INTCSR_LI2ENAB | PLX9052_INTCSR_LI2POL)
> +
> +#define LINTi1_EN_ACT_IDI0 (PLX9052_INTCSR_LI1ENAB |
> PLX9052_INTCSR_LI1STAT)
> +#define LINTi2_EN_ACT_IDI1 (PLX9052_INTCSR_LI2ENAB |
> PLX9052_INTCSR_LI2STAT)
> +
> +#define EN_PCI_LINT2EH_LINT1EH (PLX9052_INTCSR_PCIENAB | LINTi2_EN_HI |
> LINTi1_EN_HI)
> +#define EN_PCI_LINT2H_LINT1H (PLX9052_INTCSR_PCIENAB |
> PLX9052_INTCSR_LI2POL | PLX9052_INTCSR_LI1POL)
> +
>
> -enum apci1516_boardid {
Well done for spotting the cut-n-paste error in that enum type!
> +#define FuncId if( adl_pci7x3x_debug & 0x00000100) printk
> +#define FuncDone if( adl_pci7x3x_debug & 0x00000200) printk
> +#define CFuncId if( adl_pci7x3x_debug & 0x00000400) printk
> +#define CFuncDone if( adl_pci7x3x_debug & 0x00000800) printk
> +#define Pprintf if( adl_pci7x3x_debug & 0x00100000) printk
> +#define BHAprintf if( adl_pci7x3x_debug & 0x00000020) printk
> +#define BHMprintf if( adl_pci7x3x_debug & 0x00000010) printk
> +
> +#define ISRprintf if( adl_pci7x3x_debug & 0x80000000) printk
> +
> +#define Dprintf if( adl_pci7x3x_debug & 0x00800000) printk
> +
> +#define EDprintf if((adl_pci7x3x_debug & 0x00808000) ==
> 0x00808000) printk
> +
> +#define Mprintf if( adl_pci7x3x_debug & 0x00001000) printk
> +#define Iprintf if( adl_pci7x3x_debug & 0x00002000) printk
> +#define Wprintf if( adl_pci7x3x_debug & 0x00008000) printk
> +#define Eprintf if( adl_pci7x3x_debug & 0x00010000) printk
> +#define Fprintf if( adl_pci7x3x_debug & 0x00020000) printk
> +
> +#define DATETIME "2020_10_14_12:25 "
> +
All that will be gone by the time it gets merged upstream.
> +enum adl_pci7x3x_boardid {
> BOARD_PCI7230,
> BOARD_PCI7233,
> BOARD_PCI7234,
> @@ -76,14 +117,16 @@
> int nsubdevs;
> int di_nchan;
> int do_nchan;
> + int irq_nchan;
I was thinking the name 'irq_nchan' doesn't really match the usage of
the others since it spans more than one subdevice, but in fact it does
match because 'do_nchan' can also span more than one subdevice. No problem.
> };
>
> static const struct adl_pci7x3x_boardinfo adl_pci7x3x_boards[] = {
> [BOARD_PCI7230] = {
> .name = "adl_pci7230",
> - .nsubdevs = 2,
> + .nsubdevs = 4, /* IDI, IDO, IRQ_IDI0, IRQ_IDI1 */
> .di_nchan = 16,
> .do_nchan = 16,
> + .irq_nchan = 2,
> },
> [BOARD_PCI7233] = {
> .name = "adl_pci7233",
> @@ -113,13 +156,129 @@
> }
> };
>
> +
> +static u32 adl_pci7x3x_debug = 0x80838300;
> +
> +struct adl_pci7x3x_dev_private_data
> +{
> + unsigned long lcr_io_base;
> + int boardtype;
The boardtype member seems redundant. More about that further down....
> + int dio;
That one doesn't seem to be used anywhere.
> + short int int_ctrl;
Better to use 'unsigned short' so it matches the intcsr variable used
elsewhere in the driver.
> +
> +};
> +
> +
> +struct adl_pci7x3x_sd_private_data
This private data is specific to the newly added 'interrupt' subdevices,
so it would be nice if the name reflected that, but it doesn't really
matter.
> +{
> + unsigned long port_offset;
> + short int sd_idx;
The 'sd_idx' member is redundant because 's->index' can be used instead.
The usual way to get at the static board data associated with the device
is to use a pointer variable initialized to 'dev->board_ptr', e.g.:
const struct adl_pci7x3x_boardinfo * const board = dev->board_ptr;
That will point to the specific element of adl_pci7x3x_boards[].
This 'if' statement is unnecessary, because there is no point setting up
an interrupt handler for boards that can't support it.
It is possible for both interrupt sources to be active here. Both
should be handled. The easiest way to do that would be to put the block
below into a function and call it for each of the active interrupt sources.
> +
> +
> + /*block*/
> + {
> + struct comedi_subdevice * sd_p = &dev->subdevices[subdev];
> + struct adl_pci7x3x_sd_private_data * sd_priv = sd_p->private;
> + unsigned long reg = (unsigned long)sd_priv->port_offset;
> +
> + struct comedi_async * async_p = sd_p->async;
> +
> + ISRprintf(" adl IFL=%04X %s sd%u r_of=%02lX cmd=%d ", intcsr,
> pin_name, subdev, reg, sd_priv->cmd_running);
> + if(async_p)
> + {
> + sd_p->state = inw(dev->iobase + reg);
> + if(sd_priv->cmd_running)
> + {
> + comedi_buf_write_samples(sd_p, &sd_p->state, 1); /*
> 2020_09_21: Freezes the kernel without cmd running */
> + }
It would be safer to use a spin-lock around that whole
'if(sd_priv->cmd_running) { ... }' statement (but release the spin-lock
before calling comedi_handle_events() below).
Does the 'adl_pci7x3x_do_insn_bits' function really need to change?
Different subdevices do not need to use 's->private' in the same way.
Those two spin-locked sections could be combined into a single
spin-locked section. The 'if' statements just need to store the
appropriate value (either PLX9052_INTCSR_LI1ENAB or
PLX9052_INTCSR_LI2ENAB) in a local variable so that
'dev_private->int_ctrl' can then be updated within the common
spin-locked section.
> + //outb(EN_PCI_LINT2EH_LINT1EH, dev_private->lcr_io_base +
> ADL_OP_LINTCSR); /* 0x5B enable PCI + IDI sdi[0] Ch 1 Ch 0 IRQ On
> ActHigh */
> + outb(dev_private->int_ctrl, dev_private->lcr_io_base + ADL_OP_LINTCSR);
It would be safer to put that 'outb()' call within the spin-locked
section too.
> +
> + sd_priv->cmd_running = 1;
I would also suggest setting 'sd_priv->cmd_running' within a spin-locked
section. You could either use 'dev->spinlock' or create a spin-lock in
the subdevice private data and use that, whichever is easiest (but use
the same spin-lock in the interrupt handler!).
> +
> + return 0;
> +}
> +
> +static int adl_pci7x3x_asy_cancel(struct comedi_device *dev,
> + struct comedi_subdevice *s)
> +{
> + struct adl_pci7x3x_dev_private_data *dev_private = dev->private;
> + struct adl_pci7x3x_sd_private_data * sd_priv = s->private;
> + unsigned long cpu_flags;
> +
> + FuncId("adl_pci7x3x_asy_cancel() \n");
> + sd_priv->cmd_running = 0;
As above, I suggest using a spin-lock for 'sd_priv->cmd_running'.
> + /* disable Interrupts */
> + if(sd_priv->sd_idx == 2)
> + {
> + Dprintf(" disable IDI CH 0 IRQ... \n");
> + spin_lock_irqsave(&dev->spinlock, cpu_flags);
> + dev_private->int_ctrl &= ~PLX9052_INTCSR_LI1ENAB; /* Disable
> LINTi1 */
> + spin_unlock_irqrestore(&dev->spinlock, cpu_flags);
> + }
> + if(sd_priv->sd_idx == 3)
> + {
> + Dprintf(" disable IDI CH 1 IRQ... \n");
> + spin_lock_irqsave(&dev->spinlock, cpu_flags);
> + dev_private->int_ctrl &= ~PLX9052_INTCSR_LI2ENAB; /* Disable
> LINTi2 */
> + spin_unlock_irqrestore(&dev->spinlock, cpu_flags);
> + }
As above, those can use a common spin-locked section.
> + //outb(0x00, dev_private->lcr_io_base + ADL_OP_LINTCSR); /*
> Disable PCI + LINTi2 + LINTi1 */
> + outb(dev_private->int_ctrl, dev_private->lcr_io_base + ADL_OP_LINTCSR);
And that 'outb()' call can go in the spin-locked section.
> +
> + return 0;
> +}
> +
> +static int adl_pci7x3x_reset(struct comedi_device *dev)
> +{
> + struct adl_pci7x3x_dev_private_data *dev_private = dev->private;
> +
> + /* disable Interrupts */
> + dev_private->int_ctrl = 0x00; /* Disable PCI + LINTi2 + LINTi1 */
> + outb(dev_private->int_ctrl, dev_private->lcr_io_base + ADL_OP_LINTCSR);
> +
> + return 0;
> +}
The IRQ handler may still be set up when 'adl_pci7x3x_reset' is called,
so I suggest using 'dev->spinlock'.
> +
> +
> static int adl_pci7x3x_auto_attach(struct comedi_device *dev,
> unsigned long context)
> {
> - struct pci_dev *pcidev = comedi_to_pci_dev(dev);
> - const struct adl_pci7x3x_boardinfo *board = NULL;
> - struct comedi_subdevice *s;
> + struct pci_dev * pcidev = comedi_to_pci_dev(dev);
> + struct adl_pci7x3x_dev_private_data * dev_private = dev->private;
'dev->private' will be NULL when this is called. Actually, it isn't
really necessary to initialize the 'dev_private' variable here because
it is assigned to below.
I suggest also checking 'board_p->irq_nchan' to avoid setting up the
interrupt handler if not needed.
> + ret = request_irq(pcidev->irq, adl_pci7x3x_interrupt,
> IRQF_SHARED, dev->board_name, dev);
> + if (ret == 0)
> + {
> + dev->irq = pcidev->irq; /* muss gesetzt werden, bevor es
> abgefragt wird! */
> + Dprintf(" prep IDI CH 0 HI + CH 1 HI IRQs... ");
> + dev_private->int_ctrl = EN_PCI_LINT2H_LINT1H; /* 0x52 enable
> PCI + IDI sdi[0] Ch 1 Ch 0 IRQ Off ActHigh */
> + outb(dev_private->int_ctrl, dev_private->lcr_io_base +
> ADL_OP_LINTCSR);
Safer to lock 'dev->spinlock' when updating the 'int_ctrl' member and
writing to the INTCSR register.
The original version of this subdevice is fine, no need to allocate the
private data.
>
> subdev++;
>
> - nchan = board->di_nchan - nchan;
> + nchan = board_p->di_nchan - nchan;
> if (nchan) {
> s = &dev->subdevices[subdev];
> /* Isolated digital inputs 32 to 63 */
> @@ -207,14 +526,22 @@
> s->insn_bits = adl_pci7x3x_di_insn_bits;
> s->range_table = &range_digital;
>
> - s->private = (void *)PCI743X_DIO_REG;
> + sd_priv = comedi_alloc_spriv(s, sizeof(*sd_priv));
> + if(!sd_priv)
> + {
> + return -ENOMEM;
> + }
> + /*endif(!sd_priv)*/
> + sd_priv->port_offset = PCI7X3X_DIO_REG;
> + sd_priv->sd_idx = subdev;
> + sd_priv->cmd_running = 0;
Ditto.
>
> subdev++;
> }
> }
>
> - if (board->do_nchan) {
> - nchan = min(board->do_nchan, 32);
> + if (board_p->do_nchan) {
> + nchan = min(board_p->do_nchan, 32);
>
> s = &dev->subdevices[subdev];
> /* Isolated digital outputs 0 to 15/31 */
> @@ -225,11 +552,19 @@
> s->insn_bits = adl_pci7x3x_do_insn_bits;
> s->range_table = &range_digital;
>
> - s->private = (void *)PCI7X3X_DIO_REG;
> + sd_priv = comedi_alloc_spriv(s, sizeof(*sd_priv));
> + if(!sd_priv)
> + {
> + return -ENOMEM;
> + }
> + /*endif(!sd_priv)*/
> + sd_priv->port_offset = PCI7X3X_DIO_REG;
> + sd_priv->sd_idx = subdev;
> + sd_priv->cmd_running = 0;
Ditto.
>
> subdev++;
>
> - nchan = board->do_nchan - nchan;
> + nchan = board_p->do_nchan - nchan;
> if (nchan) {
> s = &dev->subdevices[subdev];
> /* Isolated digital outputs 32 to 63 */
> @@ -240,25 +575,90 @@
> s->insn_bits = adl_pci7x3x_do_insn_bits;
> s->range_table = &range_digital;
>
> - s->private = (void *)PCI743X_DIO_REG;
> + sd_priv = comedi_alloc_spriv(s, sizeof(*sd_priv));
> + if(!sd_priv)
> + {
> + return -ENOMEM;
> + }
> + /*endif(!sd_priv)*/
> + sd_priv->port_offset = PCI7X3X_DIO_REG;
> + sd_priv->sd_idx = subdev;
> + sd_priv->cmd_running = 0;
Ditto.
>
> subdev++;
> }
> }
>
> + for (ic = 0; ic < board_p->irq_nchan; ++ic) {
> + nchan = 1;
> +
> + s = &dev->subdevices[subdev];
> + /* Isolated digital inputs 0 or 1 */
> + s->type = COMEDI_SUBD_DI;
> + s->subdev_flags = SDF_READABLE;
> + s->n_chan = nchan;
> + s->maxdata = 1;
> + s->insn_bits = adl_pci7x3x_di_insn_bits;
A different 'insn_bits' handler should be used for these 'interrupt'
subdevices. It doesn't really need to return anything meaningful since
it only has a single one-bit channel. Perhaps it could check the state
of the IDI0 or IDI1 line?
'dev->private' could be NULL here (although it is unlikely), leading to
a null pointer dereference bug. (The 'detach' handler gets called to
clean up even if the 'auto_attach' handler returned an error.)
Apart from printing the message (which will be removed anyway, probably
:) ), the freeing of the IRQ handler could be left to the
'comedi_pci_detach()' call below. By the way, the message could use the
'dev->board_name' instead of 'boardtype'. (In case the 'auto_attach'
handler returned an error before setting 'dev->board_name', it will
still be a non-NULL pointer because it is initialized to the driver name
before the 'auto_attach' handler is called. Also, the 'auto_attach'
handler is setting 'dev->board_name' before setting up the interrupt
handler anyway, so it should be the correct string.)
> +
> + //comedi_free_devpriv(dev);
> + comedi_pci_detach(dev);
> +}
> +
>
> static struct comedi_driver adl_pci7x3x_driver = {
> .driver_name = "adl_pci7x3x",
> .module = THIS_MODULE,
> .auto_attach = adl_pci7x3x_auto_attach,
> - .detach = comedi_pci_detach,
> + .detach = adl_pci7x3x_detach,
> };
>
> static int adl_pci7x3x_pci_probe(struct pci_dev *dev,
> const struct pci_device_id *id)
> {
> + FuncId("adl_pci7x3x_pci_probe() %s \n", DATETIME);
> return comedi_pci_auto_config(dev, &adl_pci7x3x_driver,
> id->driver_data);
> }
That's all for now.
Best regards,
Ian