This document describes the development of a Linux kernel driver for a proprietary USB camera device (Geek szitman supercamera, ID 2ce3:3828). The camera doesn't use standard UVC descriptors but follows a proprietary protocol with a custom packet format that carries JPEG image data.
- Vendor ID: 0x2ce3 (Geek szitman)
- Product ID: 0x3828 (supercamera)
- USB Version: 2.0
- Interfaces: 2 interfaces, with interface 1 having multiple alternate settings
- Endpoint: Bulk transfer endpoint (0x81 IN, 0x01 OUT)
- Protocol: Proprietary (not standard UVC), but transmits JPEG images
The driver is implemented as a character device that:
- Initializes the camera with a specific command sequence
- Sets the alternate interface setting to 1
- Captures data from the bulk endpoint
- Processes the proprietary packet format to extract JPEG images
- Makes the image data available to userspace via a /dev/supercamera device
struct camera_frame {
unsigned char *data;
size_t size;
bool ready;
};
struct camera_dev {
struct usb_device *udev;
struct usb_interface *interface;
struct urb *urb;
unsigned char *urb_buffer;
// Connection command
unsigned char connect_cmd[CONNECT_CMD_SIZE];
// Frame ring buffer
struct camera_frame frames[MAX_FRAMES];
int current_frame;
int read_frame;
// Character device
struct cdev cdev;
dev_t dev_num;
struct class *class;
struct device *device;
struct mutex io_mutex;
bool device_open;
// Read position
size_t read_pos;
// State
bool streaming;
// Wait queue for blocking reads
wait_queue_head_t read_wait;
};A critical part of the driver is the initialization sequence. The camera needs:
- Setting interface 1 to alternate setting 1
- Sending a specific command sequence:
0xBB 0xAA 0x05 0x00 0x00
// Start streaming
static int start_streaming(struct camera_dev *dev)
{
int result;
// Set alt setting to 1
result = usb_set_interface(dev->udev, INTERFACE_NUM, 1);
if (result) {
printk(KERN_ERR "Camera: Failed to set alt setting: %d", result);
return result;
}
// Send connect command
result = send_command(dev, dev->connect_cmd, CONNECT_CMD_SIZE);
if (result) {
return result;
}
// [URB setup code...]
}The camera uses a proprietary packet format with the following characteristics:
- Each packet begins with a header
- The header starts with the sequence
0xAA 0xBB 0x07 - The header appears to be 12 bytes long
- The payload data follows the header
- The payload contains JPEG image data, which can span multiple packets
Example packet header:
AA BB 07 AB 03 00 00 00 57 43 00 73
The most challenging part was correctly parsing the proprietary packet format:
static void process_data(struct camera_dev *dev, unsigned char *data, int length)
{
struct camera_frame *frame = &dev->frames[dev->current_frame];
static int packet_count = 0;
// Custom header appears to be 12 bytes
const int HEADER_SIZE = 12;
// Debug first few packets
if (packet_count < 10) {
printk(KERN_INFO "Camera: Packet %d (%d bytes): ", packet_count, length);
for (int i = 0; i < min(16, length); i++) {
printk(KERN_CONT "%02x ", data[i]);
}
printk(KERN_CONT "\n");
packet_count++;
}
// Relaxed header check - only check first 3 bytes
if (length >= 3 && data[0] == 0xaa && data[1] == 0xbb && data[2] == 0x07) {
// Valid packet header processing
// [detailed packet processing code...]
} else {
// Invalid header handling
// [invalid packet handling code...]
}
}Challenge: The camera doesn't use standard UVC protocol.
Solution: Reverse-engineered the protocol by examining packet data and identified a consistent pattern.
Challenge: Initially, we expected a consistent header pattern (AA BB 07 AB 03), but later packets showed variations.
Solution: Relaxed the header check to only verify the first 3 bytes (AA BB 07).
Challenge: Determining where one image ends and another begins.
Solution: Used JPEG markers (SOI: FF D8, EOI: FF D9) to identify image boundaries within the data stream.
Challenge: Some extracted images were incomplete or truncated.
Solution: Implemented a JPEG validation mechanism that ensures only complete, valid JPEG images are processed.
A companion userspace tool extracts individual JPEG images from the camera's data stream:
// extract_images.c
// [detailed implementation...]Key features:
- Opens the /dev/supercamera device
- Reads frames from the driver
- Scans for JPEG SOI/EOI markers
- Extracts and validates individual JPEG images
- Saves valid images to disk
-
Assuming Standard UVC: The device is NOT a standard UVC device despite similarities.
-
Strict Header Parsing: The header format varies slightly between packets. A relaxed approach is needed.
-
Missing Frame Boundaries: Without proper detection of JPEG markers, you'll get incomplete or mixed images.
-
System Freezes: Overly aggressive assumptions about the protocol can lead to kernel crashes.
-
Insufficient Buffer Size: JPEG frames can be large; ensure buffers are big enough.
-
The driver uses a ring buffer of frames to help with throughput.
-
Blocking read operations allow userspace applications to efficiently wait for new frames.
-
Frame size thresholds prevent buffer overflows while ensuring complete images.
-
V4L2 Integration: Implementing the Video4Linux2 API would provide better compatibility with existing applications.
-
Direct JPEG Extraction: The driver could extract individual JPEG images directly rather than passing raw data to userspace.
-
Camera Control: Adding ioctls to control camera parameters like resolution, frame rate, etc.
-
Multiple Opening: Currently only one process can open the device at a time.
This driver provides basic functionality for the proprietary SuperCamera device, enabling Linux applications to capture images from it. While not a standard UVC driver, it successfully handles the device's custom protocol to extract JPEG image data. Do not ask how I developed it, it was full of tears.