Hi Ivan,
> Lauren wrote:
>> I don't own any MacBook so I can't test driver hacks. A few users
>> helped me in creating a patch for the Linux UVC driver to support
>> iSight webcams. I wasn't very happy with the last version, so I
>> asked for feedback on a revised patch. I got no reply so far.
>
> Where is this patch? Generally it's me and Ronald maintaining
> different versions of the patch. Mine has the isight functionallity
> self contained and has firmware loading in kernel space.
>
> I have attached an updated isight patch against revision 121 (svn
> head) of the trunk. There is a regression introduced for me between
> revision 100 and 121 in finding a valid video chain. Kernel version
> here is 2.6.18.8. Output of modprobe uvcvideo trace=15 of the two
> revisions below.
I have your patch running at the moment with kernel 2.6.22.3 and
uvcvideo r117. The patch is included in the uvcvideo.src.rpm of openSUSE
10.3 factory I just extracted it and attached it to this eMail. This
version is working.
# md5sum /lib/firmware/AppleUSBVideoSupport
8b78709d02d3584f40cc041db9eecfe8 /lib/firmware/AppleUSBVideoSupport
I can use the cam with:
mplayer tv:// -tv driver=v4l2:width=352:height=288:device=/dev/video0
-fps 30
This is with mplayer r24115.
greetings
Felix Möller
Index: uvc_video.c
===================================================================
--- uvc_video.c (revision 108)
+++ uvc_video.c (working copy)
@@ -22,6 +22,7 @@
#include <asm/atomic.h>
#include "uvcvideo.h"
+#include "isight.h"
/* ------------------------------------------------------------------------
* UVC Controls
@@ -216,10 +217,7 @@
#define UVC_STREAM_EOF (1 << 1)
#define UVC_STREAM_FID (1 << 0)
-/*
- *
- */
-static int uvc_video_decode(struct uvc_video_queue *queue,
+static int uvc_video_decode_uvc(struct uvc_video_queue *queue,
struct uvc_buffer *buf, const __u8 *data, unsigned int len)
{
unsigned int maxlen, nbytes;
@@ -315,6 +313,12 @@
return 0;
}
+static int uvc_video_decode(struct uvc_video_queue *queue,
+ struct uvc_buffer *buf, const __u8 *data, unsigned int len)
+{
+ return queue->decode(queue, buf, data, len);
+}
+
/* ------------------------------------------------------------------------
* URB handling
*/
@@ -722,6 +726,14 @@
video->streaming->cur_frame = frame;
atomic_set(&video->active, 1);
+ /* Select the video decoder based. This might be better handled
+ * elsewhere, based on the format GUID instead of the device ID.
+ */
+ if (is_isight (video->dev->udev) == 0)
+ video->queue.decode = isight_decode_video;
+ else
+ video->queue.decode = uvc_video_decode_uvc;
+
return 0;
}
Index: isight.c
===================================================================
--- isight.c (revision 0)
+++ isight.c (revision 0)
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2006 Ivan N. Zlatev <[EMAIL PROTECTED]>
+ *
+ * Based on extract.c by Ronald S. Bultje <[EMAIL PROTECTED]>
+ * Firmware loading specifics by Johannes Berg <[EMAIL PROTECTED]>
+ * at http://johannes.sipsolutions.net/MacBook/iSight
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA
+ */
+
+#include <linux/usb.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/crypto.h>
+#include <linux/firmware.h>
+#include <linux/mm.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+
+#include "isight.h"
+#include "uvcvideo.h"
+
+#define isight_printk(msg...) \
+ printk(KERN_DEBUG "uvcvideo: iSight: " msg)
+
+#define TIMEOUT 300
+#define N_ELEMENT(array) (sizeof (array) / sizeof ((array)[0]))
+
+static const struct {
+ unsigned char sha1sum[20];
+ long offset;
+} offsets[] = {
+ { { 0x86, 0x43, 0x0c, 0x04, 0xf9, 0xb6, 0x7c, 0x5c,
+ 0x3d, 0x84, 0x40, 0x91, 0x38, 0xa7, 0x67, 0x98,
+ 0x27, 0x02, 0x5e, 0xc2 }, 5172 /* 0x1434 */ },
+ { { 0xa1, 0x4c, 0x15, 0x9b, 0x17, 0x6d, 0x27, 0xa6,
+ 0xe9, 0x8d, 0xcb, 0x5d, 0xea, 0x5d, 0x78, 0xb8,
+ 0x1e, 0x15, 0xad, 0x41 }, 9176 /* 0x23D8 */ },
+ { { 0xc6, 0xc9, 0x4d, 0xd7, 0x7b, 0x86, 0x4f, 0x8b,
+ 0x2d, 0x31, 0xab, 0xf3, 0xcb, 0x2d, 0xe4, 0xc9,
+ 0xd1, 0x39, 0xe1, 0xbf }, 0x1434 },
+ { { 0x01, 0xe2, 0x91, 0xd5, 0x29, 0xe7, 0xc1, 0x8d,
+ 0xee, 0xa2, 0xeb, 0xa2, 0x52, 0xd1, 0x81, 0x14,
+ 0xe0, 0x96, 0x27, 0x6e }, 0x2060 },
+};
+
+
+
+/* A record looks like:
+ __be16 data_length;
+ __be16 value;
+ __u8 data[data_length];
+ */
+
+static int isight_upload_firmware (struct usb_device *dev,
+ unsigned char *data, size_t size, long offset)
+{
+ int position = 0, success = 0;
+ int record_size, record_val, chunk_size;
+ unsigned char *chunk_ptr;
+ unsigned char *data_k;
+
+ if (offset > size) {
+ return -1;
+ }
+
+ data_k = kmalloc (size, GFP_KERNEL);
+ if (data_k == NULL) {
+ isight_printk ("Unable to kalloc memory. \n");
+ return -1;
+ }
+ memcpy (data_k, data, size);
+ data = data_k;
+
+ data += offset;
+
+ if (usb_control_msg (dev, usb_rcvctrlpipe (dev, 0), 0xA0, 0x40, 0xe600, 0,
+ "\1", 1, TIMEOUT) < 0) {
+ isight_printk ("firmware loading init failed\n");
+ success = -1;
+ goto end;
+ }
+
+ while (1) {
+ if (position > size) {
+ goto end;
+ }
+
+ record_size = (data[position + 0] << 8) | data[position + 1];
+ record_val = (data[position + 2] << 8) | data[position + 3];
+
+ position += 4;
+
+ if (record_size == 0x8001) { /* success */
+ goto end;
+ }
+ else if (record_size == 0) {
+ continue;
+ }
+ else if (record_size < 0 || record_size >= 1024 ||
+ position + record_size > size) {
+ isight_printk ("invalid firmware record_size: %X \n", record_size);
+ success = -1;
+ goto end;
+ }
+
+ /* Upload to usb bus in 50 bytes chunks,
+ where the last can be less than 50
+ */
+
+ chunk_ptr = &data[position];
+ position += record_size;
+
+ while (record_size > 0) {
+ chunk_size = record_size > 50 ? 50 : record_size;
+ record_size -= chunk_size;
+ if (usb_control_msg (dev, usb_sndctrlpipe (dev, 0), 0xA0, 0x40,
+ record_val, 0, chunk_ptr, chunk_size,
+ TIMEOUT) != chunk_size) {
+ isight_printk ("firmware upload failed.\n");
+ success = -1;
+ goto end;
+ }
+ chunk_ptr += chunk_size;
+ record_val += 50;
+ }
+ }
+
+end:
+ if (usb_control_msg (dev, usb_sndctrlpipe (dev, 0), 0xA0, 0x40, 0xe600, 0,
+ "\0", 1, TIMEOUT) < 0) {
+ isight_printk ("firmware loading finish-up failed.\n");
+ success = -1;
+ }
+
+ kfree (data_k);
+ return success;
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
+static void isight_sha1 (char *result, unsigned char *data, int size)
+{
+ struct crypto_tfm *tfm;
+ struct scatterlist sg[1];
+
+ if (result == NULL)
+ return;
+ memset (result, 0x00, 20);
+ sg_set_buf (sg, data, size);
+ tfm = crypto_alloc_tfm ("sha1", 0);
+
+ if (tfm != NULL) {
+ crypto_digest_init (tfm);
+ crypto_digest_update (tfm, sg, 1);
+ crypto_digest_final (tfm, result);
+ crypto_free_tfm (tfm);
+ }
+}
+#else
+static void isight_sha1 (char *result, unsigned char *data, int size)
+{
+ struct hash_desc desc;
+ struct scatterlist sg[1];
+
+ if (result == NULL)
+ return;
+ memset (result, 0x00, 20);
+ sg_set_buf (sg, data, size);
+
+ desc.tfm = crypto_alloc_hash ("sha1", 0, CRYPTO_ALG_ASYNC);
+ desc.flags = CRYPTO_TFM_REQ_MAY_SLEEP;
+
+ if (desc.tfm != NULL) {
+ crypto_hash_init (&desc);
+ crypto_hash_update (&desc, sg, size);
+ crypto_hash_final (&desc, result);
+ crypto_free_hash (desc.tfm);
+ }
+}
+#endif
+
+/* returns the offset where the firmware blob starts, else -1 */
+static long isight_extract_firmware_offset (unsigned char *data, size_t size)
+{
+ char sha1sum[20];
+ int i, offset;
+
+ if (data == NULL ||size <= 0) {
+ return -1;
+ }
+
+ isight_sha1 (sha1sum, data, size);
+
+ offset = -1;
+ for (i=0; i < N_ELEMENT (offsets); i++) {
+ if (memcmp (offsets[i].sha1sum, sha1sum, 20) == 0) {
+ offset = offsets[i].offset;
+ break;
+ }
+ }
+
+ return offset;
+}
+
+
+int isight_load_firmware (struct usb_device *dev)
+{
+ long offset;
+ size_t size;
+ int success = -1;
+ const struct firmware *fw;
+ unsigned char *data_k = NULL;
+
+
+ if (dev->descriptor.idVendor == 0x05ac &&
+ dev->descriptor.idProduct == 0x8300 &&
+ dev->descriptor.bDeviceClass == 0xff &&
+ dev->descriptor.bDeviceSubClass == 0xff &&
+ dev->descriptor.bDeviceProtocol == 0xff) {
+
+ /* request firmware using the firmware_class kernel interface */
+ if (request_firmware (&fw, "AppleUSBVideoSupport", &dev->dev) != 0 ) {
+ isight_printk ("firmware file AppleUSBVideoSupport not found.\n");
+ return -1;
+ }
+
+ /* copy to kernel memory - required*/
+ data_k = kmalloc (fw->size, GFP_KERNEL);
+ if (data_k == NULL) {
+ isight_printk ("can't kalloc memory.");
+ return -1;
+ }
+ memcpy (data_k, fw->data, fw->size);
+ size = fw->size;
+ release_firmware (fw);
+
+ offset = isight_extract_firmware_offset (data_k, size);
+ if (offset == -1) {
+ isight_printk ("invalid firmware file\n");
+ }
+ else {
+ if (isight_upload_firmware (dev, data_k, size, offset) == 0) {
+ isight_printk ("firmware successfully loaded.\n");
+ success = 0;
+ }
+ }
+ kfree (data_k);
+ }
+ else if (dev->descriptor.idVendor == 0x05ac &&
+ dev->descriptor.idProduct == 0x8501) {
+ isight_printk ("firmware already loaded.\n");
+ success = 0;
+ }
+
+ return success;
+}
+
+
+int isight_decode_video (struct uvc_video_queue *queue, struct uvc_buffer *buf,
+ const __u8 *data, unsigned int len)
+{
+ static const __u8 hdr[] = {
+ 0x11, 0x22, 0x33, 0x44, 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xfa, 0xce
+ };
+
+ unsigned int maxlen, nbytes;
+ __u8 *mem;
+ int is_header = 0;
+
+ if (buf == NULL)
+ return 0;
+
+ /* Built-in iSight webcams are completely broken. They implement most
+ * of UVC 1.0, but the Apple engineers decided to use a completely
+ * different packet format, although the video data is in YUV. Were
+ * they on crack or just lazy ? As the hardware is 8051-based, it
+ * might be interesting to write an open-source firmware.
+ *
+ * Instead of sending a header at the beginning of each isochronous
+ * transfer payload, the webcam sends a single header per image (on
+ * its own in a packet), followed by packets containing data only.
+ *
+ * Offset Size (bytes) Description
+ * ------------------------------------------------------------------
+ * 0x00 1 Header length
+ * 0x01 1 Flags (UVC-compliant)
+ * 0x02 4 Always equal to '11223344'
+ * 0x06 8 Always equal to 'deadbeefdeadface'
+ * 0x0e 16 Unknown
+ *
+ * The header can be prefixed by an optional, unknown-purpose byte.
+ */
+
+ /* Detect the packet type. */
+ if ((len >= 14 && memcmp (&data[2], hdr, 12) == 0) ||
+ (len >= 15 && memcmp (&data[3], hdr, 12) == 0)) {
+ uvc_trace(UVC_TRACE_FRAME, "iSight header found\n");
+ is_header = 1;
+ }
+
+ /* Synchronize to the input stream by waiting for a header packet. */
+ if (buf->state != UVC_BUF_STATE_ACTIVE) {
+ if (!is_header) {
+ uvc_trace(UVC_TRACE_FRAME, "Dropping packet (out of "
+ "sync).\n");
+ return 0;
+ }
+
+ buf->state = UVC_BUF_STATE_ACTIVE;
+ }
+
+ /* Mark the buffer as done if we're at the beginning of a new frame.
+ *
+ * Empty buffers (bytesused == 0) don't trigger end of frame detection
+ * as it doesn't make sense to return an empty buffer.
+ */
+ if (is_header && buf->buf.bytesused != 0) {
+ buf->state = UVC_BUF_STATE_DONE;
+ return -EAGAIN;
+ }
+
+ /* Copy the video data to the buffer. Skip header packets, as they
+ * contain no data.
+ */
+ if (!is_header) {
+ maxlen = buf->buf.length - buf->buf.bytesused;
+ mem = queue->mem + buf->buf.m.offset + buf->buf.bytesused;
+ nbytes = min(len, maxlen);
+ memcpy(mem, data, nbytes);
+ buf->buf.bytesused += nbytes;
+
+ /* Drop the current frame if the buffer size was exceeded. */
+ if (len > maxlen || buf->buf.bytesused == buf->buf.length) {
+ uvc_trace(UVC_TRACE_FRAME, "Frame complete (overflow).\n");
+ buf->state = UVC_BUF_STATE_DONE;
+ }
+ }
+
+ return 0;
+}
+
+int is_isight (struct usb_device *dev)
+{
+ if (dev->descriptor.idVendor == 0x05ac &&
+ (dev->descriptor.idProduct == 0x8300) &&
+ dev->descriptor.bDeviceClass == 0xff &&
+ dev->descriptor.bDeviceSubClass == 0xff &&
+ dev->descriptor.bDeviceProtocol == 0xff) {
+ return 0;
+ }
+ else if (dev->descriptor.idVendor == 0x05ac &&
+ dev->descriptor.idProduct == 0x8501) {
+ return 0;
+ }
+ return -1;
+}
Index: uvc_driver.c
===================================================================
--- uvc_driver.c (revision 108)
+++ uvc_driver.c (working copy)
@@ -43,6 +43,7 @@
#endif
#include "uvcvideo.h"
+#include "isight.h"
#define DRIVER_AUTHOR "Laurent Pinchart <[EMAIL PROTECTED]>"
#define DRIVER_DESC "USB Video Class driver"
@@ -58,6 +59,14 @@
.fcc = V4L2_PIX_FMT_YUYV,
},
{
+ .guid = UVC_GUID_FORMAT_YUY2_MACOSX,
+ .fcc = V4L2_PIX_FMT_UYVY,
+ },
+ {
+ .guid = UVC_GUID_FORMAT_UYVY,
+ .fcc = V4L2_PIX_FMT_UYVY,
+ },
+ {
.guid = UVC_GUID_FORMAT_NV12,
.fcc = V4L2_PIX_FMT_NV12,
},
@@ -1449,6 +1458,16 @@
kfree(dev);
}
+static int uvc_load_firmware (struct usb_device *dev)
+{
+ int success = 0;
+
+ if (is_isight (dev) == 0) {
+ success = isight_load_firmware (dev);
+ }
+ return success;
+}
+
static int uvc_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
@@ -1468,6 +1487,9 @@
if ((dev = kzalloc(sizeof *dev, GFP_KERNEL)) == NULL)
return -ENOMEM;
+ if (uvc_load_firmware (udev) < 0)
+ return -ENODEV;
+
INIT_LIST_HEAD(&dev->entities);
INIT_LIST_HEAD(&dev->streaming);
kref_init(&dev->kref);
@@ -1554,7 +1576,21 @@
* VENDOR_SPEC because they don't announce themselves as UVC devices, even
* though they are compliant.
*/
-static struct usb_device_id uvc_ids[] = {
+static struct usb_device_id uvc_ids[] = {
+ /* Apple Built-In iSight - with firmware loaded */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE,
+ .idVendor = 0x05ac,
+ .idProduct = 0x8501,
+ .driver_info = UVC_QUIRK_PROBE_MINMAX },
+ /* Apple Built-In iSight - no firmware loaded */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x05ac,
+ .idProduct = 0x8300,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = 0xff,
+ .bInterfaceProtocol = 0xff,
+ .driver_info = UVC_QUIRK_PROBE_MINMAX },
/* Microsoft Lifecam NX-6000 */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
Index: uvcvideo.h
===================================================================
--- uvcvideo.h (revision 108)
+++ uvcvideo.h (working copy)
@@ -216,6 +216,12 @@
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
#define UVC_GUID_FORMAT_YUY2 {0x59, 0x55, 0x59, 0x32, 0x00, 0x00, 0x10, 0x00, \
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_YUY2_MACOSX \
+ {0x59, 0x55, 0x59, 0x32, 0x00, 0x00, 0x10, 0x00, \
+ 0x00, 0x80, 0x71, 0x9b, 0x38, 0x00, 0xaa, 0x00}
+#define UVC_GUID_FORMAT_UYVY {0x55, 0x59, 0x56, 0x59, 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+
#define UVC_GUID_FORMAT_NV12 {0x4e, 0x56, 0x31, 0x32, 0x00, 0x00, 0x10, 0x00, \
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
@@ -506,6 +512,9 @@
struct list_head mainqueue;
struct list_head irqqueue;
+
+ int (*decode)(struct uvc_video_queue *queue, struct uvc_buffer *buf,
+ const __u8 *data, unsigned int len);
};
struct uvc_video_device {
Index: Makefile
===================================================================
--- Makefile (revision 108)
+++ Makefile (working copy)
@@ -5,7 +5,7 @@
PWD := $(shell pwd)
obj-m := uvcvideo.o
-uvcvideo-objs := uvc_driver.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_ctrl.o
+uvcvideo-objs := uvc_driver.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_ctrl.o isight.o
all: uvcvideo
Index: isight.h
===================================================================
--- isight.h (revision 0)
+++ isight.h (revision 0)
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2006 Ivan N. Zlatev <[EMAIL PROTECTED]>
+ *
+ * Based on extract.c by Ronald S. Bultje <[EMAIL PROTECTED]>
+ * Firmware loading specifics by Johannes Berg <[EMAIL PROTECTED]>
+ * at http://johannes.sipsolutions.net/MacBook/iSight
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA
+ */
+
+#ifndef _ISIGHT_H_
+#define _ISIGHT_H_
+
+#include <linux/usb.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/crypto.h>
+#include <linux/firmware.h>
+#include <linux/mm.h>
+#include <linux/scatterlist.h>
+
+#include "uvcvideo.h"
+
+int is_isight (struct usb_device *dev);
+int isight_load_firmware (struct usb_device *dev);
+int isight_decode_video (struct uvc_video_queue *, struct uvc_buffer *, const __u8 *data, unsigned int len);
+
+#endif
+
_______________________________________________
Linux-uvc-devel mailing list
[email protected]
https://lists.berlios.de/mailman/listinfo/linux-uvc-devel