Skip to content

Commit

Permalink
Simplify interrupt message processing
Browse files Browse the repository at this point in the history
  • Loading branch information
klogg committed May 12, 2024
1 parent 5692fca commit 02f09e0
Showing 1 changed file with 28 additions and 66 deletions.
94 changes: 28 additions & 66 deletions fl2000_interrupt.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,63 +11,44 @@
struct fl2000_intr {
struct usb_device *usb_dev;
struct drm_device *drm;
u8 poll_interval;
struct urb *urb;
u8 *buf;
dma_addr_t transfer_dma;
int pipe;
struct work_struct work;
struct workqueue_struct *work_queue;
};

static void fl2000_intr_work(struct work_struct *work)
{
int event;
struct fl2000_intr *intr = container_of(work, struct fl2000_intr, work);

event = fl2000_check_interrupt(intr->usb_dev);
if (event)
drm_kms_helper_hotplug_event(intr->drm);
struct usb_device *usb_dev = intr->usb_dev;

while (1) {
int ret;

/* Receive interrupt message */
ret = usb_interrupt_msg(usb_dev, intr->pipe, intr->buf, INTR_BUFSIZE, NULL, 0);
if (ret) {
dev_err(&usb_dev->dev, "Interrupt message failed (%d)", ret);
/* TODO: Signal fault to system and start shutdown of usb_dev */
return;
}

ret = fl2000_check_interrupt(intr->usb_dev);
if (ret)
drm_kms_helper_hotplug_event(intr->drm);
}
}

static void fl2000_intr_release(struct device *dev, void *res)
{
struct fl2000_intr *intr = res;
struct usb_device *usb_dev = to_usb_device(dev);

usb_poison_urb(intr->urb);
UNUSED(dev);

cancel_work_sync(&intr->work);
destroy_workqueue(intr->work_queue);
usb_free_coherent(usb_dev, INTR_BUFSIZE, intr->buf, intr->transfer_dma);
usb_free_urb(intr->urb);
}

static void fl2000_intr_completion(struct urb *urb)
{
int ret;
struct usb_device *usb_dev = urb->dev;
struct fl2000_intr *intr = urb->context;

ret = fl2000_urb_status(usb_dev, urb->status, urb->pipe);
if (ret) {
dev_err(&usb_dev->dev, "Stopping interrupts");
return;
}

/* This possibly involves reading I2C registers, etc. so better to schedule a work queue */
queue_work(intr->work_queue, &intr->work);

/* For interrupt URBs, as part of successful URB submission urb->interval is modified to
* reflect the actual transfer period used, so we need to restore it
*/
urb->interval = intr->poll_interval;
urb->start_frame = -1;

/* Restart urb */
ret = fl2000_submit_urb(urb);
if (ret) {
dev_err(&usb_dev->dev, "URB submission failed (%d)", ret);
/* TODO: Signal fault to system and start shutdown of usb_dev */
}

kfree(intr->buf);
}

/**
Expand Down Expand Up @@ -95,6 +76,8 @@ struct fl2000_intr *fl2000_intr_create(struct usb_device *usb_dev, struct drm_de
dev_err(&usb_dev->dev, "Cannot find interrupt endpoint");
return ERR_PTR(ret);
}
ret = usb_endpoint_num(desc);
intr->pipe = usb_rcvintpipe(usb_dev, ret);

intr = devres_alloc(&fl2000_intr_release, sizeof(*intr), GFP_KERNEL);
if (!intr) {
Expand All @@ -103,45 +86,24 @@ struct fl2000_intr *fl2000_intr_create(struct usb_device *usb_dev, struct drm_de
}
devres_add(&usb_dev->dev, intr);

intr->poll_interval = desc->bInterval;
intr->usb_dev = usb_dev;
intr->drm = drm;
INIT_WORK(&intr->work, &fl2000_intr_work);

intr->urb = usb_alloc_urb(0, GFP_ATOMIC);
if (!intr->urb) {
dev_err(&usb_dev->dev, "Allocate interrupt URB failed");
devres_release(&usb_dev->dev, fl2000_intr_release, NULL, NULL);
return ERR_PTR(-ENOMEM);
}

intr->buf = usb_alloc_coherent(usb_dev, INTR_BUFSIZE, GFP_KERNEL, &intr->transfer_dma);
intr->buf = kmalloc(INTR_BUFSIZE, GFP_KERNEL);
if (!intr->buf) {
dev_err(&usb_dev->dev, "Cannot allocate interrupt data");
devres_release(&usb_dev->dev, fl2000_intr_release, NULL, NULL);
return ERR_PTR(-ENOMEM);
}

/* This possibly involves reading I2C registers, etc. so better to schedule a work queue */
INIT_WORK(&intr->work, &fl2000_intr_work);
intr->work_queue = create_workqueue("fl2000_interrupt");
if (!intr->work_queue) {
dev_err(&usb_dev->dev, "Create interrupt workqueue failed");
devres_release(&usb_dev->dev, fl2000_intr_release, NULL, NULL);
return ERR_PTR(-ENOMEM);
}

/* Interrupt URB configuration is static, including allocated buffer */
usb_fill_int_urb(intr->urb, usb_dev, usb_rcvintpipe(usb_dev, 3), intr->buf, INTR_BUFSIZE,
fl2000_intr_completion, intr, intr->poll_interval);
intr->urb->transfer_dma = intr->transfer_dma;
intr->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* use urb->transfer_dma */

/* Start checking for interrupts */
ret = usb_submit_urb(intr->urb, GFP_KERNEL);
if (ret) {
dev_err(&usb_dev->dev, "URB submission failed");
devres_release(&usb_dev->dev, fl2000_intr_release, NULL, NULL);
return ERR_PTR(ret);
}
queue_work(intr->work_queue, &intr->work);

return intr;
}
Expand Down

0 comments on commit 02f09e0

Please sign in to comment.