diff --git a/repos/libports/include/qemu/usb.h b/repos/libports/include/qemu/usb.h
index 6f8092f5c4..70cf0e46fe 100644
--- a/repos/libports/include/qemu/usb.h
+++ b/repos/libports/include/qemu/usb.h
@@ -121,7 +121,8 @@ namespace Qemu {
*/
Controller *usb_init(Timer_queue &tq, Pci_device &pd,
Genode::Entrypoint &ep,
- Genode::Allocator &, Genode::Env &);
+ Genode::Allocator &, Genode::Env &,
+ Genode::Xml_node const &);
/**
* Reset USB libray
diff --git a/repos/libports/lib/mk/qemu-usb.mk b/repos/libports/lib/mk/qemu-usb.mk
index c43b8402f9..cc5b6fbc7a 100644
--- a/repos/libports/lib/mk/qemu-usb.mk
+++ b/repos/libports/lib/mk/qemu-usb.mk
@@ -5,16 +5,20 @@ CC_WARN=
INC_DIR += $(LIB_DIR) $(QEMU_USB_DIR)
-LIBS = qemu-usb_include
+LIBS = qemu-usb_include
+LIBS += libc
+LIBS += libyuv
-SRC_CC = dummies.cc qemu_emul.cc host.cc
+SRC_CC = dummies.cc qemu_emul.cc host.cc webcam.cc
-SRC_C = hcd-xhci.c hcd-xhci-pci.c core.c bus.c
+SRC_C = desc.c hcd-xhci.c hcd-xhci-pci.c core.c bus.c
+SRC_C += dev-webcam.c
SHARED_LIB = yes
LD_OPT += --version-script=$(LIB_DIR)/symbol.map
+vpath dev-webcam.c $(LIB_DIR)
vpath %.c $(QEMU_USB_DIR)
vpath %.cc $(LIB_DIR)
diff --git a/repos/libports/ports/qemu-usb.hash b/repos/libports/ports/qemu-usb.hash
index 253f1857ba..6a9daef2fa 100644
--- a/repos/libports/ports/qemu-usb.hash
+++ b/repos/libports/ports/qemu-usb.hash
@@ -1 +1 @@
-d716b4378eb2f243207f868646dfd7e62469228b
+4b74867ae1e9383a53edb67fc3665ed8b305e2e6
diff --git a/repos/libports/src/lib/qemu-usb/README b/repos/libports/src/lib/qemu-usb/README
index d37cba9811..03081167fc 100644
--- a/repos/libports/src/lib/qemu-usb/README
+++ b/repos/libports/src/lib/qemu-usb/README
@@ -1,8 +1,9 @@
This library makes the xHCI device model of Qemu available on Genode
and is used as a back end for such for device models in existing VMMs.
+Additionally a webcam device model attached to the xHCI device is supported.
-Usage
-~~~~~
+Usage xHCI model
+~~~~~~~~~~~~~~~~
The user of this library is required to provide certain back end
functionality, namely a Timer_queue to handle timer events and a Pci_device
@@ -28,3 +29,27 @@ needs to be called to reattach USB devices.
Timer callbacks that have been registered using the Timer_queue
interface have to be executed by calling 'Qemu::usb_timer_callback'
when the timer triggers.
+
+Usage webcam model attached to the xHCI model
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If the xHCI model is enabled and used, then the webcam device model can be
+enabled by specifying a XML node in the config ROM:
+
+
+ ...
+
+ ...
+
+
+The webcam attributes are optional. The values shown above, are the default ones
+when a attribute is not specified. When the webcam node is specified, the model
+will open and will use a Genode capture session to obtain the
+frames in the rate as specified by the frame per secondes (fps) attribute. The
+'vertical_flip' attribute specifies, whether the frames are shown flipped
+for guests. For Windows guests the value has to be false, for Linux guests
+true. The format supported by the model is in BGR3. If the 'screen_size' is set
+to true, the webcam model will try to use the screen size as provided by the
+capture session. If the screen size is invalid (e.g. 0x0), the attribute
+values of 'width' and 'height' will be used instead. If the 'report' attribute
+is set, a report will be generated whenever the guests starts/ends capturing.
diff --git a/repos/libports/src/lib/qemu-usb/dev-webcam.c b/repos/libports/src/lib/qemu-usb/dev-webcam.c
new file mode 100644
index 0000000000..e4aca89150
--- /dev/null
+++ b/repos/libports/src/lib/qemu-usb/dev-webcam.c
@@ -0,0 +1,804 @@
+/*
+ * QEMU USB webcam model
+ *
+ * Written by Alexander Boettcher
+ *
+ * Copyright (C) 2021 Genode Labs GmbH
+ *
+ * This file is distributed under the terms of the GNU General Public License
+ * version 2.
+ */
+
+#include "hw/usb.h"
+#include "desc.h"
+#include "webcam-backend.h"
+
+enum DeviceConfiguration {
+ DEVICE_VC_INTERFACE_ID = 0,
+ DEVICE_VS_INTERFACE_ID = 1,
+
+ DEVICE_VS_FORMAT_YUV = 1,
+ DEVICE_VS_FORMAT_BGR = 2,
+
+ DEVICE_VS_BITS_YUV = 16,
+ DEVICE_VS_BITS_BGR = 24,
+
+ DEVICE_VS_FRAME_INDEX = 1,
+
+ TERMINAL_ID_INPUT = 1,
+ TERMINAL_ID_OUTPUT = 2,
+
+ DEVICE_EP_ID = 1,
+ EP_MAX_PACKET_SIZE = 512,
+};
+
+typedef struct USBWebcamState {
+ USBDevice dev;
+ QEMUTimer *timer;
+ USBPacket *delayed_packet;
+ unsigned bytes_frame;
+ unsigned bytes_payload;
+ unsigned frame_counter;
+ uint8_t frame_toggle_bit;
+ bool delay_packet;
+ bool capture;
+ uint8_t watchdog;
+ uint8_t *frame_pixel;
+} USBWebcamState;
+
+#define TYPE_USB_WEBCAM "usb-webcam"
+#define USB_WEBCAM(obj) OBJECT_CHECK(USBWebcamState, (obj), TYPE_USB_WEBCAM)
+
+#define U16(x) ((x) & 0xff), (((x) >> 8) & 0xff)
+#define U24(x) U16(x), (((x) >> 16) & 0xff)
+#define U32(x) U24(x), (((x) >> 24) & 0xff)
+
+enum {
+ STR_MANUFACTURER = 1,
+ STR_PRODUCT,
+ STR_SERIALNUMBER,
+ STR_CONFIG_HIGH,
+ STR_VIDEOCONTROL,
+ STR_VIDEOSTREAM,
+ STR_CAMERATERMINAL,
+};
+
+static const USBDescStrings desc_strings = {
+ [STR_MANUFACTURER] = "Genode",
+ [STR_PRODUCT] = "Genode USB WebCAM",
+ [STR_SERIALNUMBER] = "1",
+ [STR_CONFIG_HIGH] = "High speed config (usb 2.0)",
+ [STR_VIDEOCONTROL] = "Videocontrol",
+ [STR_VIDEOSTREAM] = "Videostream",
+ [STR_CAMERATERMINAL] = "Camera Sensor",
+};
+
+enum {
+ USB_CLASS_VIDEO = 0xe,
+ SC_VIDEO_CONTROL = 1,
+ SC_VIDEO_STREAMING = 2,
+ SC_VIDEO_INTERFACE_COLLECTION = 3,
+};
+
+enum {
+ VC_HEADER = 1,
+ VC_INPUT_TERMINAL = 2,
+ VC_OUTPUT_TERMINAL = 3,
+
+ VS_INPUT_HEADER = 1,
+ VS_FORMAT_UNCOMPRESSED = 4,
+ VS_FRAME_UNCOMPRESSED = 5,
+
+ VS_FORMAT_FRAME_BASED = 0x10,
+ VS_FRAME_FRAME_BASED = 0x11,
+};
+
+enum {
+ TT_STREAMING = 0x101,
+ ITT_CAMERA = 0x201,
+};
+
+enum {
+ UV_SET_CUR = 0x01,
+ UV_GET_CUR = 0x81,
+ UV_GET_MIN = 0x82,
+ UV_GET_MAX = 0x83,
+ UV_GET_DEF = 0x87,
+};
+
+enum {
+ VS_PROBE_CONTROL = 0x1,
+ VS_COMMIT_CONTROL = 0x2,
+};
+
+struct vs_probe_control {
+ uint16_t bmHint;
+ uint8_t bFormatIndex;
+ uint8_t bFrameIndex;
+ uint32_t dwFrameInterval;
+ uint16_t wKeyFrameRate;
+ uint16_t wPFrameRate;
+ uint16_t wCompQuality;
+ uint16_t wCompWindowSize;
+ uint16_t wDelay;
+ uint32_t dwMaxVideoFrameSize;
+ uint32_t dwMaxPayLoadTransferSize;
+ uint32_t dwClockFrequency;
+ uint8_t bmFramingInfo;
+ uint8_t bPreferedVersion;
+ uint8_t bMinVersion;
+ uint8_t bMaxVersion;
+} QEMU_PACKED;
+
+static struct vs_probe_control vs_commit_state = {
+ .bFormatIndex = DEVICE_VS_FORMAT_YUV,
+ .bFrameIndex = DEVICE_VS_FRAME_INDEX,
+ .bmFramingInfo = 1,
+ .bPreferedVersion = 1, /* Payload version 1.(1!) for uncompressed and frame based format */
+ .bMinVersion = 1,
+ .bMaxVersion = 1,
+};
+static struct vs_probe_control vs_probe_state;
+
+enum {
+ BFH_END_OF_FRAME = 1U << 1,
+ BFH_PRESENT_TIME = 1U << 2,
+ BFH_END_OF_HEADER = 1U << 7,
+};
+
+struct payload_header {
+ uint8_t length;
+ uint8_t bfh;
+ uint32_t timestamp;
+} QEMU_PACKED;
+
+static struct bgr3_frame_desc {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubType;
+ uint8_t bFrameIndex;
+ uint8_t bmCapabilities;
+ uint16_t wWidth;
+ uint16_t wHeight;
+ uint32_t dwMinBitRate;
+ uint32_t dwMaxBitRate;
+ uint32_t dwDefaultFrameInterval;
+ uint8_t bFrameIntervalType;
+ uint32_t dwBytesPerLine;
+ uint32_t dwFrameInterval;
+} QEMU_PACKED bgr_desc = {
+ .bLength = 30, /* n=0 ->38, >0 = 26 + 4*n */
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = VS_FRAME_FRAME_BASED,
+ .bFrameIndex = DEVICE_VS_FRAME_INDEX,
+ .bmCapabilities = 1 | 2, /* D0: Still image, D1: Fixed frame-rate */
+ .bFrameIntervalType = 1, /* n */
+};
+
+static struct yuv_frame_desc {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubType;
+ uint8_t bFrameIndex;
+ uint8_t bmCapabilities;
+ uint16_t wWidth;
+ uint16_t wHeight;
+ uint32_t dwMinBitRate;
+ uint32_t dwMaxBitRate;
+ uint32_t dwMaxVideoFrameBufferSize;
+ uint32_t dwDefaultFrameInterval;
+ uint8_t bFrameIntervalType;
+ uint32_t dwFrameInterval;
+} QEMU_PACKED yuv_desc = {
+ .bLength = 30, /* n=0 ->38, >0 = 26 + 4*n */
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = VS_FRAME_UNCOMPRESSED,
+ .bFrameIndex = DEVICE_VS_FRAME_INDEX,
+ .bmCapabilities = 1 | 2, /* D0: Still image, D1: Fixed frame-rate */
+ .bFrameIntervalType = 1, /* n */
+};
+
+static struct {
+ uint8_t bpp;
+ uint16_t width;
+ uint16_t height;
+ uint32_t interval; /* dwFrameInterval */
+ void (*capture)(void *);
+} formats [2];
+
+static unsigned active_format()
+{
+ return vs_commit_state.bFormatIndex - 1;
+}
+
+static const USBDescIface desc_iface_high [] = {
+ {
+ .bInterfaceNumber = DEVICE_VC_INTERFACE_ID,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = SC_VIDEO_CONTROL,
+ .bInterfaceProtocol = 0, /* undefined */
+ .iInterface = STR_VIDEOCONTROL,
+ .ndesc = 3,
+ .descs = (USBDescOther[]) {
+ {
+ /* Class-specific VC Interface Header Descriptor */
+ .data = (uint8_t[]) {
+ 12 + 1, /* u8 bLength 12 + n */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ VC_HEADER, /* u8 bDescriptorSubType */
+ U16(0x0110), /* u16 bcdUVC */
+ U16(13 + 15 + 9), /* u16 wTotalLength */
+ U32(1000000), /* u32 dwClockFrequency - deprecated */
+ 0x01, /* u8 bInCollection */
+ 0x01 /* u8 baInterfaceNr(1 .. n) */
+ }
+ },
+ {
+ /* Camera Terminal Descriptor */
+ .data = (uint8_t[]) {
+ 15 + 0, /* u8 bLength 15 + n */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ VC_INPUT_TERMINAL, /* u8 bDescriptorSubType */
+ TERMINAL_ID_INPUT, /* u8 bTerminalID */
+ U16(ITT_CAMERA), /* u16 wTerminalType */
+ 0, /* u8 bAssocTerminal */
+ STR_CAMERATERMINAL, /* u8 iTerminal */
+ 0, /* u16 wObjectFocalLengthMin */
+ 0, /* u16 wObjectFocalLengthMax */
+ 0, /* u16 wOcularFocalLength */
+ 0 /* u8 bControlSize */
+ }
+ },
+ {
+ .data = (uint8_t[]) {
+ 9 + 0, /* u8 bLength 9 + n */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ VC_OUTPUT_TERMINAL, /* u8 bDescriptorSubType */
+ TERMINAL_ID_OUTPUT, /* u8 bTerminalID */
+ U16(TT_STREAMING), /* u16 wTerminalType */
+ 0, /* u8 bAssocTerminal */
+ TERMINAL_ID_INPUT, /* u8 bSourceID (<- bTerminalID) */
+ 0 /* u8 iTerminal */
+ }
+ }
+ }
+ },
+ {
+ .bInterfaceNumber = DEVICE_VS_INTERFACE_ID,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = SC_VIDEO_STREAMING,
+ .bInterfaceProtocol = 0, /* undefined */
+ .iInterface = STR_VIDEOSTREAM,
+ .ndesc = 5,
+ .descs = (USBDescOther[]) {
+ {
+ /* Class-specific VS Interface Header Descriptor */
+ .data = (uint8_t[]) {
+ 13 + 2 * 1, /* u8 bLength 13 + p * n */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ VS_INPUT_HEADER, /* u8 bDescriptorSubType */
+ 2, /* u8 bNumFormats p */
+ U16(14 + 27 + 30), /* u16 wTotalLength */
+ USB_DIR_IN | DEVICE_EP_ID, /* u8 bEndpointAddress */
+ 0, /* u8 bmInfo */
+ TERMINAL_ID_OUTPUT, /* u8 bTerminalLink <- bTerminalID */
+ 1, /* u8 bStillCaptureMethod */
+ 1, /* u8 bTriggerSupport */
+ 0, /* u8 bTriggerUsage */
+ 1, /* u8 bControlSize n */
+ 0 /* u8 bmaControls (1...n) */
+ }
+ },
+ {
+ .data = (uint8_t[]) {
+ 27, /* u8 bLength */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ VS_FORMAT_UNCOMPRESSED, /* u8 bDescriptorSubType */
+ DEVICE_VS_FORMAT_YUV, /* u8 bFormatIndex */
+ 1, /* u8 bNumFrameDescriptors */
+ 0x59, 0x55, 0x59, 0x32, /* u8 guidFormat x16 - YUY2 4:2:2 */
+ 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0xAA,
+ 0x00, 0x38, 0x9B, 0x71,
+ DEVICE_VS_BITS_YUV, /* u8 bBitsPerPixel */
+ DEVICE_VS_FRAME_INDEX, /* u8 bDefaultFrameIndex */
+ 0, /* u8 bAspectRadioX */
+ 0, /* u8 bAspectRadioY */
+ 0, /* u8 bmInterlaceFlags */
+ 0 /* u8 bCopyProtect */
+ }
+ },
+ {
+ .data = (uint8_t *)&yuv_desc
+ },
+ {
+ .data = (uint8_t[]) {
+ 28, /* u8 bLength */
+ USB_DT_CS_INTERFACE, /* u8 bDescriptorType */
+ VS_FORMAT_FRAME_BASED, /* u8 bDescriptorSubType */
+ DEVICE_VS_FORMAT_BGR, /* u8 bFormatIndex */
+ 1, /* u8 bNumFrameDescriptors */
+ 0x7d, 0xeb, 0x36, 0xe4, /* u8 guidFormat - BGR */
+ 0x4f, 0x52, 0xce, 0x11,
+ 0x9f, 0x53, 0x00, 0x20,
+ 0xaf, 0x0b, 0xa7, 0x70,
+ DEVICE_VS_BITS_BGR, /* u8 bBitsPerPixel */
+ DEVICE_VS_FRAME_INDEX, /* u8 bDefaultFrameIndex */
+ 0, /* u8 bAspectRadioX */
+ 0, /* u8 bAspectRadioY */
+ 0, /* u8 bmInterlaceFlags */
+ 0, /* u8 bCopyProtect */
+ 0 /* u8 bVariableSize */
+ }
+ },
+ {
+ .data = (uint8_t *)&bgr_desc
+ }
+ },
+ .bNumEndpoints = 1,
+ .eps = (USBDescEndpoint[]) {
+ {
+ .bEndpointAddress = USB_DIR_IN | DEVICE_EP_ID,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = EP_MAX_PACKET_SIZE,
+ .bInterval = 1,
+ },
+ }
+ }
+};
+
+static struct USBDescIfaceAssoc desc_iface_group = {
+ .bFirstInterface = 0,
+ .bInterfaceCount = 2,
+ .bFunctionClass = USB_CLASS_VIDEO,
+ .bFunctionSubClass = SC_VIDEO_INTERFACE_COLLECTION,
+ .bFunctionProtocol = 0,
+ .nif = ARRAY_SIZE(desc_iface_high),
+ .ifs = desc_iface_high,
+};
+
+static const USBDescDevice desc_device_high = {
+ .bcdUSB = 0x0200,
+ .bDeviceClass = 0xef, /* Miscellaneous Device Class */
+ .bDeviceSubClass = 0x02, /* common class */
+ .bDeviceProtocol = 0x01, /* Interface Association Descriptor */
+ .bMaxPacketSize0 = 64,
+ .bNumConfigurations = 1,
+ .confs = (USBDescConfig[]) {
+ {
+ .bNumInterfaces = 2,
+ .bConfigurationValue = 1,
+ .iConfiguration = STR_CONFIG_HIGH,
+ .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER,
+ .nif = 0,
+ .nif_groups = 1,
+ .if_groups = &desc_iface_group,
+ },
+ },
+};
+
+static const USBDesc descriptor_webcam = {
+ .id = {
+ .idVendor = 0x46f4, /* CRC16() of "QEMU" */
+ .idProduct = 0x0001,
+ .bcdDevice = 0,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT,
+ .iSerialNumber = STR_SERIALNUMBER,
+ },
+ .high = &desc_device_high,
+ .str = desc_strings,
+};
+
+static const VMStateDescription vmstate_usb_webcam = {
+ .name = TYPE_USB_WEBCAM,
+};
+
+static Property webcam_properties[] = {
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void webcam_start_timer(USBWebcamState * const state)
+{
+ int64_t const now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ timer_mod(state->timer, now_ns + 100ull * formats[active_format()].interval);
+}
+
+static unsigned max_frame_size(unsigned const format)
+{
+ return formats[format].width * formats[format].height *
+ formats[format].bpp / 8;
+}
+
+static void usb_webcam_init_state(USBWebcamState *state)
+{
+ state->delayed_packet = 0;
+ state->bytes_frame = 0;
+ state->bytes_payload = 0;
+ state->frame_counter = 0;
+ state->frame_toggle_bit = 0;
+ state->delay_packet = false;
+ state->capture = false;
+ state->watchdog = 0;
+}
+
+static void usb_webcam_handle_reset(USBDevice *dev)
+{
+ USBWebcamState *state = USB_WEBCAM(dev);
+
+ if (!state)
+ return;
+
+ usb_webcam_init_state(state);
+}
+
+static void usb_webcam_capture_state_changed(bool const on)
+{
+ char const * format = "unknown";
+
+ if (vs_commit_state.bFormatIndex == DEVICE_VS_FORMAT_BGR)
+ format = "BGR3";
+ else if (vs_commit_state.bFormatIndex == DEVICE_VS_FORMAT_YUV)
+ format = "YUY2";
+
+ capture_state_changed(on, format);
+}
+
+static void usb_webcam_setup_packet(USBWebcamState * const state, USBPacket * const p)
+{
+ unsigned packet_size = vs_commit_state.dwMaxPayLoadTransferSize;
+ struct payload_header header = { .length = 0, .bfh = 0 };
+ bool start_timer = !state->bytes_frame;
+
+ if (p->iov.size < packet_size)
+ packet_size = p->iov.size;
+
+ if (packet_size <= sizeof(header)) {
+ p->status = USB_RET_STALL;
+ if (state->capture)
+ usb_webcam_capture_state_changed(false);
+ usb_webcam_init_state(state);
+ return;
+ }
+
+ if (state->bytes_frame >= max_frame_size(active_format())) {
+ p->status = USB_RET_STALL;
+ if (state->capture)
+ usb_webcam_capture_state_changed(false);
+ usb_webcam_init_state(state);
+ return;
+ }
+
+ /* reset capture watchdog */
+ if (state->watchdog) {
+ state->watchdog = 0;
+ start_timer = true;
+ }
+
+ /* check for capture state change */
+ if (!state->capture) {
+ state->capture = true;
+ start_timer = true;
+ usb_webcam_capture_state_changed(state->capture);
+ }
+
+ if (start_timer)
+ webcam_start_timer(state);
+
+ /* payload header */
+ if (!state->bytes_payload || state->bytes_payload >= vs_commit_state.dwMaxPayLoadTransferSize) {
+ header.length = sizeof(header);
+ header.bfh = BFH_END_OF_HEADER | BFH_PRESENT_TIME | state->frame_toggle_bit;
+ header.timestamp = state->frame_counter;
+
+ state->bytes_payload = 0;
+ }
+
+ /* frame end check */
+ if (state->bytes_frame + packet_size - header.length >= max_frame_size(active_format())) {
+ packet_size = header.length + max_frame_size(active_format()) - state->bytes_frame;
+
+ header.bfh |= BFH_END_OF_FRAME;
+
+ state->bytes_payload = 0;
+
+ if (state->frame_toggle_bit)
+ state->frame_toggle_bit = 0;
+ else
+ state->frame_toggle_bit = 1;
+
+ state->frame_counter++;
+ state->delay_packet = true;
+ } else {
+ state->bytes_payload += packet_size;
+ }
+
+ /* copy header data in */
+ if (header.length)
+ usb_packet_copy(p, &header, header.length);
+
+ /* copy frame data in */
+ usb_packet_copy(p, state->frame_pixel + state->bytes_frame,
+ packet_size - header.length);
+ p->status = USB_RET_SUCCESS;
+
+ if (state->delay_packet)
+ state->bytes_frame = 0;
+ else
+ state->bytes_frame += packet_size - header.length;
+}
+
+static void webcam_timeout(void *opague)
+{
+ USBDevice *dev = (USBDevice *)opague;
+ USBWebcamState *state = USB_WEBCAM(opague);
+
+ if (!state->delayed_packet) {
+ unsigned const fps = 10000000u / formats[active_format()].interval;
+ /* capture off detection - after 2s or if in delay_packet state */
+ if (state->delay_packet || (state->watchdog && state->watchdog >= fps * 2)) {
+ state->capture = false;
+ state->delay_packet = false;
+ usb_webcam_capture_state_changed(state->capture);
+ } else {
+ state->watchdog ++;
+ webcam_start_timer(state);
+ }
+ return;
+ }
+
+ USBPacket *p = state->delayed_packet;
+
+ state->delayed_packet = 0;
+ state->delay_packet = false;
+
+ /* request next frame pixel buffer */
+ formats[active_format()].capture(state->frame_pixel);
+
+ /* continue usb transmission with new frame */
+ usb_webcam_setup_packet(state, p);
+ if (p->status == USB_RET_SUCCESS)
+ usb_packet_complete(dev, p);
+}
+
+static void usb_webcam_realize(USBDevice *dev, Error **errp)
+{
+ USBWebcamState *state = USB_WEBCAM(dev);
+
+ usb_desc_create_serial(dev);
+ usb_desc_init(dev);
+
+ /* change target speed, which was set by usb_desc_init to USB_SPEED_FULL */
+ dev->speed = USB_SPEED_HIGH;
+ dev->speedmask = USB_SPEED_MASK_HIGH;
+ /* sets dev->device because of dev->speed* changes */
+ usb_desc_attach(dev);
+
+ state->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, webcam_timeout, dev);
+ state->frame_pixel = g_malloc(formats[DEVICE_VS_FORMAT_BGR - 1].width *
+ formats[DEVICE_VS_FORMAT_BGR - 1].height *
+ formats[DEVICE_VS_FORMAT_BGR - 1].bpp / 8);
+}
+
+static void usb_webcam_handle_control(USBDevice * const dev,
+ USBPacket * const p,
+ int const request, int const value,
+ int const index, int const length,
+ uint8_t * const data)
+{
+ int const ret = usb_desc_handle_control(dev, p, request, value, index,
+ length, data);
+ if (ret >= 0) {
+ p->status = USB_RET_SUCCESS;
+ /* got handled */
+ return;
+ }
+
+ bool stall = true;
+
+ switch (request) {
+ case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
+ if (length || (index != (USB_DIR_IN | DEVICE_EP_ID)))
+ break;
+
+ /* release packets on feature == 0 endpoint clear request */
+ if (!value) {
+ USBWebcamState *state = USB_WEBCAM(dev);
+ if (state && state->delayed_packet)
+ state->delayed_packet = 0;
+ stall = false;
+ }
+ break;
+ case ClassInterfaceRequest | UV_GET_DEF:
+ case ClassInterfaceRequest | UV_GET_CUR:
+ case ClassInterfaceRequest | UV_GET_MIN:
+ case ClassInterfaceRequest | UV_GET_MAX:
+ {
+ if (value & 0xff)
+ break;
+
+ unsigned const cs = (value >> 8) & 0xff; /* control selector */
+ unsigned const interface = index & 0xff;
+ unsigned const entity = (index >> 8) & 0xff;
+
+ if (interface != DEVICE_VS_INTERFACE_ID)
+ break;
+
+ if (cs != VS_PROBE_CONTROL || length < sizeof(vs_probe_state))
+ break;
+
+ memcpy(data, &vs_probe_state, sizeof(vs_probe_state));
+ p->actual_length = sizeof(vs_probe_state);
+ stall = false;
+ break;
+ }
+ case ClassInterfaceOutRequest | UV_SET_CUR:
+ {
+ if (value & 0xff)
+ break;
+
+ unsigned const cs = (value >> 8) & 0xff; /* control selector */
+ unsigned const interface = index & 0xff;
+ unsigned const entity = (index >> 8) & 0xff;
+
+ if (interface != DEVICE_VS_INTERFACE_ID)
+ break;
+
+ if (length < sizeof(vs_probe_state))
+ break;
+
+ struct vs_probe_control * req = (struct vs_probe_control *)data;
+
+ if ((cs != VS_COMMIT_CONTROL) && (cs != VS_PROBE_CONTROL))
+ break;
+
+ if ((req->bFormatIndex != DEVICE_VS_FORMAT_BGR) &&
+ (req->bFormatIndex != DEVICE_VS_FORMAT_YUV))
+ break;
+
+ vs_probe_state.bFormatIndex = req->bFormatIndex;
+ vs_probe_state.dwMaxVideoFrameSize = max_frame_size(vs_probe_state.bFormatIndex - 1);
+ vs_probe_state.dwMaxPayLoadTransferSize = max_frame_size(vs_probe_state.bFormatIndex - 1) / 2;
+
+ if (cs == VS_COMMIT_CONTROL) {
+ bool const notify = vs_commit_state.bFormatIndex != vs_probe_state.bFormatIndex;
+
+ vs_commit_state = vs_probe_state;
+
+ if (notify) {
+ USBWebcamState *state = USB_WEBCAM(dev);
+ usb_webcam_capture_state_changed(state->capture);
+ }
+ }
+
+ stall = false;
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (stall) {
+ qemu_printf("%s:%d unhandled request len=%d, request=%x, value=%x,"
+ " index=%x - stall\n", __func__, __LINE__,
+ length, request, value, index);
+
+ p->status = USB_RET_STALL;
+ } else
+ p->status = USB_RET_SUCCESS;
+}
+
+static void usb_webcam_handle_data(USBDevice *dev, USBPacket *p)
+{
+ USBWebcamState *state = USB_WEBCAM(dev);
+
+ switch (p->pid) {
+ case USB_TOKEN_IN:
+ if (!p->ep || p->ep->nr != DEVICE_EP_ID) {
+ p->status = USB_RET_STALL;
+ if (state->capture)
+ usb_webcam_capture_state_changed(false);
+ usb_webcam_init_state(state);
+ return;
+ }
+ break;
+ default:
+ p->status = USB_RET_STALL;
+ if (state->capture)
+ usb_webcam_capture_state_changed(false);
+ usb_webcam_init_state(state);
+ return;
+ }
+
+ if (state->delay_packet) {
+ p->status = USB_RET_ASYNC;
+
+ state->delayed_packet = p;
+ return;
+ }
+
+ usb_webcam_setup_packet(state, p);
+}
+
+static void usb_webcam_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
+
+ uc->realize = usb_webcam_realize;
+ uc->product_desc = desc_strings[STR_PRODUCT];
+ uc->usb_desc = &descriptor_webcam;
+ uc->handle_reset = usb_webcam_handle_reset;
+ uc->handle_control = usb_webcam_handle_control;
+ uc->handle_data = usb_webcam_handle_data;
+
+ dc->vmsd = &vmstate_usb_webcam;
+ device_class_set_props(dc, webcam_properties);
+}
+
+static const TypeInfo webcam_info = {
+ .name = TYPE_USB_WEBCAM,
+ .parent = TYPE_USB_DEVICE,
+ .instance_size = sizeof(USBWebcamState),
+ .class_init = usb_webcam_class_initfn,
+};
+
+static void usb_webcam_register_types(void)
+{
+ /* request host target configuration */
+ struct webcam_config config;
+ webcam_backend_config(&config);
+
+ unsigned const frame_interval = 10000000u / config.fps; /* in 100ns units */
+
+ for (unsigned i = 0; i < sizeof(formats) / sizeof(formats[0]); i++) {
+ formats[i].width = config.width;
+ formats[i].height = config.height;
+ formats[i].interval = frame_interval;
+ }
+
+ formats[DEVICE_VS_FORMAT_BGR - 1].bpp = DEVICE_VS_BITS_BGR;
+ formats[DEVICE_VS_FORMAT_BGR - 1].capture = capture_bgr_frame;
+ formats[DEVICE_VS_FORMAT_YUV - 1].bpp = DEVICE_VS_BITS_YUV;
+ formats[DEVICE_VS_FORMAT_YUV - 1].capture = capture_yuv_frame;
+
+ /* setup model configuration parameters */
+ { /* BGR3 */
+ unsigned const frame_bpl = config.width * DEVICE_VS_BITS_BGR / 8;
+ unsigned const frame_bitrate = config.width * config.height *
+ DEVICE_VS_BITS_BGR * config.fps;
+ bgr_desc.wWidth = config.width;
+ bgr_desc.wHeight = config.height;
+ bgr_desc.dwMinBitRate = frame_bitrate;
+ bgr_desc.dwMaxBitRate = frame_bitrate;
+ bgr_desc.dwDefaultFrameInterval = frame_interval;
+ bgr_desc.dwFrameInterval = frame_interval;
+ bgr_desc.dwBytesPerLine = frame_bpl;
+ }
+
+ { /* YUV */
+ unsigned const frame_bpl = config.width * DEVICE_VS_BITS_YUV / 8;
+ unsigned const frame_bitrate = config.width * config.height *
+ DEVICE_VS_BITS_YUV * config.fps;
+
+ yuv_desc.wWidth = config.width;
+ yuv_desc.wHeight = config.height;
+ yuv_desc.dwMinBitRate = frame_bitrate;
+ yuv_desc.dwMaxBitRate = frame_bitrate;
+ yuv_desc.dwDefaultFrameInterval = frame_interval;
+ yuv_desc.dwFrameInterval = frame_interval;
+ yuv_desc.dwMaxVideoFrameBufferSize = max_frame_size(DEVICE_VS_FORMAT_YUV - 1);
+ }
+
+ vs_commit_state.dwFrameInterval = frame_interval;
+ vs_commit_state.dwMaxVideoFrameSize = max_frame_size(active_format());
+ vs_commit_state.dwMaxPayLoadTransferSize = max_frame_size(active_format()) / 2;
+ vs_commit_state.dwClockFrequency = config.fps;
+
+ vs_probe_state = vs_commit_state;
+
+ /* register device */
+ type_register_static(&webcam_info);
+}
+
+type_init(usb_webcam_register_types)
diff --git a/repos/libports/src/lib/qemu-usb/dummies.cc b/repos/libports/src/lib/qemu-usb/dummies.cc
index f0fb19315b..63bd861291 100644
--- a/repos/libports/src/lib/qemu-usb/dummies.cc
+++ b/repos/libports/src/lib/qemu-usb/dummies.cc
@@ -212,13 +212,27 @@ void qdev_simple_device_unplug_cb(HotplugHandler*, DeviceState*, Error**)
}
-char* qdev_get_dev_path(DeviceState*)
+/**
+ * close to original in hw/core/qdev.c
+ */
+char *qdev_get_dev_path(DeviceState *dev)
{
- TRACE_AND_STOP;
- return 0;
+ BusClass *bc;
+
+ if (!dev || !dev->parent_bus) {
+ return nullptr;
+ }
+
+ bc = BUS_GET_CLASS(dev->parent_bus);
+ if (bc->get_dev_path) {
+ return bc->get_dev_path(dev);
+ }
+
+ return nullptr;
}
+
const char* qdev_fw_name(DeviceState*)
{
TRACE;
@@ -256,7 +270,14 @@ gchar* g_strdup(const gchar*)
}
-size_t strlen(const char*)
+/************************
+ ** hw/usb/desc-msos.c **
+ ************************/
+
+struct USBDesc;
+struct USBPacket;
+int usb_desc_msos(const USBDesc *desc, USBPacket *p,
+ int index, uint8_t *dest, size_t len)
{
TRACE_AND_STOP;
return 0;
diff --git a/repos/libports/src/lib/qemu-usb/files.list b/repos/libports/src/lib/qemu-usb/files.list
index e108d1c731..8112b1095a 100644
--- a/repos/libports/src/lib/qemu-usb/files.list
+++ b/repos/libports/src/lib/qemu-usb/files.list
@@ -3,6 +3,8 @@ qemu-x.x.x/include/hw/usb/xhci.h
qemu-x.x.x/include/qemu/queue.h
qemu-x.x.x/hw/usb/bus.c
qemu-x.x.x/hw/usb/core.c
+qemu-x.x.x/hw/usb/desc.c
+qemu-x.x.x/hw/usb/desc.h
qemu-x.x.x/hw/usb/hcd-xhci.c
qemu-x.x.x/hw/usb/hcd-xhci.h
qemu-x.x.x/hw/usb/hcd-xhci-nec.c
diff --git a/repos/libports/src/lib/qemu-usb/include/qemu_emul.h b/repos/libports/src/lib/qemu-usb/include/qemu_emul.h
index d64e9ecc15..82e29694b8 100644
--- a/repos/libports/src/lib/qemu-usb/include/qemu_emul.h
+++ b/repos/libports/src/lib/qemu-usb/include/qemu_emul.h
@@ -32,6 +32,7 @@ typedef signed long ssize_t;
#ifndef __cplusplus
typedef _Bool bool;
enum { false = 0, true = 1 };
+typedef __WCHAR_TYPE__ wchar_t;
#endif
typedef __SIZE_TYPE__ size_t;
typedef unsigned long dma_addr_t;
@@ -74,6 +75,7 @@ char *strchr(const char *s, int c);
#define NULL (void *)0
#define QEMU_SENTINEL
+#define QEMU_PACKED __attribute__((packed))
#define le32_to_cpu(x) (x)
#define cpu_to_le32(x) (x)
@@ -240,6 +242,8 @@ struct BusClass *cast_BusClass(void *);
struct HotplugHandlerClass *cast_HotplugHandlerClass(void *);
struct USBDeviceClass *cast_USBDeviceClass(void *);
+struct USBWebcamState;
+struct USBWebcamState *cast_USBWebcamState(void *);
struct USBBus *cast_USBBus(void *);
@@ -255,6 +259,8 @@ struct USBBus *cast_USBBus(void *);
#define OBJECT_GET_CLASS(klass, obj, str) \
OBJECT_CHECK(klass, obj, str)
+#define BUS_GET_CLASS(obj) OBJECT_GET_CLASS(BusClass, (obj), TYPE_BUS)
+
#define DECLARE_INSTANCE_CHECKER(InstanceType, OBJ_NAME, TYPENAME) \
static inline InstanceType * \
OBJ_NAME(const void *obj) \
@@ -853,6 +859,18 @@ typedef struct VMStateDescription
#define trace_usb_xhci_xfer_start(...) TRACE_PRINTF("%s:%d\n", "trace_usb_xhci_xfer_start", __LINE__)
#define trace_usb_xhci_xfer_success(...) TRACE_PRINTF("%s:%d\n", "trace_usb_xhci_xfer_success", __LINE__)
+#define trace_usb_desc_device(...) TRACE_PRINTF("%s:%d\n", "trace_usb_desc_device", __LINE__)
+#define trace_usb_desc_config(...) TRACE_PRINTF("%s:%d\n", "trace_usb_desc_config", __LINE__)
+#define trace_usb_desc_string(...) TRACE_PRINTF("%s:%d\n", "trace_usb_desc_string", __LINE__)
+#define trace_usb_desc_device_qualifier(...) TRACE_PRINTF("%s:%d\n", "trace_usb_desc_device_qualifier", __LINE__)
+#define trace_usb_desc_other_speed_config(...) TRACE_PRINTF("%s:%d\n", "trace_usb_desc_other_speed_config", __LINE__)
+#define trace_usb_desc_bos(...) TRACE_PRINTF("%s:%d\n", "trace_usb_desc_bos", __LINE__)
+#define trace_usb_set_addr(...) TRACE_PRINTF("%s:%d\n", "trace_usb_set_addr", __LINE__)
+#define trace_usb_set_config(...) TRACE_PRINTF("%s:%d\n", "trace_usb_set_config", __LINE__)
+#define trace_usb_clear_device_feature(...) TRACE_PRINTF("%s:%d\n", "trace_usb_clear_device_feature", __LINE__)
+#define trace_usb_set_device_feature(...) TRACE_PRINTF("%s:%d\n", "trace_usb_set_device_feature", __LINE__)
+#define trace_usb_set_interface(...) TRACE_PRINTF("%s:%d\n", "trace_usb_set_interface", __LINE__)
+#define trace_usb_desc_msos(...) TRACE_PRINTF("%s:%d\n", "trace_usb_desc_msos", __LINE__)
/***********************
** library interface **
diff --git a/repos/libports/src/lib/qemu-usb/qemu_emul.cc b/repos/libports/src/lib/qemu-usb/qemu_emul.cc
index c358f915d7..f0330086f3 100644
--- a/repos/libports/src/lib/qemu-usb/qemu_emul.cc
+++ b/repos/libports/src/lib/qemu-usb/qemu_emul.cc
@@ -13,6 +13,7 @@
*/
/* Genode includes */
+#include
#include
#include
#include
@@ -68,6 +69,9 @@ extern "C" void _type_init_usb_host_register_types(Genode::Entrypoint*,
extern "C" void _type_init_xhci_register_types();
extern "C" void _type_init_xhci_pci_register_types();
+extern "C" void _type_init_host_webcam_register_types(Genode::Env &,
+ Genode::Xml_node const &);
+
extern Genode::Mutex _mutex;
Qemu::Controller *qemu_controller();
@@ -80,7 +84,9 @@ static Genode::Allocator *_heap = nullptr;
Qemu::Controller *Qemu::usb_init(Timer_queue &tq, Pci_device &pci,
Genode::Entrypoint &ep,
- Genode::Allocator &alloc, Genode::Env &env)
+ Genode::Allocator &alloc,
+ Genode::Env &env,
+ Genode::Xml_node const &config)
{
_heap = &alloc;
_timer_queue = &tq;
@@ -91,6 +97,10 @@ Qemu::Controller *Qemu::usb_init(Timer_queue &tq, Pci_device &pci,
_type_init_xhci_pci_register_types();
_type_init_usb_host_register_types(&ep, &alloc, &env);
+ config.with_sub_node("webcam", [&] (Genode::Xml_node const &node) {
+ _type_init_host_webcam_register_types(env, node);
+ });
+
return qemu_controller();
}
@@ -210,6 +220,9 @@ struct Wrapper
XHCIPciState *_xhci_pci_state = nullptr;
USBHostDevice _usb_host_device;
+ USBWebcamState *_webcam_state = nullptr;
+ unsigned long _webcam_state_size = 0;
+
ObjectClass _object_class;
DeviceClass _device_class;
PCIDeviceClass _pci_device_class;
@@ -239,6 +252,11 @@ struct Wrapper
&& ptr < ((char*)_xhci_pci_state + sizeof(XHCIPciState)))
return true;
+ if (_webcam_state != nullptr
+ && ptr >= _webcam_state
+ && ptr < ((char*)_webcam_state + _webcam_state_size))
+ return true;
+
using addr_t = Genode::addr_t;
addr_t p = (addr_t)ptr;
@@ -259,6 +277,7 @@ struct Object_pool
USB_BUS, /* bus driver */
USB_DEVICE, /* USB device driver */
USB_HOST_DEVICE, /* USB host device driver */
+ USB_WEBCAM, /* USB host device driver */
MAX = 10 /* host devices (USB_HOST_DEVICE - MAX) */
};
@@ -351,6 +370,10 @@ struct DeviceClass *cast_DeviceClass(void *ptr) {
return &Object_pool::p()->find_object(ptr)->_device_class; }
+struct USBWebcamState *cast_USBWebcamState(void *ptr) {
+ return Object_pool::p()->find_object(ptr)->_webcam_state; }
+
+
struct USBDeviceClass *cast_USBDeviceClass(void *ptr) {
return &Object_pool::p()->find_object(ptr)->_usb_device_class; }
@@ -371,7 +394,8 @@ struct USBBus *cast_DeviceStateToUSBBus(void) {
return &Object_pool::p()->xhci_state()->bus; }
-USBHostDevice *create_usbdevice(void *data)
+template
+static USBHostDevice *_create_usbdevice(int const type, FUNC const &fn_init)
{
Wrapper *obj = Object_pool::p()->create_object();
if (!obj) {
@@ -379,7 +403,7 @@ USBHostDevice *create_usbdevice(void *data)
return nullptr;
}
- obj->_usb_device_class = Object_pool::p()->obj[Object_pool::USB_HOST_DEVICE]._usb_device_class;
+ obj->_usb_device_class = Object_pool::p()->obj[type]._usb_device_class;
/*
* Set parent bus object
@@ -387,8 +411,10 @@ USBHostDevice *create_usbdevice(void *data)
DeviceState *dev_state = &obj->_device_state;
dev_state->parent_bus = Object_pool::p()->bus();
- /* set data pointer */
- obj->_usb_host_device.data = data;
+ obj->_usb_device.qdev.parent_bus = dev_state->parent_bus;
+
+ /* per type initialization */
+ fn_init(*obj);
/*
* Attach new USB host device to USB device driver
@@ -410,6 +436,16 @@ USBHostDevice *create_usbdevice(void *data)
}
+USBHostDevice *create_usbdevice(void *data)
+{
+ return _create_usbdevice(Object_pool::USB_HOST_DEVICE,
+ [&](Wrapper &obj) {
+ /* set data pointer */
+ obj._usb_host_device.data = data;
+ });
+}
+
+
void remove_usbdevice(USBHostDevice *device)
{
try {
@@ -512,6 +548,16 @@ Type type_register_static(TypeInfo const *t)
t->class_init(c, 0);
}
+ if (!Genode::strcmp(t->name, "usb-webcam")) {
+ Wrapper *w = &Object_pool::p()->obj[Object_pool::USB_WEBCAM];
+ t->class_init(&w->_object_class, 0);
+
+ _create_usbdevice(Object_pool::USB_WEBCAM, [&](Wrapper &obj) {
+ obj._webcam_state = (USBWebcamState *)g_malloc(t->instance_size);
+ obj._webcam_state_size = t->instance_size;
+ });
+ }
+
return nullptr;
}
diff --git a/repos/libports/src/lib/qemu-usb/webcam-backend.h b/repos/libports/src/lib/qemu-usb/webcam-backend.h
new file mode 100644
index 0000000000..d2b597818e
--- /dev/null
+++ b/repos/libports/src/lib/qemu-usb/webcam-backend.h
@@ -0,0 +1,25 @@
+/**
+ * \brief USB webcam model back end definition
+ * \author Alexander Boettcher
+ *
+ * Copyright (C) 2021 Genode Labs GmbH
+ *
+ * This file is distributed under the terms of the GNU General Public License
+ * version 2.
+ */
+
+#ifndef _WEBCAM_BACKEND_H_
+#define _WEBCAM_BACKEND_H_
+
+struct webcam_config {
+ unsigned width;
+ unsigned height;
+ unsigned fps;
+};
+
+extern void webcam_backend_config(struct webcam_config *);
+extern void capture_bgr_frame(void * pixel);
+extern void capture_yuv_frame(void * pixel);
+extern void capture_state_changed(bool on, char const * format);
+
+#endif /* _WEBCAM_BACKEND_H_ */
diff --git a/repos/libports/src/lib/qemu-usb/webcam.cc b/repos/libports/src/lib/qemu-usb/webcam.cc
new file mode 100644
index 0000000000..af926b3689
--- /dev/null
+++ b/repos/libports/src/lib/qemu-usb/webcam.cc
@@ -0,0 +1,160 @@
+/**
+ * \brief USB webcam model back end using capture session
+ * \author Alexander Boettcher
+ * \date 2021-04-08
+ *
+ * Copyright (C) 2021 Genode Labs GmbH
+ *
+ * This file is distributed under the terms of the GNU General Public License
+ * version 2.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+extern "C" {
+ #include "webcam-backend.h"
+
+ void _type_init_usb_webcam_register_types();
+
+}
+
+using namespace Genode;
+
+struct Capture_webcam
+{
+ Env &_env;
+ Capture::Connection _capture { _env, "webcam" };
+ Gui::Area const _area;
+ bool const _vflip;
+ uint8_t const _fps;
+ Attached_dataspace _ds { _env.rm(), _capture.dataspace() };
+ Constructible _reporter { };
+
+
+ Gui::Area setup_area(Gui::Area const area_in, bool const auto_area)
+ {
+ Gui::Area area = area_in;
+
+ if (auto_area) {
+ area = _capture.screen_size();
+
+ if (!area.valid())
+ area = area_in;
+ }
+
+ /* request setup of dataspace by server */
+ _capture.buffer(area);
+ return area;
+ }
+
+ void update_yuv(void *frame)
+ {
+ _capture.capture_at(Capture::Point(0, 0));
+
+ int const src_stride_argb = _area.w() * 4;
+ int const dst_stride_yuy2 = _area.w() * 2;
+
+ libyuv::ARGBToYUY2(_ds.local_addr(), src_stride_argb,
+ reinterpret_cast(frame), dst_stride_yuy2,
+ _area.w(), _area.h());
+ }
+
+ void update_bgr(void *frame)
+ {
+ _capture.capture_at(Capture::Point(0, 0));
+
+ uint8_t * const bgr = reinterpret_cast(frame);
+ Pixel_rgb888 * data = reinterpret_cast(_ds.local_addr());
+
+ for (int y = 0; y < _area.h(); y++) {
+ unsigned const row = _vflip ? y : _area.h() - 1 - y;
+ unsigned const row_byte = (row * _area.w() * 3);
+ for (int x = 0; x < _area.w(); x++) {
+ bgr[row_byte + x * 3 + 0] = data->b();
+ bgr[row_byte + x * 3 + 1] = data->g();
+ bgr[row_byte + x * 3 + 2] = data->r();
+
+ data++;
+ }
+ }
+ }
+
+ void capture_state_changed(bool on, char const * format)
+ {
+ if (!_reporter.constructed())
+ return;
+
+ Reporter::Xml_generator xml(*_reporter, [&] () {
+ xml.attribute("enabled", on);
+ xml.attribute("format", format);
+ });
+ }
+
+ Capture_webcam (Env &env, Gui::Area area, bool auto_area, bool flip,
+ uint8_t fps, bool report)
+ :
+ _env(env),
+ _area(setup_area(area, auto_area)),
+ _vflip(flip),
+ _fps(fps)
+ {
+ if (report) {
+ _reporter.construct(_env, "capture");
+ _reporter->enabled(true);
+ }
+
+ log("USB webcam ", _area, " fps=", _fps, " vertical_flip=",
+ _vflip ? "yes" : "no",
+ " report=", _reporter.constructed() ? "enabled" : "disabled");
+ }
+};
+
+static Genode::Constructible capture;
+
+extern "C" void capture_state_changed(bool on, char const * format)
+{
+ capture->capture_state_changed(on, format);
+}
+
+extern "C" void capture_bgr_frame(void * pixel)
+{
+ capture->update_bgr(pixel);
+}
+
+extern "C" void capture_yuv_frame(void * pixel)
+{
+ capture->update_yuv(pixel);
+}
+
+
+extern "C" void webcam_backend_config(struct webcam_config *config)
+{
+ config->fps = capture->_fps;
+ config->width = capture->_area.w();
+ config->height = capture->_area.h();
+}
+
+/*
+ * Do not use type_init macro because of name mangling
+ */
+extern "C" void _type_init_host_webcam_register_types(Env &env,
+ Xml_node const &webcam)
+{
+ /* initialize capture session */
+ capture.construct(env, Gui::Area(webcam.attribute_value("width", 640u),
+ webcam.attribute_value("height", 480u)),
+ webcam.attribute_value("screen_size", false),
+ webcam.attribute_value("vertical_flip", false),
+ webcam.attribute_value("fps", 15u),
+ webcam.attribute_value("report", false));
+
+ /* register webcam model, which will call webcam_backend_config() */
+ _type_init_usb_webcam_register_types();
+}
diff --git a/repos/ports/recipes/src/vbox5-nova/content.mk b/repos/ports/recipes/src/vbox5-nova/content.mk
index 30e99b7d79..5ea6109401 100644
--- a/repos/ports/recipes/src/vbox5-nova/content.mk
+++ b/repos/ports/recipes/src/vbox5-nova/content.mk
@@ -45,9 +45,15 @@ MIRROR_FROM_LIBPORTS := lib/mk/libc-mem.mk \
src/lib/libc/libc_mem_alloc.cc \
lib/import/import-qemu-usb_include.mk \
lib/mk/qemu-usb_include.mk \
- lib/mk/qemu-usb.mk \
+ lib/mk/qemu-usb.inc \
+ lib/mk/spec/x86_32/qemu-usb.mk \
+ lib/mk/spec/x86_64/qemu-usb.mk \
include/qemu \
- src/lib/qemu-usb
+ src/lib/qemu-usb \
+ lib/import/import-libyuv.mk \
+ lib/mk/libyuv.inc \
+ lib/mk/spec/x86_32/libyuv.mk \
+ lib/mk/spec/x86_64/libyuv.mk
content: $(MIRROR_FROM_LIBPORTS)
@@ -65,6 +71,16 @@ $(MIRROR_FROM_QEMU_USB_PORT_DIR):
mkdir -p $(dir $@)
cp -r $(QEMU_USB_PORT_DIR)/$@ $(dir $@)
+LIBYUV_PORT_DIR := $(call port_dir,$(GENODE_DIR)/repos/libports/ports/libyuv)
+
+MIRROR_FROM_LIBYUV_PORT_DIR := libyuv
+
+content: $(MIRROR_FROM_LIBYUV_PORT_DIR)
+
+$(MIRROR_FROM_LIBYUV_PORT_DIR):
+ mkdir -p $(dir $@)
+ cp -r $(LIBYUV_PORT_DIR)/$@ $(dir $@)
+
MIRROR_FROM_OS := src/drivers/ps2/scan_code_set_1.h \
include/pointer/shape_report.h \
diff --git a/repos/ports/recipes/src/vbox5-nova/used_apis b/repos/ports/recipes/src/vbox5-nova/used_apis
index a5f5a30828..ccc97b5d19 100644
--- a/repos/ports/recipes/src/vbox5-nova/used_apis
+++ b/repos/ports/recipes/src/vbox5-nova/used_apis
@@ -2,6 +2,7 @@ audio_in_session
audio_out_session
base
base-nova
+capture_session
framebuffer_session
input_session
libc
diff --git a/repos/ports/recipes/src/vbox5/content.mk b/repos/ports/recipes/src/vbox5/content.mk
index 0ffb1fb667..86dff157b9 100644
--- a/repos/ports/recipes/src/vbox5/content.mk
+++ b/repos/ports/recipes/src/vbox5/content.mk
@@ -46,9 +46,15 @@ MIRROR_FROM_LIBPORTS := lib/mk/libc-mem.mk \
src/lib/libc/libc_mem_alloc.cc \
lib/import/import-qemu-usb_include.mk \
lib/mk/qemu-usb_include.mk \
- lib/mk/qemu-usb.mk \
+ lib/mk/qemu-usb.inc \
+ lib/mk/spec/x86_32/qemu-usb.mk \
+ lib/mk/spec/x86_64/qemu-usb.mk \
include/qemu \
- src/lib/qemu-usb
+ src/lib/qemu-usb \
+ lib/import/import-libyuv.mk \
+ lib/mk/libyuv.inc \
+ lib/mk/spec/x86_32/libyuv.mk \
+ lib/mk/spec/x86_64/libyuv.mk
content: $(MIRROR_FROM_LIBPORTS)
@@ -66,6 +72,16 @@ $(MIRROR_FROM_QEMU_USB_PORT_DIR):
mkdir -p $(dir $@)
cp -r $(QEMU_USB_PORT_DIR)/$@ $(dir $@)
+LIBYUV_PORT_DIR := $(call port_dir,$(GENODE_DIR)/repos/libports/ports/libyuv)
+
+MIRROR_FROM_LIBYUV_PORT_DIR := libyuv
+
+content: $(MIRROR_FROM_LIBYUV_PORT_DIR)
+
+$(MIRROR_FROM_LIBYUV_PORT_DIR):
+ mkdir -p $(dir $@)
+ cp -r $(LIBYUV_PORT_DIR)/$@ $(dir $@)
+
MIRROR_FROM_OS := src/drivers/ps2/scan_code_set_1.h \
include/pointer/shape_report.h \
diff --git a/repos/ports/recipes/src/vbox5/used_apis b/repos/ports/recipes/src/vbox5/used_apis
index a3279065ad..7a32f33f7a 100644
--- a/repos/ports/recipes/src/vbox5/used_apis
+++ b/repos/ports/recipes/src/vbox5/used_apis
@@ -1,6 +1,7 @@
audio_in_session
audio_out_session
base
+capture_session
framebuffer_session
input_session
libc
diff --git a/repos/ports/recipes/src/vbox6/content.mk b/repos/ports/recipes/src/vbox6/content.mk
index 32d3ef2d62..49a759ac03 100644
--- a/repos/ports/recipes/src/vbox6/content.mk
+++ b/repos/ports/recipes/src/vbox6/content.mk
@@ -31,11 +31,17 @@ src/virtualbox6_sdk:
MIRROR_FROM_LIBPORTS := \
include/qemu \
+ lib/import/import-libyuv.mk \
lib/import/import-qemu-usb_include.mk \
lib/mk/libc-common.inc \
lib/mk/libc-mem.mk \
- lib/mk/qemu-usb.mk \
+ lib/mk/libyuv.inc \
+ lib/mk/qemu-usb.inc \
lib/mk/qemu-usb_include.mk \
+ lib/mk/spec/x86_32/libyuv.mk \
+ lib/mk/spec/x86_32/qemu-usb.mk \
+ lib/mk/spec/x86_64/libyuv.mk \
+ lib/mk/spec/x86_64/qemu-usb.mk \
src/lib/libc/internal/init.h \
src/lib/libc/internal/mem_alloc.h \
src/lib/libc/internal/monitor.h \
@@ -63,6 +69,16 @@ $(MIRROR_FROM_QEMU_USB_PORT_DIR):
mkdir -p $(dir $@)
cp -r $(QEMU_USB_PORT_DIR)/$@ $(dir $@)
+LIBYUV_PORT_DIR := $(call port_dir,$(GENODE_DIR)/repos/libports/ports/libyuv)
+
+MIRROR_FROM_LIBYUV_PORT_DIR := libyuv
+
+content: $(MIRROR_FROM_LIBYUV_PORT_DIR)
+
+$(MIRROR_FROM_LIBYUV_PORT_DIR):
+ mkdir -p $(dir $@)
+ cp -r $(LIBYUV_PORT_DIR)/$@ $(dir $@)
+
MIRROR_FROM_OS := src/drivers/ps2/scan_code_set_1.h \
include/pointer/shape_report.h \
diff --git a/repos/ports/recipes/src/vbox6/used_apis b/repos/ports/recipes/src/vbox6/used_apis
index d8ed7e0240..de40389dd7 100644
--- a/repos/ports/recipes/src/vbox6/used_apis
+++ b/repos/ports/recipes/src/vbox6/used_apis
@@ -1,5 +1,6 @@
base
blit
+capture_session
framebuffer_session
gui_session
input_session
diff --git a/repos/ports/run/vbox5_win10_64.run b/repos/ports/run/vbox5_win10_64.run
index 7df52a8e66..6e3a6cf71a 100644
--- a/repos/ports/run/vbox5_win10_64.run
+++ b/repos/ports/run/vbox5_win10_64.run
@@ -28,4 +28,6 @@ set use_cpu_load 0
# use non-generic vbox5 VMM version
set use_vbox5_nova 1
+set use_webcam 1
+
source ${genode_dir}/repos/ports/run/vbox_win.inc
diff --git a/repos/ports/run/vbox_win.inc b/repos/ports/run/vbox_win.inc
index 46d0cca63a..a76bb1a569 100644
--- a/repos/ports/run/vbox_win.inc
+++ b/repos/ports/run/vbox_win.inc
@@ -23,6 +23,17 @@ set overlay_image "overlay_${flavor}.vdi"
if {[info exists flavor_extension]} {
set vbox_file "vm_${flavor}${flavor_extension}.vbox"
}
+if {![info exists use_webcam]} {
+ set use_webcam 0
+}
+if {![info exists webcam_vflip]} {
+ set webcam_vflip true
+ if {$use_webcam} {
+ if {[string match "win*" $flavor] } {
+ set webcam_vflip false
+ }
+ }
+}
set build_components {
server/nic_router
@@ -267,6 +278,10 @@ for { set i 1} { $i <= $use_vms } { incr i} {
"
}
+
+ append_if [expr $use_webcam] config_of_app {
+ }
+
append config_of_app {
diff --git a/repos/ports/src/virtualbox5/devxhci.cc b/repos/ports/src/virtualbox5/devxhci.cc
index c128628d08..19b32427ac 100644
--- a/repos/ports/src/virtualbox5/devxhci.cc
+++ b/repos/ports/src/virtualbox5/devxhci.cc
@@ -494,8 +494,10 @@ static DECLCALLBACK(int) xhciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFG
pThis->timer_queue = &timer_queue;
static Pci_device pci_device(pDevIns);
+ Genode::Attached_rom_dataspace config(genode_env(), "config");
+
pThis->ctl = Qemu::usb_init(timer_queue, pci_device, *pThis->usb_ep,
- vmm_heap(), genode_env());
+ vmm_heap(), genode_env(), config.xml());
Qemu::Controller::Info const ctl_info = pThis->ctl->info();
diff --git a/repos/ports/src/virtualbox6/devxhci.cc b/repos/ports/src/virtualbox6/devxhci.cc
index 3d5f2dc567..2d1b8d509c 100644
--- a/repos/ports/src/virtualbox6/devxhci.cc
+++ b/repos/ports/src/virtualbox6/devxhci.cc
@@ -476,8 +476,10 @@ static DECLCALLBACK(int) xhciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFG
pThis->timer_queue = &timer_queue;
static Pci_device pci_device(alloc, pDevIns);
+ Genode::Attached_rom_dataspace config(*_xhci_genode_env, "config");
+
pThis->ctl = Qemu::usb_init(timer_queue, pci_device, *pThis->usb_ep,
- alloc, *_xhci_genode_env);
+ alloc, *_xhci_genode_env, config.xml());
Qemu::Controller::Info const ctl_info = pThis->ctl->info();