From 10d28cffb3f6ec7ad67f0a4cd32c2afa92909452 Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Wed, 3 Dec 2025 16:24:38 +0000 Subject: comedi: Fix getting range information for subdevices 16 to 255 The `COMEDI_RANGEINFO` ioctl does not work properly for subdevice indices above 15. Currently, the only in-tree COMEDI drivers that support more than 16 subdevices are the "8255" driver and the "comedi_bond" driver. Making the ioctl work for subdevice indices up to 255 is achievable. It needs minor changes to the handling of the `COMEDI_RANGEINFO` and `COMEDI_CHANINFO` ioctls that should be mostly harmless to user-space, apart from making them less broken. Details follow... The `COMEDI_RANGEINFO` ioctl command gets the list of supported ranges (usually with units of volts or milliamps) for a COMEDI subdevice or channel. (Only some subdevices have per-channel range tables, indicated by the `SDF_RANGETYPE` flag in the subdevice information.) It uses a `range_type` value and a user-space pointer, both supplied by user-space, but the `range_type` value should match what was obtained using the `COMEDI_CHANINFO` ioctl (if the subdevice has per-channel range tables) or `COMEDI_SUBDINFO` ioctl (if the subdevice uses a single range table for all channels). Bits 15 to 0 of the `range_type` value contain the length of the range table, which is the only part that user-space should care about (so it can use a suitably sized buffer to fetch the range table). Bits 23 to 16 store the channel index, which is assumed to be no more than 255 if the subdevice has per-channel range tables, and is set to 0 if the subdevice has a single range table. For `range_type` values produced by the `COMEDI_SUBDINFO` ioctl, bits 31 to 24 contain the subdevice index, which is assumed to be no more than 255. But for `range_type` values produced by the `COMEDI_CHANINFO` ioctl, bits 27 to 24 contain the subdevice index, which is assumed to be no more than 15, and bits 31 to 28 contain the COMEDI device's minor device number for some unknown reason lost in the mists of time. The `COMEDI_RANGEINFO` ioctl extract the length from bits 15 to 0 of the user-supplied `range_type` value, extracts the channel index from bits 23 to 16 (only used if the subdevice has per-channel range tables), extracts the subdevice index from bits 27 to 24, and ignores bits 31 to 28. So for subdevice indices 16 to 255, the `COMEDI_SUBDINFO` or `COMEDI_CHANINFO` ioctl will report a `range_type` value that doesn't work with the `COMEDI_RANGEINFO` ioctl. It will either get the range table for the subdevice index modulo 16, or will fail with `-EINVAL`. To fix this, always use bits 31 to 24 of the `range_type` value to hold the subdevice index (assumed to be no more than 255). This affects the `COMEDI_CHANINFO` and `COMEDI_RANGEINFO` ioctls. There should not be anything in user-space that depends on the old, broken usage, although it may now see different values in bits 31 to 28 of the `range_type` values reported by the `COMEDI_CHANINFO` ioctl for subdevices that have per-channel subdevices. User-space should not be trying to decode bits 31 to 16 of the `range_type` values anyway. Fixes: ed9eccbe8970 ("Staging: add comedi core") Cc: stable@vger.kernel.org #5.17+ Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20251203162438.176841-1-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/comedi_fops.c | 2 +- drivers/comedi/range.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/comedi') diff --git a/drivers/comedi/comedi_fops.c b/drivers/comedi/comedi_fops.c index 657c98cd723e..2c3eb9e89571 100644 --- a/drivers/comedi/comedi_fops.c +++ b/drivers/comedi/comedi_fops.c @@ -1155,7 +1155,7 @@ static int do_chaninfo_ioctl(struct comedi_device *dev, for (i = 0; i < s->n_chan; i++) { int x; - x = (dev->minor << 28) | (it->subdev << 24) | (i << 16) | + x = (it->subdev << 24) | (i << 16) | (s->range_table_list[i]->length); if (put_user(x, it->rangelist + i)) return -EFAULT; diff --git a/drivers/comedi/range.c b/drivers/comedi/range.c index 8f43cf88d784..5b8f662365e3 100644 --- a/drivers/comedi/range.c +++ b/drivers/comedi/range.c @@ -52,7 +52,7 @@ int do_rangeinfo_ioctl(struct comedi_device *dev, const struct comedi_lrange *lr; struct comedi_subdevice *s; - subd = (it->range_type >> 24) & 0xf; + subd = (it->range_type >> 24) & 0xff; chan = (it->range_type >> 16) & 0xff; if (!dev->attached) -- cgit v1.2.3 From e03b29b55f2b7c345a919a6ee36633b06bf3fb56 Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Mon, 12 Jan 2026 16:28:35 +0000 Subject: comedi: dmm32at: serialize use of paged registers Some of the hardware registers of the DMM-32-AT board are multiplexed, using the least significant two bits of the Miscellaneous Control register to select the function of registers at offsets 12 to 15: 00 => 8254 timer/counter registers are accessible 01 => 8255 digital I/O registers are accessible 10 => Reserved 11 => Calibration registers are accessible The interrupt service routine (`dmm32at_isr()`) clobbers the bottom two bits of the register with value 00, which would interfere with access to the 8255 registers by the `dm32at_8255_io()` function (used for Comedi instruction handling on the digital I/O subdevice). Make use of the generic Comedi device spin-lock `dev->spinlock` (which is otherwise unused by this driver) to serialize access to the miscellaneous control register and paged registers. Fixes: 3c501880ac44 ("Staging: comedi: add dmm32at driver") Cc: stable@vger.kernel.org Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20260112162835.91688-1-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/drivers/dmm32at.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) (limited to 'drivers/comedi') diff --git a/drivers/comedi/drivers/dmm32at.c b/drivers/comedi/drivers/dmm32at.c index 644e3b643c79..910cd24b1bed 100644 --- a/drivers/comedi/drivers/dmm32at.c +++ b/drivers/comedi/drivers/dmm32at.c @@ -330,6 +330,7 @@ static int dmm32at_ai_cmdtest(struct comedi_device *dev, static void dmm32at_setaitimer(struct comedi_device *dev, unsigned int nansec) { + unsigned long irq_flags; unsigned char lo1, lo2, hi2; unsigned short both2; @@ -342,6 +343,9 @@ static void dmm32at_setaitimer(struct comedi_device *dev, unsigned int nansec) /* set counter clocks to 10MHz, disable all aux dio */ outb(0, dev->iobase + DMM32AT_CTRDIO_CFG_REG); + /* serialize access to control register and paged registers */ + spin_lock_irqsave(&dev->spinlock, irq_flags); + /* get access to the clock regs */ outb(DMM32AT_CTRL_PAGE_8254, dev->iobase + DMM32AT_CTRL_REG); @@ -354,6 +358,8 @@ static void dmm32at_setaitimer(struct comedi_device *dev, unsigned int nansec) outb(lo2, dev->iobase + DMM32AT_CLK2); outb(hi2, dev->iobase + DMM32AT_CLK2); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + /* enable the ai conversion interrupt and the clock to start scans */ outb(DMM32AT_INTCLK_ADINT | DMM32AT_INTCLK_CLKEN | DMM32AT_INTCLK_CLKSEL, @@ -363,13 +369,19 @@ static void dmm32at_setaitimer(struct comedi_device *dev, unsigned int nansec) static int dmm32at_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) { struct comedi_cmd *cmd = &s->async->cmd; + unsigned long irq_flags; int ret; dmm32at_ai_set_chanspec(dev, s, cmd->chanlist[0], cmd->chanlist_len); + /* serialize access to control register and paged registers */ + spin_lock_irqsave(&dev->spinlock, irq_flags); + /* reset the interrupt just in case */ outb(DMM32AT_CTRL_INTRST, dev->iobase + DMM32AT_CTRL_REG); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + /* * wait for circuit to settle * we don't have the 'insn' here but it's not needed @@ -429,8 +441,13 @@ static irqreturn_t dmm32at_isr(int irq, void *d) comedi_handle_events(dev, s); } + /* serialize access to control register and paged registers */ + spin_lock(&dev->spinlock); + /* reset the interrupt */ outb(DMM32AT_CTRL_INTRST, dev->iobase + DMM32AT_CTRL_REG); + + spin_unlock(&dev->spinlock); return IRQ_HANDLED; } @@ -481,14 +498,25 @@ static int dmm32at_ao_insn_write(struct comedi_device *dev, static int dmm32at_8255_io(struct comedi_device *dev, int dir, int port, int data, unsigned long regbase) { + unsigned long irq_flags; + int ret; + + /* serialize access to control register and paged registers */ + spin_lock_irqsave(&dev->spinlock, irq_flags); + /* get access to the DIO regs */ outb(DMM32AT_CTRL_PAGE_8255, dev->iobase + DMM32AT_CTRL_REG); if (dir) { outb(data, dev->iobase + regbase + port); - return 0; + ret = 0; + } else { + ret = inb(dev->iobase + regbase + port); } - return inb(dev->iobase + regbase + port); + + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + + return ret; } /* Make sure the board is there and put it to a known state */ -- cgit v1.2.3