mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-21 14:37:50 +00:00
parent
4826bd82fe
commit
58db8c647a
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -1 +1 @@
|
||||
d716b4378eb2f243207f868646dfd7e62469228b
|
||||
4b74867ae1e9383a53edb67fc3665ed8b305e2e6
|
||||
|
@ -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:
|
||||
|
||||
<config ...>
|
||||
...
|
||||
<webcam width="640" height="480" fps="15" vertical_flip="false" screen_size="false" report="false"/>
|
||||
...
|
||||
</config>
|
||||
|
||||
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.
|
||||
|
804
repos/libports/src/lib/qemu-usb/dev-webcam.c
Normal file
804
repos/libports/src/lib/qemu-usb/dev-webcam.c
Normal file
@ -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)
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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 **
|
||||
|
@ -13,6 +13,7 @@
|
||||
*/
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/attached_rom_dataspace.h>
|
||||
#include <base/env.h>
|
||||
#include <base/log.h>
|
||||
#include <util/misc_math.h>
|
||||
@ -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 <typename FUNC>
|
||||
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;
|
||||
}
|
||||
|
||||
|
25
repos/libports/src/lib/qemu-usb/webcam-backend.h
Normal file
25
repos/libports/src/lib/qemu-usb/webcam-backend.h
Normal file
@ -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_ */
|
160
repos/libports/src/lib/qemu-usb/webcam.cc
Normal file
160
repos/libports/src/lib/qemu-usb/webcam.cc
Normal file
@ -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 <base/allocator.h>
|
||||
#include <base/env.h>
|
||||
#include <capture_session/connection.h>
|
||||
#include <gui_session/gui_session.h>
|
||||
#include <os/reporter.h>
|
||||
#include <util/xml_node.h>
|
||||
|
||||
#include <libyuv/convert_from_argb.h>
|
||||
|
||||
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> _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<uint8_t>(), src_stride_argb,
|
||||
reinterpret_cast<uint8_t*>(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<uint8_t *>(frame);
|
||||
Pixel_rgb888 * data = reinterpret_cast<Pixel_rgb888 *>(_ds.local_addr<void>());
|
||||
|
||||
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_webcam> 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();
|
||||
}
|
@ -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 \
|
||||
|
||||
|
@ -2,6 +2,7 @@ audio_in_session
|
||||
audio_out_session
|
||||
base
|
||||
base-nova
|
||||
capture_session
|
||||
framebuffer_session
|
||||
input_session
|
||||
libc
|
||||
|
@ -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 \
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
audio_in_session
|
||||
audio_out_session
|
||||
base
|
||||
capture_session
|
||||
framebuffer_session
|
||||
input_session
|
||||
libc
|
||||
|
@ -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 \
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
base
|
||||
blit
|
||||
capture_session
|
||||
framebuffer_session
|
||||
gui_session
|
||||
input_session
|
||||
|
@ -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
|
||||
|
@ -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} {
|
||||
<affinity xpos=\"[expr $i]\" width=\"2\" />
|
||||
<config ld_verbose=\"yes\" vbox_file=\"${vbox_file}\" vm_name=\"${flavor}\" xhci=\"no\" capslock=\"NOROM\">"
|
||||
}
|
||||
|
||||
append_if [expr $use_webcam] config_of_app {
|
||||
<webcam vertical_flip="} $webcam_vflip {" fps="15" report="false" screen_size="false" width="640" height="480" />}
|
||||
|
||||
append config_of_app {
|
||||
<libc stdout="/dev/log" stderr="/dev/log" rtc="/dev/rtc" pipe="/pipe"/>
|
||||
<vfs>
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user