Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 | // SPDX-License-Identifier: GPL-2.0-or-later /* * uvc_status.c -- USB Video Class driver - Status endpoint * * Copyright (C) 2005-2009 * Laurent Pinchart (laurent.pinchart@ideasonboard.com) */ #include <asm/barrier.h> #include <linux/kernel.h> #include <linux/input.h> #include <linux/slab.h> #include <linux/usb.h> #include <linux/usb/input.h> #include "uvcvideo.h" /* -------------------------------------------------------------------------- * Input device */ #ifdef CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV static int uvc_input_init(struct uvc_device *dev) { struct input_dev *input; int ret; input = input_allocate_device(); if (input == NULL) return -ENOMEM; usb_make_path(dev->udev, dev->input_phys, sizeof(dev->input_phys)); strlcat(dev->input_phys, "/button", sizeof(dev->input_phys)); input->name = dev->name; input->phys = dev->input_phys; usb_to_input_id(dev->udev, &input->id); input->dev.parent = &dev->intf->dev; __set_bit(EV_KEY, input->evbit); __set_bit(KEY_CAMERA, input->keybit); if ((ret = input_register_device(input)) < 0) goto error; dev->input = input; return 0; error: input_free_device(input); return ret; } static void uvc_input_unregister(struct uvc_device *dev) { if (dev->input) input_unregister_device(dev->input); } static void uvc_input_report_key(struct uvc_device *dev, unsigned int code, int value) { if (dev->input) { input_report_key(dev->input, code, value); input_sync(dev->input); } } #else #define uvc_input_init(dev) #define uvc_input_unregister(dev) #define uvc_input_report_key(dev, code, value) #endif /* CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV */ /* -------------------------------------------------------------------------- * Status interrupt endpoint */ struct uvc_streaming_status { u8 bStatusType; u8 bOriginator; u8 bEvent; u8 bValue[]; } __packed; struct uvc_control_status { u8 bStatusType; u8 bOriginator; u8 bEvent; u8 bSelector; u8 bAttribute; u8 bValue[]; } __packed; static void uvc_event_streaming(struct uvc_device *dev, struct uvc_streaming_status *status, int len) { if (len < 3) { uvc_trace(UVC_TRACE_STATUS, "Invalid streaming status event " "received.\n"); return; } if (status->bEvent == 0) { if (len < 4) return; uvc_trace(UVC_TRACE_STATUS, "Button (intf %u) %s len %d\n", status->bOriginator, status->bValue[0] ? "pressed" : "released", len); uvc_input_report_key(dev, KEY_CAMERA, status->bValue[0]); } else { uvc_trace(UVC_TRACE_STATUS, "Stream %u error event %02x len %d.\n", status->bOriginator, status->bEvent, len); } } #define UVC_CTRL_VALUE_CHANGE 0 #define UVC_CTRL_INFO_CHANGE 1 #define UVC_CTRL_FAILURE_CHANGE 2 #define UVC_CTRL_MIN_CHANGE 3 #define UVC_CTRL_MAX_CHANGE 4 static struct uvc_control *uvc_event_entity_find_ctrl(struct uvc_entity *entity, u8 selector) { struct uvc_control *ctrl; unsigned int i; for (i = 0, ctrl = entity->controls; i < entity->ncontrols; i++, ctrl++) if (ctrl->info.selector == selector) return ctrl; return NULL; } static struct uvc_control *uvc_event_find_ctrl(struct uvc_device *dev, const struct uvc_control_status *status, struct uvc_video_chain **chain) { list_for_each_entry((*chain), &dev->chains, list) { struct uvc_entity *entity; struct uvc_control *ctrl; list_for_each_entry(entity, &(*chain)->entities, chain) { if (entity->id != status->bOriginator) continue; ctrl = uvc_event_entity_find_ctrl(entity, status->bSelector); if (ctrl) return ctrl; } } return NULL; } static bool uvc_event_control(struct urb *urb, const struct uvc_control_status *status, int len) { static const char *attrs[] = { "value", "info", "failure", "min", "max" }; struct uvc_device *dev = urb->context; struct uvc_video_chain *chain; struct uvc_control *ctrl; if (len < 6 || status->bEvent != 0 || status->bAttribute >= ARRAY_SIZE(attrs)) { uvc_trace(UVC_TRACE_STATUS, "Invalid control status event " "received.\n"); return false; } uvc_trace(UVC_TRACE_STATUS, "Control %u/%u %s change len %d.\n", status->bOriginator, status->bSelector, attrs[status->bAttribute], len); /* Find the control. */ ctrl = uvc_event_find_ctrl(dev, status, &chain); if (!ctrl) return false; switch (status->bAttribute) { case UVC_CTRL_VALUE_CHANGE: return uvc_ctrl_status_event_async(urb, chain, ctrl, status->bValue); case UVC_CTRL_INFO_CHANGE: case UVC_CTRL_FAILURE_CHANGE: case UVC_CTRL_MIN_CHANGE: case UVC_CTRL_MAX_CHANGE: break; } return false; } static void uvc_status_complete(struct urb *urb) { struct uvc_device *dev = urb->context; int len, ret; switch (urb->status) { case 0: break; case -ENOENT: /* usb_kill_urb() called. */ case -ECONNRESET: /* usb_unlink_urb() called. */ case -ESHUTDOWN: /* The endpoint is being disabled. */ case -EPROTO: /* Device is disconnected (reported by some * host controller). */ return; default: uvc_printk(KERN_WARNING, "Non-zero status (%d) in status " "completion handler.\n", urb->status); return; } len = urb->actual_length; if (len > 0) { switch (dev->status[0] & 0x0f) { case UVC_STATUS_TYPE_CONTROL: { struct uvc_control_status *status = (struct uvc_control_status *)dev->status; if (uvc_event_control(urb, status, len)) /* The URB will be resubmitted in work context. */ return; break; } case UVC_STATUS_TYPE_STREAMING: { struct uvc_streaming_status *status = (struct uvc_streaming_status *)dev->status; uvc_event_streaming(dev, status, len); break; } default: uvc_trace(UVC_TRACE_STATUS, "Unknown status event " "type %u.\n", dev->status[0]); break; } } /* Resubmit the URB. */ urb->interval = dev->int_ep->desc.bInterval; if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { uvc_printk(KERN_ERR, "Failed to resubmit status URB (%d).\n", ret); } } int uvc_status_init(struct uvc_device *dev) { struct usb_host_endpoint *ep = dev->int_ep; unsigned int pipe; int interval; if (ep == NULL) return 0; uvc_input_init(dev); dev->status = kzalloc(UVC_MAX_STATUS_SIZE, GFP_KERNEL); if (dev->status == NULL) return -ENOMEM; dev->int_urb = usb_alloc_urb(0, GFP_KERNEL); if (dev->int_urb == NULL) { kfree(dev->status); return -ENOMEM; } pipe = usb_rcvintpipe(dev->udev, ep->desc.bEndpointAddress); /* For high-speed interrupt endpoints, the bInterval value is used as * an exponent of two. Some developers forgot about it. */ interval = ep->desc.bInterval; if (interval > 16 && dev->udev->speed == USB_SPEED_HIGH && (dev->quirks & UVC_QUIRK_STATUS_INTERVAL)) interval = fls(interval) - 1; usb_fill_int_urb(dev->int_urb, dev->udev, pipe, dev->status, UVC_MAX_STATUS_SIZE, uvc_status_complete, dev, interval); return 0; } void uvc_status_unregister(struct uvc_device *dev) { usb_kill_urb(dev->int_urb); uvc_input_unregister(dev); } void uvc_status_cleanup(struct uvc_device *dev) { usb_free_urb(dev->int_urb); kfree(dev->status); } int uvc_status_start(struct uvc_device *dev, gfp_t flags) { if (dev->int_urb == NULL) return 0; return usb_submit_urb(dev->int_urb, flags); } void uvc_status_stop(struct uvc_device *dev) { struct uvc_ctrl_work *w = &dev->async_ctrl; /* * Prevent the asynchronous control handler from requeing the URB. The * barrier is needed so the flush_status change is visible to other * CPUs running the asynchronous handler before usb_kill_urb() is * called below. */ smp_store_release(&dev->flush_status, true); /* * Cancel any pending asynchronous work. If any status event was queued, * process it synchronously. */ if (cancel_work_sync(&w->work)) uvc_ctrl_status_event(w->chain, w->ctrl, w->data); /* Kill the urb. */ usb_kill_urb(dev->int_urb); /* * The URB completion handler may have queued asynchronous work. This * won't resubmit the URB as flush_status is set, but it needs to be * cancelled before returning or it could then race with a future * uvc_status_start() call. */ if (cancel_work_sync(&w->work)) uvc_ctrl_status_event(w->chain, w->ctrl, w->data); /* * From this point, there are no events on the queue and the status URB * is dead. No events will be queued until uvc_status_start() is called. * The barrier is needed to make sure that flush_status is visible to * uvc_ctrl_status_event_work() when uvc_status_start() will be called * again. */ smp_store_release(&dev->flush_status, false); } |