Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

staging: comedi: comedi_bond: handle base channel for insn_bits

If a DIO subdevice has more than 32 channels, its 'insn_bits' handler is
supposed to take account of the base channel from
`CR_CHAN(insn->chanspec)`. (The comedi core will adjust the base
channel to 0 and shift the mask and data to compensate if the subdevice
has less than or equal to 32 channels.) The "comedi_bond" driver
currently ignores the base channel and assumes it is 0.

Replace `comedi_dio_bitfield()` in the "kcomedilib" module with
`comedi_dio_bitfield2()` that takes account of the base channel, and
rewrite the "comedi_bond" driver's 'insn_bits' handler
(`bonding_dio_insn_bits()`) to take account of the base channel and use
the new function.

No other modules use `comedi_dio_bitfield()` so it is safe to replace it
with `comedi_dio_bitfield2()`. The name follows that of the equivalent
function in the user-space comedilib. If the base channel is non-zero
and the subdevice has less than or equal to 32 channels it needs to
adjust things in the same way as the comedi core (same as `parse_insn()`
in "comedi_fops.c") due to most drivers ignoring the base channel.

Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Ian Abbott and committed by
Greg Kroah-Hartman
0f3ce1a6 16d2d3cb

+83 -46
+3 -2
drivers/staging/comedi/comedilib.h
··· 25 25 unsigned int chan, unsigned int *io); 26 26 int comedi_dio_config(struct comedi_device *dev, unsigned int subdev, 27 27 unsigned int chan, unsigned int io); 28 - int comedi_dio_bitfield(struct comedi_device *dev, unsigned int subdev, 29 - unsigned int mask, unsigned int *bits); 28 + int comedi_dio_bitfield2(struct comedi_device *dev, unsigned int subdev, 29 + unsigned int mask, unsigned int *bits, 30 + unsigned int base_channel); 30 31 int comedi_find_subdevice_by_type(struct comedi_device *dev, int type, 31 32 unsigned int subd); 32 33 int comedi_get_n_channels(struct comedi_device *dev, unsigned int subdevice);
+49 -38
drivers/staging/comedi/drivers/comedi_bond.c
··· 73 73 struct comedi_insn *insn, unsigned int *data) 74 74 { 75 75 struct comedi_bond_private *devpriv = dev->private; 76 - #define LSAMPL_BITS (sizeof(unsigned int)*8) 77 - unsigned nchans = LSAMPL_BITS, num_done = 0, i; 76 + unsigned int n_left, n_done, base_chan; 77 + unsigned int write_mask, data_bits; 78 + struct bonded_device **devs; 78 79 79 - if (devpriv->nchans < nchans) 80 - nchans = devpriv->nchans; 80 + write_mask = data[0]; 81 + data_bits = data[1]; 82 + base_chan = CR_CHAN(insn->chanspec); 83 + /* do a maximum of 32 channels, starting from base_chan. */ 84 + n_left = devpriv->nchans - base_chan; 85 + if (n_left > 32) 86 + n_left = 32; 81 87 82 - /* 83 - * The insn data is a mask in data[0] and the new data 84 - * in data[1], each channel cooresponding to a bit. 85 - */ 86 - for (i = 0; num_done < nchans && i < devpriv->ndevs; ++i) { 87 - struct bonded_device *bdev = devpriv->devs[i]; 88 - /* 89 - * Grab the channel mask and data of only the bits corresponding 90 - * to this subdevice.. need to shift them to zero position of 91 - * course. 92 - */ 93 - /* Bits corresponding to this subdev. */ 94 - unsigned int subdev_mask = ((1 << bdev->nchans) - 1); 95 - unsigned int write_mask, data_bits; 88 + n_done = 0; 89 + devs = devpriv->devs; 90 + do { 91 + struct bonded_device *bdev = *devs++; 96 92 97 - /* Argh, we have >= LSAMPL_BITS chans.. take all bits */ 98 - if (bdev->nchans >= LSAMPL_BITS) 99 - subdev_mask = (unsigned int)(-1); 93 + if (base_chan < bdev->nchans) { 94 + /* base channel falls within bonded device */ 95 + unsigned int b_chans, b_mask, b_write_mask, b_data_bits; 96 + int ret; 100 97 101 - write_mask = (data[0] >> num_done) & subdev_mask; 102 - data_bits = (data[1] >> num_done) & subdev_mask; 103 - 104 - /* Read/Write the new digital lines */ 105 - if (comedi_dio_bitfield(bdev->dev, bdev->subdev, write_mask, 106 - &data_bits) != 2) 107 - return -EINVAL; 108 - 109 - /* Make room for the new bits in data[1], the return value */ 110 - data[1] &= ~(subdev_mask << num_done); 111 - /* Put the bits in the return value */ 112 - data[1] |= (data_bits & subdev_mask) << num_done; 113 - /* Save the new bits to the saved state.. */ 114 - s->state = data[1]; 115 - 116 - num_done += bdev->nchans; 117 - } 98 + /* 99 + * Get num channels to do for bonded device and set 100 + * up mask and data bits for bonded device. 101 + */ 102 + b_chans = bdev->nchans - base_chan; 103 + if (b_chans > n_left) 104 + b_chans = n_left; 105 + b_mask = (1U << b_chans) - 1; 106 + b_write_mask = (write_mask >> n_done) & b_mask; 107 + b_data_bits = (data_bits >> n_done) & b_mask; 108 + /* Read/Write the new digital lines. */ 109 + ret = comedi_dio_bitfield2(bdev->dev, bdev->subdev, 110 + b_write_mask, &b_data_bits, 111 + base_chan); 112 + if (ret < 0) 113 + return ret; 114 + /* Place read bits into data[1]. */ 115 + data[1] &= ~(b_mask << n_done); 116 + data[1] |= (b_data_bits & b_mask) << n_done; 117 + /* 118 + * Set up for following bonded device (if still have 119 + * channels to read/write). 120 + */ 121 + base_chan = 0; 122 + n_done += b_chans; 123 + n_left -= b_chans; 124 + } else { 125 + /* Skip bonded devices before base channel. */ 126 + base_chan -= bdev->nchans; 127 + } 128 + } while (n_left); 118 129 119 130 return insn->n; 120 131 }
+31 -6
drivers/staging/comedi/kcomedilib/kcomedilib_main.c
··· 159 159 } 160 160 EXPORT_SYMBOL_GPL(comedi_dio_config); 161 161 162 - int comedi_dio_bitfield(struct comedi_device *dev, unsigned int subdev, 163 - unsigned int mask, unsigned int *bits) 162 + int comedi_dio_bitfield2(struct comedi_device *dev, unsigned int subdev, 163 + unsigned int mask, unsigned int *bits, 164 + unsigned int base_channel) 164 165 { 165 166 struct comedi_insn insn; 166 167 unsigned int data[2]; 168 + unsigned int n_chan; 169 + unsigned int shift; 167 170 int ret; 171 + 172 + if (subdev >= dev->n_subdevices) 173 + return -EINVAL; 174 + 175 + base_channel = CR_CHAN(base_channel); 176 + n_chan = comedi_get_n_channels(dev, subdev); 177 + if (base_channel >= n_chan) 178 + return -EINVAL; 168 179 169 180 memset(&insn, 0, sizeof(insn)); 170 181 insn.insn = INSN_BITS; 182 + insn.chanspec = base_channel; 171 183 insn.n = 2; 172 184 insn.subdev = subdev; 173 185 174 186 data[0] = mask; 175 187 data[1] = *bits; 176 188 189 + /* 190 + * Most drivers ignore the base channel in insn->chanspec. 191 + * Fix this here if the subdevice has <= 32 channels. 192 + */ 193 + if (n_chan <= 32) { 194 + shift = base_channel; 195 + if (shift) { 196 + insn.chanspec = 0; 197 + data[0] <<= shift; 198 + data[1] <<= shift; 199 + } 200 + } else { 201 + shift = 0; 202 + } 203 + 177 204 ret = comedi_do_insn(dev, &insn, data); 178 - 179 - *bits = data[1]; 180 - 205 + *bits = data[1] >> shift; 181 206 return ret; 182 207 } 183 - EXPORT_SYMBOL_GPL(comedi_dio_bitfield); 208 + EXPORT_SYMBOL_GPL(comedi_dio_bitfield2); 184 209 185 210 int comedi_find_subdevice_by_type(struct comedi_device *dev, int type, 186 211 unsigned int subd)