919d82b68e
to bx_param_enum_c. Updated code to provide support for legacy syntax (portX="device:option"). TODO: generate USB devices list from plugins list.
622 lines
17 KiB
C++
622 lines
17 KiB
C++
/////////////////////////////////////////////////////////////////////////
|
|
// $Id$
|
|
/////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Generic USB emulation code
|
|
//
|
|
// Copyright (c) 2005 Fabrice Bellard
|
|
// Copyright (C) 2009-2015 Benjamin D Lunt (fys at fysnet net)
|
|
// 2009-2021 The Bochs Project
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "iodev.h"
|
|
|
|
#if BX_SUPPORT_PCI && BX_SUPPORT_PCIUSB
|
|
|
|
#include "usb_common.h"
|
|
|
|
#define LOG_THIS bx_usbdev_ctl.
|
|
|
|
bx_usbdev_ctl_c bx_usbdev_ctl;
|
|
|
|
bx_usbdev_ctl_c::bx_usbdev_ctl_c()
|
|
{
|
|
put("usbdevctl", "USBCTL");
|
|
}
|
|
|
|
void bx_usbdev_ctl_c::init(void)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
void bx_usbdev_ctl_c::exit(void)
|
|
{
|
|
// TODO
|
|
usbdev_locator_c::cleanup();
|
|
}
|
|
|
|
const char *usbmod_names[] =
|
|
{
|
|
"none",
|
|
"usb_cbi",
|
|
"usb_hid",
|
|
"usb_hub",
|
|
"usb_msd",
|
|
"usb_printer"
|
|
};
|
|
|
|
const char *usb_device_names[] =
|
|
{
|
|
"none",
|
|
"mouse",
|
|
"tablet",
|
|
"keypad",
|
|
"keyboard",
|
|
"disk",
|
|
"cdrom",
|
|
"hub",
|
|
"printer",
|
|
"floppy"
|
|
};
|
|
|
|
const char **bx_usbdev_ctl_c::get_device_names(void)
|
|
{
|
|
return usb_device_names;
|
|
}
|
|
|
|
void bx_usbdev_ctl_c::list_devices(void)
|
|
{
|
|
char list[60];
|
|
Bit8u i = 1; // skip "none"
|
|
size_t len = 0, len1;
|
|
|
|
BX_INFO(("Pluggable USB devices"));
|
|
list[0] = 0;
|
|
while (usb_device_names[i] != NULL) {
|
|
len1 = strlen(usb_device_names[i]);
|
|
if ((len + len1 + 1) > 58) {
|
|
BX_INFO((" %s", list));
|
|
list[0] = 0;
|
|
len = 0;
|
|
}
|
|
strcat(list, " ");
|
|
strcat(list, usb_device_names[i]);
|
|
len = strlen(list);
|
|
i++;
|
|
}
|
|
if (len > 0) {
|
|
BX_INFO((" %s", list));
|
|
}
|
|
}
|
|
|
|
int bx_usbdev_ctl_c::init_device(bx_list_c *portconf, logfunctions *hub, void **dev)
|
|
{
|
|
usbmod_type modtype = USB_MOD_TYPE_NONE;
|
|
usbdev_type devtype = USB_DEV_TYPE_NONE;
|
|
usb_device_c **device = (usb_device_c**)dev;
|
|
const char *devname, *options, *options2;
|
|
char new_options[BX_PATHNAME_LEN];
|
|
bx_param_string_c *opts2;
|
|
|
|
devname = ((bx_param_enum_c*)portconf->get_by_name("device"))->get_selected();
|
|
options = ((bx_param_string_c*)portconf->get_by_name("options"))->getptr();
|
|
opts2 = (bx_param_string_c*)portconf->get_by_name("options2");
|
|
if (!strcmp(devname, "mouse")) {
|
|
modtype = USB_MOD_TYPE_HID;
|
|
devtype = USB_DEV_TYPE_MOUSE;
|
|
} else if (!strcmp(devname, "tablet")) {
|
|
modtype = USB_MOD_TYPE_HID;
|
|
devtype = USB_DEV_TYPE_TABLET;
|
|
} else if (!strcmp(devname, "keypad")) {
|
|
modtype = USB_MOD_TYPE_HID;
|
|
devtype = USB_DEV_TYPE_KEYPAD;
|
|
} else if (!strcmp(devname, "keyboard")) {
|
|
modtype = USB_MOD_TYPE_HID;
|
|
devtype = USB_DEV_TYPE_KEYBOARD;
|
|
} else if (!strcmp(devname, "disk")) {
|
|
modtype = USB_MOD_TYPE_MSD;
|
|
devtype = USB_DEV_TYPE_DISK;
|
|
} else if (!strcmp(devname, "cdrom")) {
|
|
modtype = USB_MOD_TYPE_MSD;
|
|
devtype = USB_DEV_TYPE_CDROM;
|
|
} else if (!strcmp(devname, "hub")) {
|
|
modtype = USB_MOD_TYPE_HUB;
|
|
devtype = USB_DEV_TYPE_HUB;
|
|
} else if (!strcmp(devname, "printer")) {
|
|
modtype = USB_MOD_TYPE_PRINTER;
|
|
devtype = USB_DEV_TYPE_PRINTER;
|
|
} else if (!strncmp(devname, "floppy", 6)) {
|
|
modtype = USB_MOD_TYPE_CBI;
|
|
devtype = USB_DEV_TYPE_FLOPPY;
|
|
} else {
|
|
hub->panic("unknown USB device: %s", devname);
|
|
delete [] devname;
|
|
return devtype;
|
|
}
|
|
if (opts2 != NULL) {
|
|
// backward compatibility code
|
|
options2 = opts2->getptr();
|
|
if (strlen(options) > 0) {
|
|
sprintf(new_options, "%s, %s", options2, options);
|
|
} else {
|
|
sprintf(new_options, "%s", options2);
|
|
}
|
|
((bx_param_string_c*)portconf->get_by_name("options"))->set(new_options);
|
|
portconf->remove("options2");
|
|
}
|
|
if (!usbdev_locator_c::module_present(usbmod_names[modtype])) {
|
|
#if BX_PLUGINS
|
|
PLUG_load_plugin_var(usbmod_names[modtype], PLUGTYPE_USB);
|
|
#else
|
|
BX_PANIC(("could not find USB device '%s'", usbmod_names[modtype]));
|
|
#endif
|
|
}
|
|
*device = usbdev_locator_c::create(usbmod_names[modtype], devtype);
|
|
if (*device != NULL) {
|
|
parse_port_options(*device, portconf);
|
|
}
|
|
return devtype;
|
|
}
|
|
|
|
void bx_usbdev_ctl_c::parse_port_options(usb_device_c *device, bx_list_c *portconf)
|
|
{
|
|
const char *raw_options;
|
|
int i, optc, speed = USB_SPEED_LOW; // assume LOW speed device if parameter not given.
|
|
char *opts[16];
|
|
|
|
memset(opts, 0, sizeof(opts));
|
|
raw_options = ((bx_param_string_c*)portconf->get_by_name("options"))->getptr();
|
|
optc = bx_split_option_list("USB port options", raw_options, opts, 16);
|
|
for (i = 0; i < optc; i++) {
|
|
if (!strncmp(opts[i], "speed:", 6)) {
|
|
if (!strcmp(opts[i]+6, "low")) {
|
|
speed = USB_SPEED_LOW;
|
|
} else if (!strcmp(opts[i]+6, "full")) {
|
|
speed = USB_SPEED_FULL;
|
|
} else if (!strcmp(opts[i]+6, "high")) {
|
|
speed = USB_SPEED_HIGH;
|
|
} else if (!strcmp(opts[i]+6, "super")) {
|
|
speed = USB_SPEED_SUPER;
|
|
} else {
|
|
BX_ERROR(("ignoring unknown USB device speed: '%s'", opts[i]+6));
|
|
}
|
|
if (!device->set_speed(speed)) {
|
|
BX_PANIC(("USB device '%s' doesn't support '%s' speed",
|
|
usb_device_names[device->get_type()], opts[i]+6));
|
|
}
|
|
} else if (!strcmp(opts[i], "debug")) {
|
|
device->set_debug_mode();
|
|
} else if (!device->set_option(opts[i])) {
|
|
BX_ERROR(("ignoring unknown USB device option: '%s'", opts[i]));
|
|
}
|
|
}
|
|
for (i = 0; i < optc; i++) {
|
|
if (opts[i] != NULL) {
|
|
free(opts[i]);
|
|
opts[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
usbdev_locator_c *usbdev_locator_c::all;
|
|
|
|
//
|
|
// Each USB device module has a static locator class that registers
|
|
// here
|
|
//
|
|
usbdev_locator_c::usbdev_locator_c(const char *type)
|
|
{
|
|
next = all;
|
|
all = this;
|
|
this->type = type;
|
|
}
|
|
|
|
usbdev_locator_c::~usbdev_locator_c()
|
|
{
|
|
usbdev_locator_c *ptr = 0;
|
|
|
|
if (this == all) {
|
|
all = all->next;
|
|
} else {
|
|
ptr = all;
|
|
while (ptr != NULL) {
|
|
if (ptr->next != this) {
|
|
ptr = ptr->next;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (ptr) {
|
|
ptr->next = this->next;
|
|
}
|
|
}
|
|
|
|
bool usbdev_locator_c::module_present(const char *type)
|
|
{
|
|
usbdev_locator_c *ptr = 0;
|
|
|
|
for (ptr = all; ptr != NULL; ptr = ptr->next) {
|
|
if (strcmp(type, ptr->type) == 0)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void usbdev_locator_c::cleanup()
|
|
{
|
|
#if BX_PLUGINS
|
|
while (all != NULL) {
|
|
PLUG_unload_plugin_type(all->type, PLUGTYPE_USB);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// Called by USB HC emulations to locate and create a usb_device_c
|
|
// object
|
|
//
|
|
usb_device_c* usbdev_locator_c::create(const char *type, usbdev_type devtype)
|
|
{
|
|
usbdev_locator_c *ptr = 0;
|
|
|
|
for (ptr = all; ptr != NULL; ptr = ptr->next) {
|
|
if (strcmp(type, ptr->type) == 0)
|
|
return (ptr->allocate(devtype));
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#undef LOG_THIS
|
|
#define LOG_THIS
|
|
|
|
// Base class for USB devices
|
|
|
|
usb_device_c::usb_device_c(void)
|
|
{
|
|
memset((void*)&d, 0, sizeof(d));
|
|
d.async_mode = 1;
|
|
}
|
|
|
|
// Find device with given address
|
|
usb_device_c* usb_device_c::find_device(Bit8u addr)
|
|
{
|
|
if (addr == d.addr) {
|
|
return this;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// Generic USB packet handler
|
|
|
|
#define SETUP_STATE_IDLE 0
|
|
#define SETUP_STATE_DATA 1
|
|
#define SETUP_STATE_ACK 2
|
|
|
|
int usb_device_c::handle_packet(USBPacket *p)
|
|
{
|
|
int l, ret = 0;
|
|
int len = p->len;
|
|
Bit8u *data = p->data;
|
|
|
|
switch(p->pid) {
|
|
case USB_MSG_ATTACH:
|
|
d.state = USB_STATE_ATTACHED;
|
|
break;
|
|
case USB_MSG_DETACH:
|
|
d.state = USB_STATE_NOTATTACHED;
|
|
break;
|
|
case USB_MSG_RESET:
|
|
d.remote_wakeup = 0;
|
|
d.addr = 0;
|
|
d.state = USB_STATE_DEFAULT;
|
|
handle_reset();
|
|
break;
|
|
case USB_TOKEN_SETUP:
|
|
if (d.state < USB_STATE_DEFAULT || p->devaddr != d.addr)
|
|
return USB_RET_NODEV;
|
|
if (len != 8)
|
|
goto fail;
|
|
d.stall = 0;
|
|
memcpy(d.setup_buf, data, 8);
|
|
d.setup_len = (d.setup_buf[7] << 8) | d.setup_buf[6];
|
|
d.setup_index = 0;
|
|
if (d.setup_buf[0] & USB_DIR_IN) {
|
|
ret = handle_control((d.setup_buf[0] << 8) | d.setup_buf[1],
|
|
(d.setup_buf[3] << 8) | d.setup_buf[2],
|
|
(d.setup_buf[5] << 8) | d.setup_buf[4],
|
|
d.setup_len, d.data_buf);
|
|
if (ret < 0)
|
|
return ret;
|
|
if (ret < d.setup_len)
|
|
d.setup_len = ret;
|
|
d.setup_state = SETUP_STATE_DATA;
|
|
} else {
|
|
if (d.setup_len == 0)
|
|
d.setup_state = SETUP_STATE_ACK;
|
|
else
|
|
d.setup_state = SETUP_STATE_DATA;
|
|
}
|
|
break;
|
|
case USB_TOKEN_IN:
|
|
if (d.state < USB_STATE_DEFAULT || p->devaddr != d.addr)
|
|
return USB_RET_NODEV;
|
|
if (d.stall) goto fail;
|
|
switch(p->devep) {
|
|
case 0:
|
|
switch(d.setup_state) {
|
|
case SETUP_STATE_ACK:
|
|
if (!(d.setup_buf[0] & USB_DIR_IN)) {
|
|
d.setup_state = SETUP_STATE_IDLE;
|
|
ret = handle_control((d.setup_buf[0] << 8) | d.setup_buf[1],
|
|
(d.setup_buf[3] << 8) | d.setup_buf[2],
|
|
(d.setup_buf[5] << 8) | d.setup_buf[4],
|
|
d.setup_len, d.data_buf);
|
|
if (ret > 0)
|
|
ret = 0;
|
|
} else {
|
|
// return 0 byte
|
|
}
|
|
break;
|
|
case SETUP_STATE_DATA:
|
|
if (d.setup_buf[0] & USB_DIR_IN) {
|
|
l = d.setup_len - d.setup_index;
|
|
if (l > len)
|
|
l = len;
|
|
memcpy(data, d.data_buf + d.setup_index, l);
|
|
d.setup_index += l;
|
|
if (d.setup_index >= d.setup_len)
|
|
d.setup_state = SETUP_STATE_ACK;
|
|
ret = l;
|
|
} else {
|
|
d.setup_state = SETUP_STATE_IDLE;
|
|
goto fail;
|
|
}
|
|
break;
|
|
default:
|
|
goto fail;
|
|
}
|
|
break;
|
|
default:
|
|
ret = handle_data(p);
|
|
break;
|
|
}
|
|
break;
|
|
case USB_TOKEN_OUT:
|
|
if (d.state < USB_STATE_DEFAULT || p->devaddr != d.addr)
|
|
return USB_RET_NODEV;
|
|
if (d.stall) goto fail;
|
|
switch(p->devep) {
|
|
case 0:
|
|
switch(d.setup_state) {
|
|
case SETUP_STATE_ACK:
|
|
if (d.setup_buf[0] & USB_DIR_IN) {
|
|
d.setup_state = SETUP_STATE_IDLE;
|
|
// transfer OK
|
|
} else {
|
|
// ignore additionnal output
|
|
}
|
|
break;
|
|
case SETUP_STATE_DATA:
|
|
if (!(d.setup_buf[0] & USB_DIR_IN)) {
|
|
l = d.setup_len - d.setup_index;
|
|
if (l > len)
|
|
l = len;
|
|
memcpy(d.data_buf + d.setup_index, data, l);
|
|
d.setup_index += l;
|
|
if (d.setup_index >= d.setup_len)
|
|
d.setup_state = SETUP_STATE_ACK;
|
|
ret = l;
|
|
} else {
|
|
// it is okay for a host to send an OUT before it reads
|
|
// all of the expected IN. It is telling the controller
|
|
// that it doesn't want any more from that particular call.
|
|
ret = 0;
|
|
d.setup_state = SETUP_STATE_IDLE;
|
|
}
|
|
break;
|
|
default:
|
|
goto fail;
|
|
}
|
|
break;
|
|
default:
|
|
ret = handle_data(p);
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
fail:
|
|
d.stall = 1;
|
|
ret = USB_RET_STALL;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int usb_device_c::handle_control_common(int request, int value, int index, int length, Bit8u *data)
|
|
{
|
|
int ret = -1;
|
|
|
|
switch (request) {
|
|
case DeviceOutRequest | USB_REQ_SET_ADDRESS:
|
|
BX_DEBUG(("USB_REQ_SET_ADDRESS:"));
|
|
d.state = USB_STATE_ADDRESS;
|
|
d.addr = value;
|
|
ret = 0;
|
|
break;
|
|
case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
|
|
switch (value >> 8) {
|
|
case USB_DT_DEVICE:
|
|
BX_DEBUG(("USB_REQ_GET_DESCRIPTOR: Device"));
|
|
memcpy(data, d.dev_descriptor, d.device_desc_size);
|
|
ret = d.device_desc_size;
|
|
break;
|
|
case USB_DT_CONFIG:
|
|
BX_DEBUG(("USB_REQ_GET_DESCRIPTOR: Config"));
|
|
memcpy(data, d.config_descriptor, d.config_desc_size);
|
|
ret = d.config_desc_size;
|
|
break;
|
|
case USB_DT_STRING:
|
|
BX_DEBUG(("USB_REQ_GET_DESCRIPTOR: String"));
|
|
switch(value & 0xff) {
|
|
case 0:
|
|
// language IDs
|
|
data[0] = 4;
|
|
data[1] = 3;
|
|
data[2] = 0x09;
|
|
data[3] = 0x04;
|
|
ret = 4;
|
|
break;
|
|
case 1:
|
|
// vendor description
|
|
ret = set_usb_string(data, d.vendor_desc);
|
|
break;
|
|
case 2:
|
|
// product description
|
|
ret = set_usb_string(data, d.product_desc);
|
|
break;
|
|
case 3:
|
|
// serial number
|
|
ret = set_usb_string(data, d.serial_num);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case DeviceRequest | USB_REQ_GET_STATUS:
|
|
BX_DEBUG(("USB_REQ_GET_STATUS:"));
|
|
data[0] = 0x00;
|
|
if (d.config_descriptor[7] & 0x40) {
|
|
data[0] |= (1 << USB_DEVICE_SELF_POWERED);
|
|
}
|
|
if (d.remote_wakeup) {
|
|
data[0] |= (1 << USB_DEVICE_REMOTE_WAKEUP);
|
|
}
|
|
data[1] = 0x00;
|
|
ret = 2;
|
|
break;
|
|
case DeviceRequest | USB_REQ_GET_CONFIGURATION:
|
|
BX_DEBUG(("USB_REQ_GET_CONFIGURATION:"));
|
|
data[0] = d.config;
|
|
ret = 1;
|
|
break;
|
|
case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
|
|
BX_DEBUG(("USB_REQ_SET_CONFIGURATION: value=%d", value));
|
|
d.config = value;
|
|
d.state = USB_STATE_CONFIGURED;
|
|
ret = 0;
|
|
break;
|
|
case DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
|
|
if (value == USB_DEVICE_REMOTE_WAKEUP) {
|
|
d.remote_wakeup = 0;
|
|
ret = 0;
|
|
}
|
|
break;
|
|
case DeviceOutRequest | USB_REQ_SET_FEATURE:
|
|
if (value == USB_DEVICE_REMOTE_WAKEUP) {
|
|
d.remote_wakeup = 1;
|
|
ret = 0;
|
|
}
|
|
break;
|
|
case InterfaceRequest | USB_REQ_GET_INTERFACE:
|
|
BX_DEBUG(("USB_REQ_GET_INTERFACE:"));
|
|
data[0] = d.iface;
|
|
ret = 1;
|
|
break;
|
|
case InterfaceOutRequest | USB_REQ_SET_INTERFACE:
|
|
BX_DEBUG(("USB_REQ_SET_INTERFACE: value=%d", value));
|
|
d.iface = value;
|
|
ret = 0;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void usb_device_c::register_state(bx_list_c *parent)
|
|
{
|
|
d.sr = parent;
|
|
bx_list_c *list = new bx_list_c(parent, "d", "Common USB Device State");
|
|
BXRS_DEC_PARAM_FIELD(list, addr, d.addr);
|
|
BXRS_DEC_PARAM_FIELD(list, config, d.config);
|
|
BXRS_DEC_PARAM_FIELD(list, interface, d.iface);
|
|
BXRS_DEC_PARAM_FIELD(list, state, d.state);
|
|
BXRS_DEC_PARAM_FIELD(list, remote_wakeup, d.remote_wakeup);
|
|
register_state_specific(parent);
|
|
}
|
|
|
|
// Turn on BX_DEBUG messages at connection time
|
|
void usb_device_c::set_debug_mode()
|
|
{
|
|
setonoff(LOGLEV_DEBUG, ACT_REPORT);
|
|
}
|
|
|
|
// Send an internal message to a USB device
|
|
void usb_device_c::usb_send_msg(int msg)
|
|
{
|
|
USBPacket p;
|
|
memset(&p, 0, sizeof(p));
|
|
p.pid = msg;
|
|
handle_packet(&p);
|
|
}
|
|
|
|
// Dumps the contents of a buffer to the log file
|
|
void usb_device_c::usb_dump_packet(Bit8u *data, unsigned size)
|
|
{
|
|
char buf_str[1025], temp_str[17];
|
|
|
|
if (getonoff(LOGLEV_DEBUG) == ACT_REPORT) {
|
|
BX_DEBUG(("packet hexdump (%i bytes)", size));
|
|
buf_str[0] = 0;
|
|
for (unsigned i = 0; i < size; i++) {
|
|
sprintf(temp_str, "%02X ", data[i]);
|
|
strcat(buf_str, temp_str);
|
|
if ((i % 16) == 15) {
|
|
BX_DEBUG(("%s", buf_str));
|
|
buf_str[0] = 0;
|
|
}
|
|
}
|
|
if (strlen(buf_str) > 0) BX_DEBUG(("%s", buf_str));
|
|
}
|
|
}
|
|
|
|
int usb_device_c::set_usb_string(Bit8u *buf, const char *str)
|
|
{
|
|
size_t len, i;
|
|
Bit8u *q;
|
|
|
|
q = buf;
|
|
len = strlen(str);
|
|
if (len > 32) {
|
|
*q = 0;
|
|
return 0;
|
|
}
|
|
*q++ = (Bit8u)(2 * len + 2);
|
|
*q++ = 3;
|
|
for(i = 0; i < len; i++) {
|
|
*q++ = str[i];
|
|
*q++ = 0;
|
|
}
|
|
return q - buf;
|
|
}
|
|
|
|
#endif // BX_SUPPORT_PCI && BX_SUPPORT_PCIUSB
|