Re: [PATCH v7 6/6] [media] cxusb: add analog mode support for Medion MD95700
From: Hans Verkuil
Date: Tue Jul 17 2018 - 08:56:15 EST
On 10/07/18 23:48, Maciej S. Szmigiero wrote:
> Hi Hans,
>
> On 04.07.2018 11:33, Hans Verkuil wrote:
>> Hi Maciej,
>>
>> On 02/07/18 23:23, Maciej S. Szmigiero wrote:
> (..)
>>> +static int cxusb_medion_v_queue_setup(struct vb2_queue *q,
>>> + unsigned int *num_buffers,
>>> + unsigned int *num_planes,
>>> + unsigned int sizes[],
>>> + struct device *alloc_devs[])
>>> +{
>>> + struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
>>> + struct cxusb_medion_dev *cxdev = dvbdev->priv;
>>> + unsigned int size = cxdev->raw_mode ?
>>> + CXUSB_VIDEO_MAX_FRAME_SIZE :
>>> + cxdev->width * cxdev->height * 2;
>>> +
>>> + if (*num_planes > 0) {
>>> + if (*num_planes != 1)
>>> + return -EINVAL;
>>> +
>>> + if (sizes[0] < size)
>>> + return -EINVAL;
>>> + } else {
>>> + *num_planes = 1;
>>> + sizes[0] = size;
>>> + }
>>> +
>>> + if (q->num_buffers + *num_buffers < 6)
>>> + *num_buffers = 6 - q->num_buffers;
>>
>> Huh? These two lines should be removed. I'm not sure what the purpose is.
>
> This is to request that the vb2 framework keeps at least 6 buffers in the
> queue.
>
> There is a similar code in usb/usbtv/usbtv-video.c:
>> if (vq->num_buffers + *nbuffers < 2)
>> *nbuffers = 2 - vq->num_buffers;
>
> And in usb/hackrf/hackrf.c and usb/airspy/airspy.c:
>> /* Need at least 8 buffers */
>> if (vq->num_buffers + *nbuffers < 8)
>> *nbuffers = 8 - vq->num_buffers;
>
> In addition to this, many drivers (like stk1160, s2255, pwc, msi2500,
> go7007, ...) always enforce some minimum *num_buffers count, regardless
> of the number of buffers currently in the queue.
Yeah, that's old code. These days you set the min_buffers_needed field of
struct vb2_queue and vb2 will take care of this itself.
Please set that field instead.
>
>>> +static void cxusb_auxbuf_init(struct cxusb_medion_auxbuf *auxbuf,
>>> + u8 *buf, unsigned int len)
>>> +{
>>> + auxbuf->buf = buf;
>>> + auxbuf->len = len;
>>> + auxbuf->paylen = 0;
>>> +}
>>> +
>>> +static void cxusb_auxbuf_head_trim(struct dvb_usb_device *dvbdev,
>>> + struct cxusb_medion_auxbuf *auxbuf,
>>> + unsigned int pos)
>>> +{
>>> + if (pos == 0)
>>> + return;
>>> +
>>> + if (WARN_ON(pos > auxbuf->paylen))
>>> + return;
>>> +
>>> + cxusb_vprintk(dvbdev, AUXB,
>>> + "trimming auxbuf len by %u to %u\n",
>>> + pos, auxbuf->paylen - pos);
>>> +
>>> + memmove(auxbuf->buf, auxbuf->buf + pos, auxbuf->paylen - pos);
>>> + auxbuf->paylen -= pos;
>>> +}
>>> +
>>> +static unsigned int cxusb_auxbuf_paylen(struct cxusb_medion_auxbuf *auxbuf)
>>> +{
>>> + return auxbuf->paylen;
>>> +}
>>> +
>>> +static bool cxusb_auxbuf_make_space(struct dvb_usb_device *dvbdev,
>>> + struct cxusb_medion_auxbuf *auxbuf,
>>> + unsigned int howmuch)
>>> +{
>>> + unsigned int freespace;
>>> +
>>> + if (WARN_ON(howmuch >= auxbuf->len))
>>> + howmuch = auxbuf->len - 1;
>>> +
>>> + freespace = auxbuf->len - cxusb_auxbuf_paylen(auxbuf);
>>> +
>>> + cxusb_vprintk(dvbdev, AUXB, "freespace is %u\n", freespace);
>>> +
>>> + if (freespace >= howmuch)
>>> + return true;
>>> +
>>> + howmuch -= freespace;
>>> +
>>> + cxusb_vprintk(dvbdev, AUXB, "will overwrite %u bytes of buffer\n",
>>> + howmuch);
>>> +
>>> + cxusb_auxbuf_head_trim(dvbdev, auxbuf, howmuch);
>>> +
>>> + return false;
>>> +}
>>> +
>>> +/* returns false if some data was overwritten */
>>> +static bool cxusb_auxbuf_append_urb(struct dvb_usb_device *dvbdev,
>>> + struct cxusb_medion_auxbuf *auxbuf,
>>> + struct urb *urb)
>>> +{
>>> + unsigned long len = 0;
>>> + int i;
>>> + bool ret;
>>> +
>>> + for (i = 0; i < urb->number_of_packets; i++)
>>> + len += urb->iso_frame_desc[i].actual_length;
>>> +
>>> + ret = cxusb_auxbuf_make_space(dvbdev, auxbuf, len);
>>> +
>>> + for (i = 0; i < urb->number_of_packets; i++) {
>>> + unsigned int to_copy;
>>> +
>>> + to_copy = urb->iso_frame_desc[i].actual_length;
>>> +
>>> + memcpy(auxbuf->buf + auxbuf->paylen, urb->transfer_buffer +
>>> + urb->iso_frame_desc[i].offset, to_copy);
>>> +
>>> + auxbuf->paylen += to_copy;
>>> + }
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static bool cxusb_auxbuf_copy(struct cxusb_medion_auxbuf *auxbuf,
>>> + unsigned int pos, unsigned char *dest,
>>> + unsigned int len)
>>> +{
>>> + if (pos + len > auxbuf->paylen)
>>> + return false;
>>> +
>>> + memcpy(dest, auxbuf->buf + pos, len);
>>> +
>>> + return true;
>>> +}
>>> +
>>> +static unsigned int cxusb_auxbuf_advance(struct cxusb_medion_auxbuf *auxbuf,
>>> + unsigned int pos,
>>> + unsigned int increment)
>>> +{
>>> + return pos + increment;
>>> +}
>>> +
>>> +static unsigned int cxusb_auxbuf_begin(struct cxusb_medion_auxbuf *auxbuf)
>>> +{
>>> + return 0;
>>> +}
>>> +
>>> +static bool cxusb_auxbuf_isend(struct cxusb_medion_auxbuf *auxbuf,
>>> + unsigned int pos)
>>> +{
>>> + return pos >= auxbuf->paylen;
>>> +}
>>
>> These three functions seem pointless to me.
>
> I will remove cxusb_auxbuf_begin().
>
> cxusb_auxbuf_advance() is called 6 times in the code while
> cxusb_auxbuf_isend() keeps the buffer implementation opaque for users so
> I think they make the code a bit nicer.
Why would you want to keep these one-liners opaque? It doesn't help me
understand the code. Please don't do this.
>
>>> +static void cxusb_medion_v_process_urb_raw_mode(struct cxusb_medion_dev *cxdev,
>>> + struct urb *urb)
>>> +{
>>> + struct dvb_usb_device *dvbdev = cxdev->dvbdev;
>>> + u8 *buf;
>>> + struct cxusb_medion_vbuffer *vbuf;
>>> + int i;
>>> + unsigned long len = 0;
>>> +
>>> + if (list_empty(&cxdev->buflist)) {
>>> + dev_warn(&dvbdev->udev->dev, "no free buffers\n");
>>> + return;
>>> + }
>>> +
>>> + vbuf = list_first_entry(&cxdev->buflist, struct cxusb_medion_vbuffer,
>>> + list);
>>> + list_del(&vbuf->list);
>>> +
>>> + vbuf->vb2.timestamp = ktime_get_ns();
>>> +
>>> + buf = vb2_plane_vaddr(&vbuf->vb2, 0);
>>> +
>>> + for (i = 0; i < urb->number_of_packets; i++) {
>>> + memcpy(buf, urb->transfer_buffer +
>>> + urb->iso_frame_desc[i].offset,
>>> + urb->iso_frame_desc[i].actual_length);
>>> +
>>> + buf += urb->iso_frame_desc[i].actual_length;
>>> + len += urb->iso_frame_desc[i].actual_length;
>>> + }
>>> +
>>> + vb2_set_plane_payload(&vbuf->vb2, 0, len);
>>> +
>>> + vb2_buffer_done(&vbuf->vb2, VB2_BUF_STATE_DONE);
>>
>> The frame sequence counter in vb2_v4l2_buffer does not seem to be incremented.
>>
>> Did you test this driver with v4l2-compliance? It will detect such errors.
>
> Hmm, I did test this driver with v4l2-compliance last year and don't
> remember seeing any error about the frame sequence counter.
>
> When I'll be doing a respin I will check this again and see whether
> v4l2-compliance reports anything (it seems it should).
>
>>> +static int cxusb_medion_v_start_streaming(struct vb2_queue *q,
>>> + unsigned int count)
>>> +{
>>> + struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
>>> + struct cxusb_medion_dev *cxdev = dvbdev->priv;
>>> + u8 streamon_params[2] = { 0x03, 0x00 };
>>> + int npackets, i;
>>> + int ret;
>>> +
>>> + cxusb_vprintk(dvbdev, OPS, "should start streaming\n");
>>> +
>>> + /* already streaming */
>>> + if (cxdev->streaming)
>>> + return 0;
>>
>> Unnecessary check, this can't happen.
>>
>>> +
>>> + if (cxusb_medion_stream_busy(cxdev)) {
>>> + ret = -EBUSY;
>>> + goto ret_retbufs;
>>> + }
>>> +
>>> + ret = v4l2_subdev_call(cxdev->cx25840, video, s_stream, 1);
>>> + if (ret != 0) {
>>> + dev_err(&dvbdev->udev->dev,
>>> + "unable to start stream (%d)\n", ret);
>>> + goto ret_retbufs;
>>> + }
>>> +
>>> + ret = cxusb_ctrl_msg(dvbdev, CMD_STREAMING_ON, streamon_params, 2,
>>> + NULL, 0);
>>> + if (ret != 0) {
>>> + dev_err(&dvbdev->udev->dev,
>>> + "unable to start streaming (%d)\n", ret);
>>> + goto ret_unstream_cx;
>>> + }
>>> +
>>> + if (cxdev->raw_mode)
>>> + npackets = CXUSB_VIDEO_MAX_FRAME_PKTS;
>>> + else {
>>> + u8 *buf;
>>> + unsigned int urblen, auxbuflen;
>>> +
>>> + /* has to be less than full frame size */
>>> + urblen = (cxdev->width * 2 + 4 + 4) * cxdev->height;
>>> + npackets = urblen / CXUSB_VIDEO_PKT_SIZE;
>>> + urblen = npackets * CXUSB_VIDEO_PKT_SIZE;
>>> +
>>> + auxbuflen = (cxdev->width * 2 + 4 + 4) *
>>> + (cxdev->height + 50 /* VBI lines */) + urblen;
>>> +
>>> + buf = vmalloc(auxbuflen);
>>> + if (buf == NULL) {
>>> + ret = -ENOMEM;
>>> + goto ret_unstream_md;
>>> + }
>>> +
>>> + cxusb_auxbuf_init(&cxdev->auxbuf, buf, auxbuflen);
>>> + }
>>> +
>>> + for (i = 0; i < CXUSB_VIDEO_URBS; i++) {
>>> + int framen;
>>> + u8 *streambuf;
>>> + struct urb *surb;
>>> +
>>> + streambuf = kmalloc(npackets * CXUSB_VIDEO_PKT_SIZE,
>>> + GFP_KERNEL);
>>> + if (streambuf == NULL) {
>>> + if (i == 0) {
>>> + ret = -ENOMEM;
>>> + goto ret_freeab;
>>> + } else
>>> + break;
>>> + }
>>> +
>>> + surb = usb_alloc_urb(npackets, GFP_KERNEL);
>>> + if (surb == NULL) {
>>> + kfree(streambuf);
>>> + ret = -ENOMEM;
>>> + goto ret_freeu;
>>> + }
>>> +
>>> + cxdev->streamurbs[i] = surb;
>>> + surb->dev = dvbdev->udev;
>>> + surb->context = dvbdev;
>>> + surb->pipe = usb_rcvisocpipe(dvbdev->udev, 2);
>>> +
>>> + surb->interval = 1;
>>> + surb->transfer_flags = URB_ISO_ASAP;
>>> +
>>> + surb->transfer_buffer = streambuf;
>>> +
>>> + surb->complete = cxusb_medion_v_complete;
>>> + surb->number_of_packets = npackets;
>>> + surb->transfer_buffer_length = npackets * CXUSB_VIDEO_PKT_SIZE;
>>> +
>>> + for (framen = 0; framen < npackets; framen++) {
>>> + surb->iso_frame_desc[framen].offset =
>>> + CXUSB_VIDEO_PKT_SIZE * framen;
>>> +
>>> + surb->iso_frame_desc[framen].length =
>>> + CXUSB_VIDEO_PKT_SIZE;
>>> + }
>>> + }
>>> +
>>> + cxdev->urbcomplete = 0;
>>> + cxdev->nexturb = 0;
>>> + cxdev->vbuf = NULL;
>>> + cxdev->bt656.mode = NEW_FRAME;
>>> + cxdev->bt656.buf = NULL;
>>> +
>>> + for (i = 0; i < CXUSB_VIDEO_URBS; i++)
>>> + if (cxdev->streamurbs[i] != NULL) {
>>> + ret = usb_submit_urb(cxdev->streamurbs[i],
>>> + GFP_KERNEL);
>>> + if (ret != 0)
>>> + dev_err(&dvbdev->udev->dev,
>>> + "URB %d submission failed (%d)\n", i,
>>> + ret);
>>> + }
>>> +
>>> + cxdev->streaming = true;
>>
>> No need to keep track of the streaming state. You can use vb2_start_streaming_called()
>> instead since vb2 already keeps track of this.
>
> The driver still needs some flag to tell its workqueue task to not
> process and resubmit URBs when the stream is being stopped.
>
> Otherwise, as the device lock gets released to flush the workqueue task
> there will be a time window where this task could still resubmit URBs
> (not knowing that we are stopping the stream).
> And we need to have the URBs killed before we flush the workqueue since
> their completion routine can schedule the workqueue task.
>
> There seems to be no vb2 specific flag for this:
> q->start_streaming_called is zeroed only after stop_streaming callback
> has already returned.
> And we need to have URBs killed and the workqueue task flushed before
> we return from stop_streaming callback so we can clean up their
> resources in this callback.
Ah, it's used in cxusb_medion_v_complete_work. I think it would be better
to set a 'stop_streaming' flag that cxusb_medion_v_complete_work() looks
at, rather than this 'streaming' flag.
There should not be any URBs in flight before start_streaming is called, and
there should not be any URBs in flight when stop_streaming exists.
cxusb_medion_stream_busy() is very dubious as well. It should never return
true when you call it in start_streaming, and in the other cases it is called
it adds nothing to vb2_is_busy().
>
>>> +
>>> + return 0;
>>> +
>>> +ret_freeu:
>>> + for (i = 0; i < CXUSB_VIDEO_URBS; i++)
>>> + if (cxdev->streamurbs[i] != NULL) {
>>> + kfree(cxdev->streamurbs[i]->transfer_buffer);
>>> + usb_free_urb(cxdev->streamurbs[i]);
>>> + cxdev->streamurbs[i] = NULL;
>>> + }
>>> +
>>> +ret_freeab:
>>> + if (!cxdev->raw_mode)
>>> + vfree(cxdev->auxbuf.buf);
>>> +
>>> +ret_unstream_md:
>>> + cxusb_ctrl_msg(dvbdev, CMD_STREAMING_OFF, NULL, 0, NULL, 0);
>>> +
>>> +ret_unstream_cx:
>>> + v4l2_subdev_call(cxdev->cx25840, video, s_stream, 0);
>>> +
>>> +ret_retbufs:
>>> + cxusb_medion_return_buffers(cxdev, true);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static void cxusb_medion_v_stop_streaming(struct vb2_queue *q)
>>> +{
>>> + struct dvb_usb_device *dvbdev = vb2_get_drv_priv(q);
>>> + struct cxusb_medion_dev *cxdev = dvbdev->priv;
>>> + int i, ret;
>>> +
>>> + cxusb_vprintk(dvbdev, OPS, "should stop streaming\n");
>>> +
>>> + if (!cxdev->streaming)
>>> + return;
>>> +
>>> + cxdev->streaming = false;
>>> +
>>> + cxusb_ctrl_msg(dvbdev, CMD_STREAMING_OFF, NULL, 0, NULL, 0);
>>> +
>>> + ret = v4l2_subdev_call(cxdev->cx25840, video, s_stream, 0);
>>> + if (ret != 0)
>>> + dev_err(&dvbdev->udev->dev, "unable to stop stream (%d)\n",
>>> + ret);
>>> +
>>> + /* let URB completion run */
>>> + mutex_unlock(cxdev->videodev->lock);
>>> +
>>> + for (i = 0; i < CXUSB_VIDEO_URBS; i++)
>>> + if (cxdev->streamurbs[i] != NULL)
>>> + usb_kill_urb(cxdev->streamurbs[i]);
>>> +
>>> + flush_work(&cxdev->urbwork);
>>> +
>>> + mutex_lock(cxdev->videodev->lock);
>>> +
>>> + /* free transfer buffer and URB */
>>> + if (!cxdev->raw_mode)
>>> + vfree(cxdev->auxbuf.buf);
>>> +
>>> + for (i = 0; i < CXUSB_VIDEO_URBS; i++)
>>> + if (cxdev->streamurbs[i] != NULL) {
>>> + kfree(cxdev->streamurbs[i]->transfer_buffer);
>>> + usb_free_urb(cxdev->streamurbs[i]);
>>> + cxdev->streamurbs[i] = NULL;
>>> + }
>>> +
>>> + cxusb_medion_return_buffers(cxdev, false);
>>> +}
>>> +
>>> +static void cxusub_medion_v_buf_queue(struct vb2_buffer *vb)
>>> +{
>>> + struct cxusb_medion_vbuffer *vbuf =
>>> + container_of(vb, struct cxusb_medion_vbuffer, vb2);
>>> + struct dvb_usb_device *dvbdev = vb2_get_drv_priv(vb->vb2_queue);
>>> + struct cxusb_medion_dev *cxdev = dvbdev->priv;
>>> +
>>> + /* cxusb_vprintk(dvbdev, OPS, "mmmm.. fresh buffer...\n"); */
>>> +
>>> + list_add_tail(&vbuf->list, &cxdev->buflist);
>>
>> I would expect a spinlock here to protect the list.
>
> All buffer list accesses are done either while holding main
> lock or in vb2 callbacks (which accoring to
> https://lwn.net/Articles/447435/ can assume that the lock has been
> taken).
Ah, nothing runs in interrupt context in this driver. Got it.
>
>>> +static int cxusb_medion_g_fmt_vid_cap(struct file *file, void *fh,
>>> + struct v4l2_format *f)
>>> +{
>>> + struct dvb_usb_device *dvbdev = video_drvdata(file);
>>> + struct cxusb_medion_dev *cxdev = dvbdev->priv;
>>> +
>>> + f->fmt.pix.width = cxdev->width;
>>> + f->fmt.pix.height = cxdev->height;
>>> + f->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
>>> + f->fmt.pix.field = V4L2_FIELD_SEQ_TB;
>>> + f->fmt.pix.bytesperline = cxdev->raw_mode ? 0 : cxdev->width * 2;
>>
>> Dunno what raw_mode is, but it looks suspicous.
>
> It is simply an ability to capture a raw BT.656 stream as received by
> the driver from the device.
> This functionality is very useful for debugging.
Please drop raw_mode support. Feel free to keep it as a separate patch for
your own debugging, but this isn't the way it should be done if you want
to get this mainlined. Since it is only used for debugging, I don't think
it is worth the effort.
>> I stopped reviewing halfway because I was wondering whether this is the right approach.
>>
>> Most of the analog support is not actually tied to the medion but is fairly generic.
>> This is how other drivers do that as well: the implementation is generic and the board
>> specific bits are implemented via card structures.
>>
>> They also make use of v4l2_device_call_all() instead of calling each subdev directly,
>> this means that the board-specific code loads the correct subdevs, but the generic
>> code doesn't need to know (in general) what those are, it just calls all of them.
>
> There are only three subdevices here: a RF tuner, TDA9887 demod and
> cx25840 chip.
> Most of the subdev calls are for c25840 only, some (like g_tuner) should
> be done in a proper order so returned details aren't overwritten by less
> specific data being returned from the next subdevice.
> The RF tuner and the demod are target of just a few of the subdev calls.
>
> v4l2_device_call_all() ignores any errors, which makes for example trying
If you need to intercept errors, then you can use v4l2_device_call_until_err().
> a video format hard (and which format will be accepted by the cx25840
> depends on the currently set broadcast standard and parameters of the
> last signal that was received), while V4L2 docs say that
> VIDIOC_{S,TRY}_FMT ioctls "should not return an error code unless the
> type field is invalid", that is, they should not return an error for
> invalid or unsupported image widths or heights.
> They should instead return something sensible for these image parameters
> which requires a feedback from the cx25840 subdev which one it will
> finally accept.
>
> With respect to making the code more generic: considering this is a
> 13-year old hardware I think it is fairly unlikely that there are going
> to be devices with similar internal design released in the future.
> And in the unlikely case that this indeed happens then the code can
> always be refactored into some more generic framework.
That's a fair point.
I would like to see some comments that explain that if we ever need to
support other analog hardware, then this code should be made generic.
Regards,
Hans
>
> This driver has been alredy nearly merged back in 2012 (even without last
> year cleanups), just I wasn't able to find time to rebase it then:
> https://www.mail-archive.com/linux-media@xxxxxxxxxxxxxxx/msg50449.html
> so it isn't something completely new.
>
>> Regards,
>>
>> Hans
>>
>
> Thanks and best regards,
> Maciej
>