linux-stable/drivers/comedi/drivers/vmk80xx.c

871 lines
20 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0+
/*
* vmk80xx.c
* Velleman USB Board Low-Level Driver
*
* Copyright (C) 2009 Manuel Gebele <forensixs@gmx.de>, Germany
*
* COMEDI - Linux Control and Measurement Device Interface
* Copyright (C) 2000 David A. Schleef <ds@schleef.org>
*/
/*
* Driver: vmk80xx
* Description: Velleman USB Board Low-Level Driver
* Devices: [Velleman] K8055 (K8055/VM110), K8061 (K8061/VM140),
* VM110 (K8055/VM110), VM140 (K8061/VM140)
* Author: Manuel Gebele <forensixs@gmx.de>
* Updated: Sun, 10 May 2009 11:14:59 +0200
* Status: works
*
* Supports:
* - analog input
* - analog output
* - digital input
* - digital output
* - counter
* - pwm
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/errno.h>
#include <linux/input.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/uaccess.h>
#include <linux/comedi/comedi_usb.h>
enum {
DEVICE_VMK8055,
DEVICE_VMK8061
};
#define VMK8055_DI_REG 0x00
#define VMK8055_DO_REG 0x01
#define VMK8055_AO1_REG 0x02
#define VMK8055_AO2_REG 0x03
#define VMK8055_AI1_REG 0x02
#define VMK8055_AI2_REG 0x03
#define VMK8055_CNT1_REG 0x04
#define VMK8055_CNT2_REG 0x06
#define VMK8061_CH_REG 0x01
#define VMK8061_DI_REG 0x01
#define VMK8061_DO_REG 0x01
#define VMK8061_PWM_REG1 0x01
#define VMK8061_PWM_REG2 0x02
#define VMK8061_CNT_REG 0x02
#define VMK8061_AO_REG 0x02
#define VMK8061_AI_REG1 0x02
#define VMK8061_AI_REG2 0x03
#define VMK8055_CMD_RST 0x00
#define VMK8055_CMD_DEB1_TIME 0x01
#define VMK8055_CMD_DEB2_TIME 0x02
#define VMK8055_CMD_RST_CNT1 0x03
#define VMK8055_CMD_RST_CNT2 0x04
#define VMK8055_CMD_WRT_AD 0x05
#define VMK8061_CMD_RD_AI 0x00
#define VMK8061_CMR_RD_ALL_AI 0x01 /* !non-active! */
#define VMK8061_CMD_SET_AO 0x02
#define VMK8061_CMD_SET_ALL_AO 0x03 /* !non-active! */
#define VMK8061_CMD_OUT_PWM 0x04
#define VMK8061_CMD_RD_DI 0x05
#define VMK8061_CMD_DO 0x06 /* !non-active! */
#define VMK8061_CMD_CLR_DO 0x07
#define VMK8061_CMD_SET_DO 0x08
#define VMK8061_CMD_RD_CNT 0x09 /* TODO: completely pointless? */
#define VMK8061_CMD_RST_CNT 0x0a /* TODO: completely pointless? */
#define VMK8061_CMD_RD_VERSION 0x0b /* internal usage */
#define VMK8061_CMD_RD_JMP_STAT 0x0c /* TODO: not implemented yet */
#define VMK8061_CMD_RD_PWR_STAT 0x0d /* internal usage */
#define VMK8061_CMD_RD_DO 0x0e
#define VMK8061_CMD_RD_AO 0x0f
#define VMK8061_CMD_RD_PWM 0x10
#define IC3_VERSION BIT(0)
#define IC6_VERSION BIT(1)
#define MIN_BUF_SIZE 64
#define PACKET_TIMEOUT 10000 /* ms */
enum vmk80xx_model {
VMK8055_MODEL,
VMK8061_MODEL
};
static const struct comedi_lrange vmk8061_range = {
2, {
UNI_RANGE(5),
UNI_RANGE(10)
}
};
struct vmk80xx_board {
const char *name;
enum vmk80xx_model model;
const struct comedi_lrange *range;
int ai_nchans;
unsigned int ai_maxdata;
int ao_nchans;
int di_nchans;
unsigned int cnt_maxdata;
int pwm_nchans;
unsigned int pwm_maxdata;
};
static const struct vmk80xx_board vmk80xx_boardinfo[] = {
[DEVICE_VMK8055] = {
.name = "K8055 (VM110)",
.model = VMK8055_MODEL,
.range = &range_unipolar5,
.ai_nchans = 2,
.ai_maxdata = 0x00ff,
.ao_nchans = 2,
.di_nchans = 6,
.cnt_maxdata = 0xffff,
},
[DEVICE_VMK8061] = {
.name = "K8061 (VM140)",
.model = VMK8061_MODEL,
.range = &vmk8061_range,
.ai_nchans = 8,
.ai_maxdata = 0x03ff,
.ao_nchans = 8,
.di_nchans = 8,
.cnt_maxdata = 0, /* unknown, device is not writeable */
.pwm_nchans = 1,
.pwm_maxdata = 0x03ff,
},
};
struct vmk80xx_private {
struct usb_endpoint_descriptor *ep_rx;
struct usb_endpoint_descriptor *ep_tx;
struct semaphore limit_sem;
unsigned char *usb_rx_buf;
unsigned char *usb_tx_buf;
enum vmk80xx_model model;
};
static void vmk80xx_do_bulk_msg(struct comedi_device *dev)
{
struct vmk80xx_private *devpriv = dev->private;
struct usb_device *usb = comedi_to_usb_dev(dev);
__u8 tx_addr;
__u8 rx_addr;
unsigned int tx_pipe;
unsigned int rx_pipe;
size_t tx_size;
size_t rx_size;
tx_addr = devpriv->ep_tx->bEndpointAddress;
rx_addr = devpriv->ep_rx->bEndpointAddress;
tx_pipe = usb_sndbulkpipe(usb, tx_addr);
rx_pipe = usb_rcvbulkpipe(usb, rx_addr);
tx_size = usb_endpoint_maxp(devpriv->ep_tx);
rx_size = usb_endpoint_maxp(devpriv->ep_rx);
usb_bulk_msg(usb, tx_pipe, devpriv->usb_tx_buf, tx_size, NULL,
PACKET_TIMEOUT);
usb_bulk_msg(usb, rx_pipe, devpriv->usb_rx_buf, rx_size, NULL,
PACKET_TIMEOUT);
}
static int vmk80xx_read_packet(struct comedi_device *dev)
{
struct vmk80xx_private *devpriv = dev->private;
struct usb_device *usb = comedi_to_usb_dev(dev);
struct usb_endpoint_descriptor *ep;
unsigned int pipe;
if (devpriv->model == VMK8061_MODEL) {
vmk80xx_do_bulk_msg(dev);
return 0;
}
ep = devpriv->ep_rx;
pipe = usb_rcvintpipe(usb, ep->bEndpointAddress);
return usb_interrupt_msg(usb, pipe, devpriv->usb_rx_buf,
usb_endpoint_maxp(ep), NULL,
PACKET_TIMEOUT);
}
static int vmk80xx_write_packet(struct comedi_device *dev, int cmd)
{
struct vmk80xx_private *devpriv = dev->private;
struct usb_device *usb = comedi_to_usb_dev(dev);
struct usb_endpoint_descriptor *ep;
unsigned int pipe;
devpriv->usb_tx_buf[0] = cmd;
if (devpriv->model == VMK8061_MODEL) {
vmk80xx_do_bulk_msg(dev);
return 0;
}
ep = devpriv->ep_tx;
pipe = usb_sndintpipe(usb, ep->bEndpointAddress);
return usb_interrupt_msg(usb, pipe, devpriv->usb_tx_buf,
usb_endpoint_maxp(ep), NULL,
PACKET_TIMEOUT);
}
static int vmk80xx_reset_device(struct comedi_device *dev)
{
struct vmk80xx_private *devpriv = dev->private;
size_t size;
int retval;
size = usb_endpoint_maxp(devpriv->ep_tx);
memset(devpriv->usb_tx_buf, 0, size);
retval = vmk80xx_write_packet(dev, VMK8055_CMD_RST);
if (retval)
return retval;
/* set outputs to known state as we cannot read them */
return vmk80xx_write_packet(dev, VMK8055_CMD_WRT_AD);
}
static int vmk80xx_ai_insn_read(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
struct vmk80xx_private *devpriv = dev->private;
int chan;
int reg[2];
int n;
down(&devpriv->limit_sem);
chan = CR_CHAN(insn->chanspec);
switch (devpriv->model) {
case VMK8055_MODEL:
if (!chan)
reg[0] = VMK8055_AI1_REG;
else
reg[0] = VMK8055_AI2_REG;
break;
case VMK8061_MODEL:
default:
reg[0] = VMK8061_AI_REG1;
reg[1] = VMK8061_AI_REG2;
devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AI;
devpriv->usb_tx_buf[VMK8061_CH_REG] = chan;
break;
}
for (n = 0; n < insn->n; n++) {
if (vmk80xx_read_packet(dev))
break;
if (devpriv->model == VMK8055_MODEL) {
data[n] = devpriv->usb_rx_buf[reg[0]];
continue;
}
/* VMK8061_MODEL */
data[n] = devpriv->usb_rx_buf[reg[0]] + 256 *
devpriv->usb_rx_buf[reg[1]];
}
up(&devpriv->limit_sem);
return n;
}
static int vmk80xx_ao_insn_write(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
struct vmk80xx_private *devpriv = dev->private;
int chan;
int cmd;
int reg;
int n;
down(&devpriv->limit_sem);
chan = CR_CHAN(insn->chanspec);
switch (devpriv->model) {
case VMK8055_MODEL:
cmd = VMK8055_CMD_WRT_AD;
if (!chan)
reg = VMK8055_AO1_REG;
else
reg = VMK8055_AO2_REG;
break;
default: /* NOTE: avoid compiler warnings */
cmd = VMK8061_CMD_SET_AO;
reg = VMK8061_AO_REG;
devpriv->usb_tx_buf[VMK8061_CH_REG] = chan;
break;
}
for (n = 0; n < insn->n; n++) {
devpriv->usb_tx_buf[reg] = data[n];
if (vmk80xx_write_packet(dev, cmd))
break;
}
up(&devpriv->limit_sem);
return n;
}
static int vmk80xx_ao_insn_read(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
struct vmk80xx_private *devpriv = dev->private;
int chan;
int reg;
int n;
down(&devpriv->limit_sem);
chan = CR_CHAN(insn->chanspec);
reg = VMK8061_AO_REG - 1;
devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AO;
for (n = 0; n < insn->n; n++) {
if (vmk80xx_read_packet(dev))
break;
data[n] = devpriv->usb_rx_buf[reg + chan];
}
up(&devpriv->limit_sem);
return n;
}
static int vmk80xx_di_insn_bits(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
struct vmk80xx_private *devpriv = dev->private;
unsigned char *rx_buf;
int reg;
int retval;
down(&devpriv->limit_sem);
rx_buf = devpriv->usb_rx_buf;
if (devpriv->model == VMK8061_MODEL) {
reg = VMK8061_DI_REG;
devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_DI;
} else {
reg = VMK8055_DI_REG;
}
retval = vmk80xx_read_packet(dev);
if (!retval) {
if (devpriv->model == VMK8055_MODEL)
data[1] = (((rx_buf[reg] >> 4) & 0x03) |
((rx_buf[reg] << 2) & 0x04) |
((rx_buf[reg] >> 3) & 0x18));
else
data[1] = rx_buf[reg];
retval = 2;
}
up(&devpriv->limit_sem);
return retval;
}
static int vmk80xx_do_insn_bits(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
struct vmk80xx_private *devpriv = dev->private;
unsigned char *rx_buf = devpriv->usb_rx_buf;
unsigned char *tx_buf = devpriv->usb_tx_buf;
int reg, cmd;
int ret = 0;
if (devpriv->model == VMK8061_MODEL) {
reg = VMK8061_DO_REG;
cmd = VMK8061_CMD_DO;
} else { /* VMK8055_MODEL */
reg = VMK8055_DO_REG;
cmd = VMK8055_CMD_WRT_AD;
}
down(&devpriv->limit_sem);
if (comedi_dio_update_state(s, data)) {
tx_buf[reg] = s->state;
ret = vmk80xx_write_packet(dev, cmd);
if (ret)
goto out;
}
if (devpriv->model == VMK8061_MODEL) {
tx_buf[0] = VMK8061_CMD_RD_DO;
ret = vmk80xx_read_packet(dev);
if (ret)
goto out;
data[1] = rx_buf[reg];
} else {
data[1] = s->state;
}
out:
up(&devpriv->limit_sem);
return ret ? ret : insn->n;
}
static int vmk80xx_cnt_insn_read(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
struct vmk80xx_private *devpriv = dev->private;
int chan;
int reg[2];
int n;
down(&devpriv->limit_sem);
chan = CR_CHAN(insn->chanspec);
switch (devpriv->model) {
case VMK8055_MODEL:
if (!chan)
reg[0] = VMK8055_CNT1_REG;
else
reg[0] = VMK8055_CNT2_REG;
break;
case VMK8061_MODEL:
default:
reg[0] = VMK8061_CNT_REG;
reg[1] = VMK8061_CNT_REG;
devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_CNT;
break;
}
for (n = 0; n < insn->n; n++) {
if (vmk80xx_read_packet(dev))
break;
if (devpriv->model == VMK8055_MODEL)
data[n] = devpriv->usb_rx_buf[reg[0]];
else /* VMK8061_MODEL */
data[n] = devpriv->usb_rx_buf[reg[0] * (chan + 1) + 1]
+ 256 * devpriv->usb_rx_buf[reg[1] * 2 + 2];
}
up(&devpriv->limit_sem);
return n;
}
static int vmk80xx_cnt_insn_config(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
struct vmk80xx_private *devpriv = dev->private;
unsigned int chan = CR_CHAN(insn->chanspec);
int cmd;
int reg;
int ret;
down(&devpriv->limit_sem);
switch (data[0]) {
case INSN_CONFIG_RESET:
if (devpriv->model == VMK8055_MODEL) {
if (!chan) {
cmd = VMK8055_CMD_RST_CNT1;
reg = VMK8055_CNT1_REG;
} else {
cmd = VMK8055_CMD_RST_CNT2;
reg = VMK8055_CNT2_REG;
}
devpriv->usb_tx_buf[reg] = 0x00;
} else {
cmd = VMK8061_CMD_RST_CNT;
}
ret = vmk80xx_write_packet(dev, cmd);
break;
default:
ret = -EINVAL;
break;
}
up(&devpriv->limit_sem);
return ret ? ret : insn->n;
}
static int vmk80xx_cnt_insn_write(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
struct vmk80xx_private *devpriv = dev->private;
unsigned long debtime;
unsigned long val;
int chan;
int cmd;
int n;
down(&devpriv->limit_sem);
chan = CR_CHAN(insn->chanspec);
if (!chan)
cmd = VMK8055_CMD_DEB1_TIME;
else
cmd = VMK8055_CMD_DEB2_TIME;
for (n = 0; n < insn->n; n++) {
debtime = data[n];
if (debtime == 0)
debtime = 1;
/* TODO: Prevent overflows */
if (debtime > 7450)
debtime = 7450;
val = int_sqrt(debtime * 1000 / 115);
if (((val + 1) * val) < debtime * 1000 / 115)
val += 1;
devpriv->usb_tx_buf[6 + chan] = val;
if (vmk80xx_write_packet(dev, cmd))
break;
}
up(&devpriv->limit_sem);
return n;
}
static int vmk80xx_pwm_insn_read(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
struct vmk80xx_private *devpriv = dev->private;
unsigned char *tx_buf;
unsigned char *rx_buf;
int reg[2];
int n;
down(&devpriv->limit_sem);
tx_buf = devpriv->usb_tx_buf;
rx_buf = devpriv->usb_rx_buf;
reg[0] = VMK8061_PWM_REG1;
reg[1] = VMK8061_PWM_REG2;
tx_buf[0] = VMK8061_CMD_RD_PWM;
for (n = 0; n < insn->n; n++) {
if (vmk80xx_read_packet(dev))
break;
data[n] = rx_buf[reg[0]] + 4 * rx_buf[reg[1]];
}
up(&devpriv->limit_sem);
return n;
}
static int vmk80xx_pwm_insn_write(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
struct vmk80xx_private *devpriv = dev->private;
unsigned char *tx_buf;
int reg[2];
int cmd;
int n;
down(&devpriv->limit_sem);
tx_buf = devpriv->usb_tx_buf;
reg[0] = VMK8061_PWM_REG1;
reg[1] = VMK8061_PWM_REG2;
cmd = VMK8061_CMD_OUT_PWM;
/*
* The followin piece of code was translated from the inline
* assembler code in the DLL source code.
*
* asm
* mov eax, k ; k is the value (data[n])
* and al, 03h ; al are the lower 8 bits of eax
* mov lo, al ; lo is the low part (tx_buf[reg[0]])
* mov eax, k
* shr eax, 2 ; right shift eax register by 2
* mov hi, al ; hi is the high part (tx_buf[reg[1]])
* end;
*/
for (n = 0; n < insn->n; n++) {
tx_buf[reg[0]] = (unsigned char)(data[n] & 0x03);
tx_buf[reg[1]] = (unsigned char)(data[n] >> 2) & 0xff;
if (vmk80xx_write_packet(dev, cmd))
break;
}
up(&devpriv->limit_sem);
return n;
}
static int vmk80xx_find_usb_endpoints(struct comedi_device *dev)
{
struct vmk80xx_private *devpriv = dev->private;
struct usb_interface *intf = comedi_to_usb_interface(dev);
struct usb_host_interface *iface_desc = intf->cur_altsetting;
comedi: vmk80xx: fix incomplete endpoint checking While vmk80xx does have endpoint checking implemented, some things can fall through the cracks. Depending on the hardware model, URBs can have either bulk or interrupt type, and current version of vmk80xx_find_usb_endpoints() function does not take that fully into account. While this warning does not seem to be too harmful, at the very least it will crash systems with 'panic_on_warn' set on them. Fix the issue found by Syzkaller [1] by somewhat simplifying the endpoint checking process with usb_find_common_endpoints() and ensuring that only expected endpoint types are present. This patch has not been tested on real hardware. [1] Syzkaller report: usb 1-1: BOGUS urb xfer, pipe 1 != type 3 WARNING: CPU: 0 PID: 781 at drivers/usb/core/urb.c:504 usb_submit_urb+0xc4e/0x18c0 drivers/usb/core/urb.c:503 ... Call Trace: <TASK> usb_start_wait_urb+0x113/0x520 drivers/usb/core/message.c:59 vmk80xx_reset_device drivers/comedi/drivers/vmk80xx.c:227 [inline] vmk80xx_auto_attach+0xa1c/0x1a40 drivers/comedi/drivers/vmk80xx.c:818 comedi_auto_config+0x238/0x380 drivers/comedi/drivers.c:1067 usb_probe_interface+0x5cd/0xb00 drivers/usb/core/driver.c:399 ... Similar issue also found by Syzkaller: Link: https://syzkaller.appspot.com/bug?extid=5205eb2f17de3e01946e Reported-and-tested-by: syzbot+5f29dc6a889fc42bd896@syzkaller.appspotmail.com Cc: stable <stable@kernel.org> Fixes: 49253d542cc0 ("staging: comedi: vmk80xx: factor out usb endpoint detection") Reviewed-by: Ian Abbott <abbotti@mev.co.uk> Signed-off-by: Nikita Zhandarovich <n.zhandarovich@fintech.ru> Link: https://lore.kernel.org/r/20240408171633.31649-1-n.zhandarovich@fintech.ru Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2024-04-08 17:16:33 +00:00
struct usb_endpoint_descriptor *ep_rx_desc, *ep_tx_desc;
int ret;
comedi: vmk80xx: fix incomplete endpoint checking While vmk80xx does have endpoint checking implemented, some things can fall through the cracks. Depending on the hardware model, URBs can have either bulk or interrupt type, and current version of vmk80xx_find_usb_endpoints() function does not take that fully into account. While this warning does not seem to be too harmful, at the very least it will crash systems with 'panic_on_warn' set on them. Fix the issue found by Syzkaller [1] by somewhat simplifying the endpoint checking process with usb_find_common_endpoints() and ensuring that only expected endpoint types are present. This patch has not been tested on real hardware. [1] Syzkaller report: usb 1-1: BOGUS urb xfer, pipe 1 != type 3 WARNING: CPU: 0 PID: 781 at drivers/usb/core/urb.c:504 usb_submit_urb+0xc4e/0x18c0 drivers/usb/core/urb.c:503 ... Call Trace: <TASK> usb_start_wait_urb+0x113/0x520 drivers/usb/core/message.c:59 vmk80xx_reset_device drivers/comedi/drivers/vmk80xx.c:227 [inline] vmk80xx_auto_attach+0xa1c/0x1a40 drivers/comedi/drivers/vmk80xx.c:818 comedi_auto_config+0x238/0x380 drivers/comedi/drivers.c:1067 usb_probe_interface+0x5cd/0xb00 drivers/usb/core/driver.c:399 ... Similar issue also found by Syzkaller: Link: https://syzkaller.appspot.com/bug?extid=5205eb2f17de3e01946e Reported-and-tested-by: syzbot+5f29dc6a889fc42bd896@syzkaller.appspotmail.com Cc: stable <stable@kernel.org> Fixes: 49253d542cc0 ("staging: comedi: vmk80xx: factor out usb endpoint detection") Reviewed-by: Ian Abbott <abbotti@mev.co.uk> Signed-off-by: Nikita Zhandarovich <n.zhandarovich@fintech.ru> Link: https://lore.kernel.org/r/20240408171633.31649-1-n.zhandarovich@fintech.ru Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2024-04-08 17:16:33 +00:00
if (devpriv->model == VMK8061_MODEL)
ret = usb_find_common_endpoints(iface_desc, &ep_rx_desc,
&ep_tx_desc, NULL, NULL);
else
ret = usb_find_common_endpoints(iface_desc, NULL, NULL,
&ep_rx_desc, &ep_tx_desc);
comedi: vmk80xx: fix incomplete endpoint checking While vmk80xx does have endpoint checking implemented, some things can fall through the cracks. Depending on the hardware model, URBs can have either bulk or interrupt type, and current version of vmk80xx_find_usb_endpoints() function does not take that fully into account. While this warning does not seem to be too harmful, at the very least it will crash systems with 'panic_on_warn' set on them. Fix the issue found by Syzkaller [1] by somewhat simplifying the endpoint checking process with usb_find_common_endpoints() and ensuring that only expected endpoint types are present. This patch has not been tested on real hardware. [1] Syzkaller report: usb 1-1: BOGUS urb xfer, pipe 1 != type 3 WARNING: CPU: 0 PID: 781 at drivers/usb/core/urb.c:504 usb_submit_urb+0xc4e/0x18c0 drivers/usb/core/urb.c:503 ... Call Trace: <TASK> usb_start_wait_urb+0x113/0x520 drivers/usb/core/message.c:59 vmk80xx_reset_device drivers/comedi/drivers/vmk80xx.c:227 [inline] vmk80xx_auto_attach+0xa1c/0x1a40 drivers/comedi/drivers/vmk80xx.c:818 comedi_auto_config+0x238/0x380 drivers/comedi/drivers.c:1067 usb_probe_interface+0x5cd/0xb00 drivers/usb/core/driver.c:399 ... Similar issue also found by Syzkaller: Link: https://syzkaller.appspot.com/bug?extid=5205eb2f17de3e01946e Reported-and-tested-by: syzbot+5f29dc6a889fc42bd896@syzkaller.appspotmail.com Cc: stable <stable@kernel.org> Fixes: 49253d542cc0 ("staging: comedi: vmk80xx: factor out usb endpoint detection") Reviewed-by: Ian Abbott <abbotti@mev.co.uk> Signed-off-by: Nikita Zhandarovich <n.zhandarovich@fintech.ru> Link: https://lore.kernel.org/r/20240408171633.31649-1-n.zhandarovich@fintech.ru Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2024-04-08 17:16:33 +00:00
if (ret)
return -ENODEV;
comedi: vmk80xx: fix incomplete endpoint checking While vmk80xx does have endpoint checking implemented, some things can fall through the cracks. Depending on the hardware model, URBs can have either bulk or interrupt type, and current version of vmk80xx_find_usb_endpoints() function does not take that fully into account. While this warning does not seem to be too harmful, at the very least it will crash systems with 'panic_on_warn' set on them. Fix the issue found by Syzkaller [1] by somewhat simplifying the endpoint checking process with usb_find_common_endpoints() and ensuring that only expected endpoint types are present. This patch has not been tested on real hardware. [1] Syzkaller report: usb 1-1: BOGUS urb xfer, pipe 1 != type 3 WARNING: CPU: 0 PID: 781 at drivers/usb/core/urb.c:504 usb_submit_urb+0xc4e/0x18c0 drivers/usb/core/urb.c:503 ... Call Trace: <TASK> usb_start_wait_urb+0x113/0x520 drivers/usb/core/message.c:59 vmk80xx_reset_device drivers/comedi/drivers/vmk80xx.c:227 [inline] vmk80xx_auto_attach+0xa1c/0x1a40 drivers/comedi/drivers/vmk80xx.c:818 comedi_auto_config+0x238/0x380 drivers/comedi/drivers.c:1067 usb_probe_interface+0x5cd/0xb00 drivers/usb/core/driver.c:399 ... Similar issue also found by Syzkaller: Link: https://syzkaller.appspot.com/bug?extid=5205eb2f17de3e01946e Reported-and-tested-by: syzbot+5f29dc6a889fc42bd896@syzkaller.appspotmail.com Cc: stable <stable@kernel.org> Fixes: 49253d542cc0 ("staging: comedi: vmk80xx: factor out usb endpoint detection") Reviewed-by: Ian Abbott <abbotti@mev.co.uk> Signed-off-by: Nikita Zhandarovich <n.zhandarovich@fintech.ru> Link: https://lore.kernel.org/r/20240408171633.31649-1-n.zhandarovich@fintech.ru Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2024-04-08 17:16:33 +00:00
devpriv->ep_rx = ep_rx_desc;
devpriv->ep_tx = ep_tx_desc;
if (!usb_endpoint_maxp(devpriv->ep_rx) || !usb_endpoint_maxp(devpriv->ep_tx))
return -EINVAL;
return 0;
}
static int vmk80xx_alloc_usb_buffers(struct comedi_device *dev)
{
struct vmk80xx_private *devpriv = dev->private;
size_t size;
size = max(usb_endpoint_maxp(devpriv->ep_rx), MIN_BUF_SIZE);
devpriv->usb_rx_buf = kzalloc(size, GFP_KERNEL);
if (!devpriv->usb_rx_buf)
return -ENOMEM;
size = max(usb_endpoint_maxp(devpriv->ep_tx), MIN_BUF_SIZE);
devpriv->usb_tx_buf = kzalloc(size, GFP_KERNEL);
if (!devpriv->usb_tx_buf)
return -ENOMEM;
return 0;
}
static int vmk80xx_init_subdevices(struct comedi_device *dev)
{
const struct vmk80xx_board *board = dev->board_ptr;
struct vmk80xx_private *devpriv = dev->private;
struct comedi_subdevice *s;
int n_subd;
int ret;
down(&devpriv->limit_sem);
if (devpriv->model == VMK8055_MODEL)
n_subd = 5;
else
n_subd = 6;
ret = comedi_alloc_subdevices(dev, n_subd);
if (ret) {
up(&devpriv->limit_sem);
return ret;
}
/* Analog input subdevice */
s = &dev->subdevices[0];
s->type = COMEDI_SUBD_AI;
s->subdev_flags = SDF_READABLE | SDF_GROUND;
s->n_chan = board->ai_nchans;
s->maxdata = board->ai_maxdata;
s->range_table = board->range;
s->insn_read = vmk80xx_ai_insn_read;
/* Analog output subdevice */
s = &dev->subdevices[1];
s->type = COMEDI_SUBD_AO;
s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
s->n_chan = board->ao_nchans;
s->maxdata = 0x00ff;
s->range_table = board->range;
s->insn_write = vmk80xx_ao_insn_write;
if (devpriv->model == VMK8061_MODEL) {
s->subdev_flags |= SDF_READABLE;
s->insn_read = vmk80xx_ao_insn_read;
}
/* Digital input subdevice */
s = &dev->subdevices[2];
s->type = COMEDI_SUBD_DI;
s->subdev_flags = SDF_READABLE;
s->n_chan = board->di_nchans;
s->maxdata = 1;
s->range_table = &range_digital;
s->insn_bits = vmk80xx_di_insn_bits;
/* Digital output subdevice */
s = &dev->subdevices[3];
s->type = COMEDI_SUBD_DO;
s->subdev_flags = SDF_WRITABLE;
s->n_chan = 8;
s->maxdata = 1;
s->range_table = &range_digital;
s->insn_bits = vmk80xx_do_insn_bits;
/* Counter subdevice */
s = &dev->subdevices[4];
s->type = COMEDI_SUBD_COUNTER;
s->subdev_flags = SDF_READABLE;
s->n_chan = 2;
s->maxdata = board->cnt_maxdata;
s->insn_read = vmk80xx_cnt_insn_read;
s->insn_config = vmk80xx_cnt_insn_config;
if (devpriv->model == VMK8055_MODEL) {
s->subdev_flags |= SDF_WRITABLE;
s->insn_write = vmk80xx_cnt_insn_write;
}
/* PWM subdevice */
if (devpriv->model == VMK8061_MODEL) {
s = &dev->subdevices[5];
s->type = COMEDI_SUBD_PWM;
s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
s->n_chan = board->pwm_nchans;
s->maxdata = board->pwm_maxdata;
s->insn_read = vmk80xx_pwm_insn_read;
s->insn_write = vmk80xx_pwm_insn_write;
}
up(&devpriv->limit_sem);
return 0;
}
static int vmk80xx_auto_attach(struct comedi_device *dev,
unsigned long context)
{
struct usb_interface *intf = comedi_to_usb_interface(dev);
const struct vmk80xx_board *board = NULL;
struct vmk80xx_private *devpriv;
int ret;
if (context < ARRAY_SIZE(vmk80xx_boardinfo))
board = &vmk80xx_boardinfo[context];
if (!board)
return -ENODEV;
dev->board_ptr = board;
dev->board_name = board->name;
devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
if (!devpriv)
return -ENOMEM;
devpriv->model = board->model;
staging: comedi: vmk80xx: Fix use of uninitialized semaphore If `vmk80xx_auto_attach()` returns an error, the core comedi module code will call `vmk80xx_detach()` to clean up. If `vmk80xx_auto_attach()` successfully allocated the comedi device private data, `vmk80xx_detach()` assumes that a `struct semaphore limit_sem` contained in the private data has been initialized and uses it. Unfortunately, there are a couple of places where `vmk80xx_auto_attach()` can return an error after allocating the device private data but before initializing the semaphore, so this assumption is invalid. Fix it by initializing the semaphore just after allocating the private data in `vmk80xx_auto_attach()` before any other errors can be returned. I believe this was the cause of the following syzbot crash report <https://syzkaller.appspot.com/bug?extid=54c2f58f15fe6876b6ad>: usb 1-1: config 0 has no interface number 0 usb 1-1: New USB device found, idVendor=10cf, idProduct=8068, bcdDevice=e6.8d usb 1-1: New USB device strings: Mfr=0, Product=0, SerialNumber=0 usb 1-1: config 0 descriptor?? vmk80xx 1-1:0.117: driver 'vmk80xx' failed to auto-configure device. INFO: trying to register non-static key. the code is fine but needs lockdep annotation. turning off the locking correctness validator. CPU: 0 PID: 12 Comm: kworker/0:1 Not tainted 5.1.0-rc4-319354-g9a33b36 #3 Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011 Workqueue: usb_hub_wq hub_event Call Trace: __dump_stack lib/dump_stack.c:77 [inline] dump_stack+0xe8/0x16e lib/dump_stack.c:113 assign_lock_key kernel/locking/lockdep.c:786 [inline] register_lock_class+0x11b8/0x1250 kernel/locking/lockdep.c:1095 __lock_acquire+0xfb/0x37c0 kernel/locking/lockdep.c:3582 lock_acquire+0x10d/0x2f0 kernel/locking/lockdep.c:4211 __raw_spin_lock_irqsave include/linux/spinlock_api_smp.h:110 [inline] _raw_spin_lock_irqsave+0x44/0x60 kernel/locking/spinlock.c:152 down+0x12/0x80 kernel/locking/semaphore.c:58 vmk80xx_detach+0x59/0x100 drivers/staging/comedi/drivers/vmk80xx.c:829 comedi_device_detach+0xed/0x800 drivers/staging/comedi/drivers.c:204 comedi_device_cleanup.part.0+0x68/0x140 drivers/staging/comedi/comedi_fops.c:156 comedi_device_cleanup drivers/staging/comedi/comedi_fops.c:187 [inline] comedi_free_board_dev.part.0+0x16/0x90 drivers/staging/comedi/comedi_fops.c:190 comedi_free_board_dev drivers/staging/comedi/comedi_fops.c:189 [inline] comedi_release_hardware_device+0x111/0x140 drivers/staging/comedi/comedi_fops.c:2880 comedi_auto_config.cold+0x124/0x1b0 drivers/staging/comedi/drivers.c:1068 usb_probe_interface+0x31d/0x820 drivers/usb/core/driver.c:361 really_probe+0x2da/0xb10 drivers/base/dd.c:509 driver_probe_device+0x21d/0x350 drivers/base/dd.c:671 __device_attach_driver+0x1d8/0x290 drivers/base/dd.c:778 bus_for_each_drv+0x163/0x1e0 drivers/base/bus.c:454 __device_attach+0x223/0x3a0 drivers/base/dd.c:844 bus_probe_device+0x1f1/0x2a0 drivers/base/bus.c:514 device_add+0xad2/0x16e0 drivers/base/core.c:2106 usb_set_configuration+0xdf7/0x1740 drivers/usb/core/message.c:2021 generic_probe+0xa2/0xda drivers/usb/core/generic.c:210 usb_probe_device+0xc0/0x150 drivers/usb/core/driver.c:266 really_probe+0x2da/0xb10 drivers/base/dd.c:509 driver_probe_device+0x21d/0x350 drivers/base/dd.c:671 __device_attach_driver+0x1d8/0x290 drivers/base/dd.c:778 bus_for_each_drv+0x163/0x1e0 drivers/base/bus.c:454 __device_attach+0x223/0x3a0 drivers/base/dd.c:844 bus_probe_device+0x1f1/0x2a0 drivers/base/bus.c:514 device_add+0xad2/0x16e0 drivers/base/core.c:2106 usb_new_device.cold+0x537/0xccf drivers/usb/core/hub.c:2534 hub_port_connect drivers/usb/core/hub.c:5089 [inline] hub_port_connect_change drivers/usb/core/hub.c:5204 [inline] port_event drivers/usb/core/hub.c:5350 [inline] hub_event+0x138e/0x3b00 drivers/usb/core/hub.c:5432 process_one_work+0x90f/0x1580 kernel/workqueue.c:2269 worker_thread+0x9b/0xe20 kernel/workqueue.c:2415 kthread+0x313/0x420 kernel/kthread.c:253 ret_from_fork+0x3a/0x50 arch/x86/entry/entry_64.S:352 Reported-by: syzbot+54c2f58f15fe6876b6ad@syzkaller.appspotmail.com Signed-off-by: Ian Abbott <abbotti@mev.co.uk> Cc: stable <stable@vger.kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2019-04-15 11:10:14 +00:00
sema_init(&devpriv->limit_sem, 8);
ret = vmk80xx_find_usb_endpoints(dev);
if (ret)
return ret;
ret = vmk80xx_alloc_usb_buffers(dev);
if (ret)
return ret;
usb_set_intfdata(intf, devpriv);
if (devpriv->model == VMK8055_MODEL)
vmk80xx_reset_device(dev);
return vmk80xx_init_subdevices(dev);
}
static void vmk80xx_detach(struct comedi_device *dev)
{
struct usb_interface *intf = comedi_to_usb_interface(dev);
struct vmk80xx_private *devpriv = dev->private;
if (!devpriv)
return;
down(&devpriv->limit_sem);
usb_set_intfdata(intf, NULL);
kfree(devpriv->usb_rx_buf);
kfree(devpriv->usb_tx_buf);
up(&devpriv->limit_sem);
}
static struct comedi_driver vmk80xx_driver = {
.module = THIS_MODULE,
.driver_name = "vmk80xx",
.auto_attach = vmk80xx_auto_attach,
.detach = vmk80xx_detach,
};
static int vmk80xx_usb_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
return comedi_usb_auto_config(intf, &vmk80xx_driver, id->driver_info);
}
static const struct usb_device_id vmk80xx_usb_id_table[] = {
{ USB_DEVICE(0x10cf, 0x5500), .driver_info = DEVICE_VMK8055 },
{ USB_DEVICE(0x10cf, 0x5501), .driver_info = DEVICE_VMK8055 },
{ USB_DEVICE(0x10cf, 0x5502), .driver_info = DEVICE_VMK8055 },
{ USB_DEVICE(0x10cf, 0x5503), .driver_info = DEVICE_VMK8055 },
{ USB_DEVICE(0x10cf, 0x8061), .driver_info = DEVICE_VMK8061 },
{ USB_DEVICE(0x10cf, 0x8062), .driver_info = DEVICE_VMK8061 },
{ USB_DEVICE(0x10cf, 0x8063), .driver_info = DEVICE_VMK8061 },
{ USB_DEVICE(0x10cf, 0x8064), .driver_info = DEVICE_VMK8061 },
{ USB_DEVICE(0x10cf, 0x8065), .driver_info = DEVICE_VMK8061 },
{ USB_DEVICE(0x10cf, 0x8066), .driver_info = DEVICE_VMK8061 },
{ USB_DEVICE(0x10cf, 0x8067), .driver_info = DEVICE_VMK8061 },
{ USB_DEVICE(0x10cf, 0x8068), .driver_info = DEVICE_VMK8061 },
{ }
};
MODULE_DEVICE_TABLE(usb, vmk80xx_usb_id_table);
static struct usb_driver vmk80xx_usb_driver = {
.name = "vmk80xx",
.id_table = vmk80xx_usb_id_table,
.probe = vmk80xx_usb_probe,
.disconnect = comedi_usb_auto_unconfig,
};
module_comedi_usb_driver(vmk80xx_driver, vmk80xx_usb_driver);
MODULE_AUTHOR("Manuel Gebele <forensixs@gmx.de>");
MODULE_DESCRIPTION("Velleman USB Board Low-Level Driver");
MODULE_LICENSE("GPL");