mirror of https://gitee.com/openkylin/qemu.git
* Removal of the deprecated bluetooth code
* Some qtest and misc patches -----BEGIN PGP SIGNATURE----- iQJFBAABCAAvFiEEJ7iIR+7gJQEY8+q5LtnXdP5wLbUFAl34jSQRHHRodXRoQHJl ZGhhdC5jb20ACgkQLtnXdP5wLbWq4A//XOn32ePftcnAwCM/1GSdyS8Olpn8mGdF g7oA94PVGMS5G8RQLhdC7i/TXhjs7B/HdSL9b6XShcqDI5zRg3sZp1ZIy1rPXZ++ X9fIKETh2HIuwMaUJ/Tj93zPs0bnTrs2V0NADBw5tvXXRrdl5V/dKq3d89NRRQjx a4VSM39HkME2gGqb5zmlQ/ROaJx4UMplqIgT3UUsUcAlmKs+YJtU4nmkgmgP7kby sYNT5P3T39BWA5D9PAaDHyh7gG9xeraMhwe96A/zalvTACZ+dAYK65jVv4aGg1kQ 6av3mmxLbkOdql5y13ihfcWn4Y/V9i8ccrgVZ1gDFVaCNyZwoNBPMc05dC2dgbkQ jvgZejvbCryIcSfKPdjD92LI9pGmfHiMUf0cehXAtVcBWvCXbcgXGHN1ophz2rD4 w6JhzcXLCbTZHGxTmJNVSblCzbOmGFwLlRONWQ7/2AD5cItQmbE0hgyFjtmhmYxq f2bnvYi8vK9dOKkQQ4vNyQNTDCnksFCx4+d9trwyCYeVSPOgK9daehybLMkUzntD GoBpFVrpb3c5mLRkOYf9U/poxtqutFJYX/i/+miF+pJKaDizQxI+Qcs4m1qEAPiw bMn8+JWsjM+/Uo4JKPU4Il8QfHgNsGE6yZrSqu00CO0zvJ0B83aQV8DLXo/4L42s YDeZR0JNlGk= =tcei -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/huth-gitlab/tags/pull-request-2019-12-17' into staging * Removal of the deprecated bluetooth code * Some qtest and misc patches # gpg: Signature made Tue 17 Dec 2019 08:09:08 GMT # gpg: using RSA key 27B88847EEE0250118F3EAB92ED9D774FE702DB5 # gpg: issuer "thuth@redhat.com" # gpg: Good signature from "Thomas Huth <th.huth@gmx.de>" [full] # gpg: aka "Thomas Huth <thuth@redhat.com>" [full] # gpg: aka "Thomas Huth <huth@tuxfamily.org>" [full] # gpg: aka "Thomas Huth <th.huth@posteo.de>" [unknown] # Primary key fingerprint: 27B8 8847 EEE0 2501 18F3 EAB9 2ED9 D774 FE70 2DB5 * remotes/huth-gitlab/tags/pull-request-2019-12-17: tests: use g_test_rand_int tests/Makefile: Fix check-report.* targets shown in check-help glib: use portable g_setenv() hw/misc/ivshmem: Bury dead legacy INTx code pseries: disable migration-test if /dev/kvm cannot be used tests: fix modules-test 'duplicate test case' error Remove libbluetooth / bluez from the CI tests Remove the core bluetooth code hw/usb: Remove the USB bluetooth dongle device hw/arm/nseries: Replace the bluetooth chardev with a "null" chardev Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
aceeaa69d2
|
@ -36,7 +36,7 @@ build-disabled:
|
|||
|
||||
build-tcg-disabled:
|
||||
script:
|
||||
- apt-get install -y -qq clang libgtk-3-dev libbluetooth-dev libusb-dev
|
||||
- apt-get install -y -qq clang libgtk-3-dev libusb-dev
|
||||
- ./configure --cc=clang --enable-werror --disable-tcg --audio-drv-list=""
|
||||
- make -j2
|
||||
- make check-unit
|
||||
|
|
|
@ -65,8 +65,6 @@ common-obj-y += replay/
|
|||
|
||||
common-obj-y += ui/
|
||||
common-obj-m += ui/
|
||||
common-obj-y += bt-host.o bt-vhci.o
|
||||
bt-host.o-cflags := $(BLUEZ_CFLAGS)
|
||||
|
||||
common-obj-y += dma-helpers.o
|
||||
common-obj-y += vl.o
|
||||
|
|
198
bt-host.c
198
bt-host.c
|
@ -1,198 +0,0 @@
|
|||
/*
|
||||
* Wrap a host Bluetooth HCI socket in a struct HCIInfo.
|
||||
*
|
||||
* Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 or
|
||||
* (at your option) version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu-common.h"
|
||||
#include "sysemu/bt.h"
|
||||
#include "qemu/main-loop.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <sys/ioctl.h>
|
||||
# include <sys/uio.h>
|
||||
# ifdef CONFIG_BLUEZ
|
||||
# include <bluetooth/bluetooth.h>
|
||||
# include <bluetooth/hci.h>
|
||||
# include <bluetooth/hci_lib.h>
|
||||
# else
|
||||
# include "hw/bt.h"
|
||||
# define HCI_MAX_FRAME_SIZE 1028
|
||||
# endif
|
||||
|
||||
struct bt_host_hci_s {
|
||||
struct HCIInfo hci;
|
||||
int fd;
|
||||
|
||||
uint8_t hdr[HCI_MAX_FRAME_SIZE];
|
||||
int len;
|
||||
};
|
||||
|
||||
static void bt_host_send(struct HCIInfo *hci,
|
||||
int type, const uint8_t *data, int len)
|
||||
{
|
||||
struct bt_host_hci_s *s = (struct bt_host_hci_s *) hci;
|
||||
uint8_t pkt = type;
|
||||
struct iovec iv[2];
|
||||
|
||||
iv[0].iov_base = (void *)&pkt;
|
||||
iv[0].iov_len = 1;
|
||||
iv[1].iov_base = (void *) data;
|
||||
iv[1].iov_len = len;
|
||||
|
||||
while (writev(s->fd, iv, 2) < 0) {
|
||||
if (errno != EAGAIN && errno != EINTR) {
|
||||
fprintf(stderr, "qemu: error %i writing bluetooth packet.\n",
|
||||
errno);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_host_cmd(struct HCIInfo *hci, const uint8_t *data, int len)
|
||||
{
|
||||
bt_host_send(hci, HCI_COMMAND_PKT, data, len);
|
||||
}
|
||||
|
||||
static void bt_host_acl(struct HCIInfo *hci, const uint8_t *data, int len)
|
||||
{
|
||||
bt_host_send(hci, HCI_ACLDATA_PKT, data, len);
|
||||
}
|
||||
|
||||
static void bt_host_sco(struct HCIInfo *hci, const uint8_t *data, int len)
|
||||
{
|
||||
bt_host_send(hci, HCI_SCODATA_PKT, data, len);
|
||||
}
|
||||
|
||||
static void bt_host_read(void *opaque)
|
||||
{
|
||||
struct bt_host_hci_s *s = (struct bt_host_hci_s *) opaque;
|
||||
uint8_t *pkt;
|
||||
int pktlen;
|
||||
|
||||
/* Seems that we can't read only the header first and then the amount
|
||||
* of data indicated in the header because Linux will discard everything
|
||||
* that's not been read in one go. */
|
||||
s->len = read(s->fd, s->hdr, sizeof(s->hdr));
|
||||
|
||||
if (s->len < 0) {
|
||||
fprintf(stderr, "qemu: error %i reading HCI frame\n", errno);
|
||||
return;
|
||||
}
|
||||
|
||||
pkt = s->hdr;
|
||||
while (s->len --)
|
||||
switch (*pkt ++) {
|
||||
case HCI_EVENT_PKT:
|
||||
if (s->len < 2)
|
||||
goto bad_pkt;
|
||||
|
||||
pktlen = MIN(pkt[1] + 2, s->len);
|
||||
s->hci.evt_recv(s->hci.opaque, pkt, pktlen);
|
||||
s->len -= pktlen;
|
||||
pkt += pktlen;
|
||||
|
||||
/* TODO: if this is an Inquiry Result event, it's also
|
||||
* interpreted by Linux kernel before we received it, possibly
|
||||
* we should clean the kernel Inquiry cache through
|
||||
* ioctl(s->fd, HCI_INQUIRY, ...). */
|
||||
break;
|
||||
|
||||
case HCI_ACLDATA_PKT:
|
||||
if (s->len < 4)
|
||||
goto bad_pkt;
|
||||
|
||||
pktlen = MIN(((pkt[3] << 8) | pkt[2]) + 4, s->len);
|
||||
s->hci.acl_recv(s->hci.opaque, pkt, pktlen);
|
||||
s->len -= pktlen;
|
||||
pkt += pktlen;
|
||||
break;
|
||||
|
||||
case HCI_SCODATA_PKT:
|
||||
if (s->len < 3)
|
||||
goto bad_pkt;
|
||||
|
||||
pktlen = MIN(pkt[2] + 3, s->len);
|
||||
s->len -= pktlen;
|
||||
pkt += pktlen;
|
||||
break;
|
||||
|
||||
default:
|
||||
bad_pkt:
|
||||
fprintf(stderr, "qemu: bad HCI packet type %02x\n", pkt[-1]);
|
||||
}
|
||||
}
|
||||
|
||||
static int bt_host_bdaddr_set(struct HCIInfo *hci, const uint8_t *bd_addr)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
struct HCIInfo *bt_host_hci(const char *id)
|
||||
{
|
||||
struct bt_host_hci_s *s;
|
||||
int fd = -1;
|
||||
# ifdef CONFIG_BLUEZ
|
||||
int dev_id = hci_devid(id);
|
||||
struct hci_filter flt;
|
||||
|
||||
if (dev_id < 0) {
|
||||
fprintf(stderr, "qemu: `%s' not available\n", id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fd = hci_open_dev(dev_id);
|
||||
|
||||
/* XXX: can we ensure nobody else has the device opened? */
|
||||
# endif
|
||||
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "qemu: Can't open `%s': %s (%i)\n",
|
||||
id, strerror(errno), errno);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
# ifdef CONFIG_BLUEZ
|
||||
hci_filter_clear(&flt);
|
||||
hci_filter_all_ptypes(&flt);
|
||||
hci_filter_all_events(&flt);
|
||||
|
||||
if (qemu_setsockopt(fd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) {
|
||||
fprintf(stderr, "qemu: Can't set HCI filter on socket (%i)\n", errno);
|
||||
return 0;
|
||||
}
|
||||
# endif
|
||||
|
||||
s = g_malloc0(sizeof(struct bt_host_hci_s));
|
||||
s->fd = fd;
|
||||
s->hci.cmd_send = bt_host_cmd;
|
||||
s->hci.sco_send = bt_host_sco;
|
||||
s->hci.acl_send = bt_host_acl;
|
||||
s->hci.bdaddr_set = bt_host_bdaddr_set;
|
||||
|
||||
qemu_set_fd_handler(s->fd, bt_host_read, NULL, s);
|
||||
|
||||
return &s->hci;
|
||||
}
|
||||
#else
|
||||
struct HCIInfo *bt_host_hci(const char *id)
|
||||
{
|
||||
fprintf(stderr, "qemu: bluetooth passthrough not supported (yet)\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
167
bt-vhci.c
167
bt-vhci.c
|
@ -1,167 +0,0 @@
|
|||
/*
|
||||
* Support for host VHCIs inside qemu scatternets.
|
||||
*
|
||||
* Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 or
|
||||
* (at your option) version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "sysemu/bt.h"
|
||||
#include "hw/bt.h"
|
||||
#include "qemu/main-loop.h"
|
||||
|
||||
#define VHCI_DEV "/dev/vhci"
|
||||
#define VHCI_UDEV "/dev/hci_vhci"
|
||||
|
||||
struct bt_vhci_s {
|
||||
int fd;
|
||||
struct HCIInfo *info;
|
||||
|
||||
uint8_t hdr[4096];
|
||||
int len;
|
||||
};
|
||||
|
||||
static void vhci_read(void *opaque)
|
||||
{
|
||||
struct bt_vhci_s *s = (struct bt_vhci_s *) opaque;
|
||||
uint8_t *pkt;
|
||||
int pktlen;
|
||||
|
||||
/* Seems that we can't read only the header first and then the amount
|
||||
* of data indicated in the header because Linux will discard everything
|
||||
* that's not been read in one go. */
|
||||
s->len = read(s->fd, s->hdr, sizeof(s->hdr));
|
||||
|
||||
if (s->len < 0) {
|
||||
fprintf(stderr, "qemu: error %i reading the PDU\n", errno);
|
||||
return;
|
||||
}
|
||||
|
||||
pkt = s->hdr;
|
||||
while (s->len --)
|
||||
switch (*pkt ++) {
|
||||
case HCI_COMMAND_PKT:
|
||||
if (s->len < 3)
|
||||
goto bad_pkt;
|
||||
|
||||
pktlen = MIN(pkt[2] + 3, s->len);
|
||||
s->info->cmd_send(s->info, pkt, pktlen);
|
||||
s->len -= pktlen;
|
||||
pkt += pktlen;
|
||||
break;
|
||||
|
||||
case HCI_ACLDATA_PKT:
|
||||
if (s->len < 4)
|
||||
goto bad_pkt;
|
||||
|
||||
pktlen = MIN(((pkt[3] << 8) | pkt[2]) + 4, s->len);
|
||||
s->info->acl_send(s->info, pkt, pktlen);
|
||||
s->len -= pktlen;
|
||||
pkt += pktlen;
|
||||
break;
|
||||
|
||||
case HCI_SCODATA_PKT:
|
||||
if (s->len < 3)
|
||||
goto bad_pkt;
|
||||
|
||||
pktlen = MIN(pkt[2] + 3, s->len);
|
||||
s->info->sco_send(s->info, pkt, pktlen);
|
||||
s->len -= pktlen;
|
||||
pkt += pktlen;
|
||||
break;
|
||||
|
||||
default:
|
||||
bad_pkt:
|
||||
fprintf(stderr, "qemu: bad HCI packet type %02x\n", pkt[-1]);
|
||||
}
|
||||
}
|
||||
|
||||
static void vhci_host_send(void *opaque,
|
||||
int type, const uint8_t *data, int len)
|
||||
{
|
||||
struct bt_vhci_s *s = (struct bt_vhci_s *) opaque;
|
||||
#if 0
|
||||
uint8_t pkt = type;
|
||||
struct iovec iv[2];
|
||||
|
||||
iv[0].iov_base = &pkt;
|
||||
iv[0].iov_len = 1;
|
||||
iv[1].iov_base = (void *) data;
|
||||
iv[1].iov_len = len;
|
||||
|
||||
while (writev(s->fd, iv, 2) < 0)
|
||||
if (errno != EAGAIN && errno != EINTR) {
|
||||
fprintf(stderr, "qemu: error %i writing bluetooth packet.\n",
|
||||
errno);
|
||||
return;
|
||||
}
|
||||
#else
|
||||
/* Apparently VHCI wants us to write everything in one chunk :-( */
|
||||
static uint8_t buf[4096];
|
||||
|
||||
buf[0] = type;
|
||||
memcpy(buf + 1, data, len);
|
||||
|
||||
while (write(s->fd, buf, len + 1) < 0)
|
||||
if (errno != EAGAIN && errno != EINTR) {
|
||||
fprintf(stderr, "qemu: error %i writing bluetooth packet.\n",
|
||||
errno);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void vhci_out_hci_packet_event(void *opaque,
|
||||
const uint8_t *data, int len)
|
||||
{
|
||||
vhci_host_send(opaque, HCI_EVENT_PKT, data, len);
|
||||
}
|
||||
|
||||
static void vhci_out_hci_packet_acl(void *opaque,
|
||||
const uint8_t *data, int len)
|
||||
{
|
||||
vhci_host_send(opaque, HCI_ACLDATA_PKT, data, len);
|
||||
}
|
||||
|
||||
void bt_vhci_init(struct HCIInfo *info)
|
||||
{
|
||||
struct bt_vhci_s *s;
|
||||
int err[2];
|
||||
int fd;
|
||||
|
||||
fd = open(VHCI_DEV, O_RDWR);
|
||||
err[0] = errno;
|
||||
if (fd < 0) {
|
||||
fd = open(VHCI_UDEV, O_RDWR);
|
||||
err[1] = errno;
|
||||
}
|
||||
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "qemu: Can't open `%s': %s (%i)\n",
|
||||
VHCI_DEV, strerror(err[0]), err[0]);
|
||||
fprintf(stderr, "qemu: Can't open `%s': %s (%i)\n",
|
||||
VHCI_UDEV, strerror(err[1]), err[1]);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
s = g_malloc0(sizeof(struct bt_vhci_s));
|
||||
s->fd = fd;
|
||||
s->info = info ?: qemu_next_hci();
|
||||
s->info->opaque = s;
|
||||
s->info->evt_recv = vhci_out_hci_packet_event;
|
||||
s->info->acl_recv = vhci_out_hci_packet_acl;
|
||||
|
||||
qemu_set_fd_handler(s->fd, vhci_read, NULL, s);
|
||||
}
|
|
@ -349,7 +349,6 @@ unset target_list_exclude
|
|||
# Distributions want to ensure that several features are compiled in, and it
|
||||
# is impossible without a --enable-foo that exits if a feature is not found.
|
||||
|
||||
bluez=""
|
||||
brlapi=""
|
||||
curl=""
|
||||
curses=""
|
||||
|
@ -1151,10 +1150,6 @@ for opt do
|
|||
;;
|
||||
--enable-brlapi) brlapi="yes"
|
||||
;;
|
||||
--disable-bluez) bluez="no"
|
||||
;;
|
||||
--enable-bluez) bluez="yes"
|
||||
;;
|
||||
--disable-kvm) kvm="no"
|
||||
;;
|
||||
--enable-kvm) kvm="yes"
|
||||
|
@ -1762,7 +1757,6 @@ disabled with --disable-FEATURE, default is enabled if available:
|
|||
curl curl connectivity
|
||||
membarrier membarrier system call (for Linux 4.14+ or Windows)
|
||||
fdt fdt device tree
|
||||
bluez bluez stack connectivity
|
||||
kvm KVM acceleration support
|
||||
hax HAX acceleration support
|
||||
hvf Hypervisor.framework acceleration support
|
||||
|
@ -3665,26 +3659,6 @@ EOF
|
|||
fi
|
||||
fi # test "$curl"
|
||||
|
||||
##########################################
|
||||
# bluez support probe
|
||||
if test "$bluez" != "no" ; then
|
||||
cat > $TMPC << EOF
|
||||
#include <bluetooth/bluetooth.h>
|
||||
int main(void) { return bt_error(0); }
|
||||
EOF
|
||||
bluez_cflags=$($pkg_config --cflags bluez 2>/dev/null)
|
||||
bluez_libs=$($pkg_config --libs bluez 2>/dev/null)
|
||||
if compile_prog "$bluez_cflags" "$bluez_libs" ; then
|
||||
bluez=yes
|
||||
libs_softmmu="$bluez_libs $libs_softmmu"
|
||||
else
|
||||
if test "$bluez" = "yes" ; then
|
||||
feature_not_found "bluez" "Install bluez-libs/libbluetooth devel"
|
||||
fi
|
||||
bluez="no"
|
||||
fi
|
||||
fi
|
||||
|
||||
##########################################
|
||||
# glib support probe
|
||||
|
||||
|
@ -6493,7 +6467,6 @@ if test "$xen" = "yes" ; then
|
|||
echo "xen ctrl version $xen_ctrl_version"
|
||||
fi
|
||||
echo "brlapi support $brlapi"
|
||||
echo "bluez support $bluez"
|
||||
echo "Documentation $docs"
|
||||
echo "PIE $pie"
|
||||
echo "vde support $vde"
|
||||
|
@ -6917,10 +6890,6 @@ if test "$brlapi" = "yes" ; then
|
|||
echo "CONFIG_BRLAPI=y" >> $config_host_mak
|
||||
echo "BRLAPI_LIBS=$brlapi_libs" >> $config_host_mak
|
||||
fi
|
||||
if test "$bluez" = "yes" ; then
|
||||
echo "CONFIG_BLUEZ=y" >> $config_host_mak
|
||||
echo "BLUEZ_CFLAGS=$bluez_cflags" >> $config_host_mak
|
||||
fi
|
||||
if test "$gtk" = "yes" ; then
|
||||
echo "CONFIG_GTK=m" >> $config_host_mak
|
||||
echo "GTK_CFLAGS=$gtk_cflags" >> $config_host_mak
|
||||
|
|
|
@ -4,7 +4,6 @@ source acpi/Kconfig
|
|||
source adc/Kconfig
|
||||
source audio/Kconfig
|
||||
source block/Kconfig
|
||||
source bt/Kconfig
|
||||
source char/Kconfig
|
||||
source core/Kconfig
|
||||
source display/Kconfig
|
||||
|
|
|
@ -5,7 +5,6 @@ devices-dirs-y += acpi/
|
|||
devices-dirs-y += adc/
|
||||
devices-dirs-y += audio/
|
||||
devices-dirs-y += block/
|
||||
devices-dirs-y += bt/
|
||||
devices-dirs-y += char/
|
||||
devices-dirs-y += cpu/
|
||||
devices-dirs-y += display/
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "cpu.h"
|
||||
#include "chardev/char.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qemu/bswap.h"
|
||||
#include "sysemu/reset.h"
|
||||
|
@ -39,7 +40,6 @@
|
|||
#include "hw/qdev-properties.h"
|
||||
#include "hw/block/flash.h"
|
||||
#include "hw/hw.h"
|
||||
#include "hw/bt.h"
|
||||
#include "hw/loader.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "qemu/log.h"
|
||||
|
@ -792,13 +792,11 @@ static void n8x0_cbus_setup(struct n800_s *s)
|
|||
|
||||
static void n8x0_uart_setup(struct n800_s *s)
|
||||
{
|
||||
Chardev *radio = uart_hci_init();
|
||||
|
||||
qdev_connect_gpio_out(s->mpu->gpio, N8X0_BT_RESET_GPIO,
|
||||
csrhci_pins_get(radio)[csrhci_pin_reset]);
|
||||
qdev_connect_gpio_out(s->mpu->gpio, N8X0_BT_WKUP_GPIO,
|
||||
csrhci_pins_get(radio)[csrhci_pin_wakeup]);
|
||||
|
||||
Chardev *radio = qemu_chr_new("bt-dummy-uart", "null", NULL);
|
||||
/*
|
||||
* Note: We used to connect N8X0_BT_RESET_GPIO and N8X0_BT_WKUP_GPIO
|
||||
* here, but this code has been removed with the bluetooth backend.
|
||||
*/
|
||||
omap_uart_attach(s->mpu->uart[BT_UART], radio);
|
||||
}
|
||||
|
||||
|
@ -1137,7 +1135,7 @@ static struct omap_partition_info_s {
|
|||
{ 0, 0, 0, NULL }
|
||||
};
|
||||
|
||||
static bdaddr_t n8x0_bd_addr = {{ N8X0_BD_ADDR }};
|
||||
static uint8_t n8x0_bd_addr[6] = { N8X0_BD_ADDR };
|
||||
|
||||
static int n8x0_atag_setup(void *p, int model)
|
||||
{
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
config BLUETOOTH
|
||||
bool
|
|
@ -1,3 +0,0 @@
|
|||
common-obj-y += core.o l2cap.o sdp.o hci.o hid.o
|
||||
common-obj-y += hci-csr.o
|
||||
|
143
hw/bt/core.c
143
hw/bt/core.c
|
@ -1,143 +0,0 @@
|
|||
/*
|
||||
* Convenience functions for bluetooth.
|
||||
*
|
||||
* Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 or
|
||||
* (at your option) version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "sysemu/bt.h"
|
||||
#include "hw/bt.h"
|
||||
|
||||
/* Slave implementations can ignore this */
|
||||
static void bt_dummy_lmp_mode_change(struct bt_link_s *link)
|
||||
{
|
||||
}
|
||||
|
||||
/* Slaves should never receive these PDUs */
|
||||
static void bt_dummy_lmp_connection_complete(struct bt_link_s *link)
|
||||
{
|
||||
if (link->slave->reject_reason)
|
||||
error_report("%s: stray LMP_not_accepted received, fixme", __func__);
|
||||
else
|
||||
error_report("%s: stray LMP_accepted received, fixme", __func__);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
static void bt_dummy_lmp_disconnect_master(struct bt_link_s *link)
|
||||
{
|
||||
error_report("%s: stray LMP_detach received, fixme", __func__);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
static void bt_dummy_lmp_acl_resp(struct bt_link_s *link,
|
||||
const uint8_t *data, int start, int len)
|
||||
{
|
||||
error_report("%s: stray ACL response PDU, fixme", __func__);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/* Slaves that don't hold any additional per link state can use these */
|
||||
static void bt_dummy_lmp_connection_request(struct bt_link_s *req)
|
||||
{
|
||||
struct bt_link_s *link = g_malloc0(sizeof(struct bt_link_s));
|
||||
|
||||
link->slave = req->slave;
|
||||
link->host = req->host;
|
||||
|
||||
req->host->reject_reason = 0;
|
||||
req->host->lmp_connection_complete(link);
|
||||
}
|
||||
|
||||
static void bt_dummy_lmp_disconnect_slave(struct bt_link_s *link)
|
||||
{
|
||||
g_free(link);
|
||||
}
|
||||
|
||||
static void bt_dummy_destroy(struct bt_device_s *device)
|
||||
{
|
||||
bt_device_done(device);
|
||||
g_free(device);
|
||||
}
|
||||
|
||||
static int bt_dev_idx = 0;
|
||||
|
||||
void bt_device_init(struct bt_device_s *dev, struct bt_scatternet_s *net)
|
||||
{
|
||||
memset(dev, 0, sizeof(*dev));
|
||||
dev->inquiry_scan = 1;
|
||||
dev->page_scan = 1;
|
||||
|
||||
dev->bd_addr.b[0] = bt_dev_idx & 0xff;
|
||||
dev->bd_addr.b[1] = bt_dev_idx >> 8;
|
||||
dev->bd_addr.b[2] = 0xd0;
|
||||
dev->bd_addr.b[3] = 0xba;
|
||||
dev->bd_addr.b[4] = 0xbe;
|
||||
dev->bd_addr.b[5] = 0xba;
|
||||
bt_dev_idx ++;
|
||||
|
||||
/* Simple slave-only devices need to implement only .lmp_acl_data */
|
||||
dev->lmp_connection_complete = bt_dummy_lmp_connection_complete;
|
||||
dev->lmp_disconnect_master = bt_dummy_lmp_disconnect_master;
|
||||
dev->lmp_acl_resp = bt_dummy_lmp_acl_resp;
|
||||
dev->lmp_mode_change = bt_dummy_lmp_mode_change;
|
||||
dev->lmp_connection_request = bt_dummy_lmp_connection_request;
|
||||
dev->lmp_disconnect_slave = bt_dummy_lmp_disconnect_slave;
|
||||
|
||||
dev->handle_destroy = bt_dummy_destroy;
|
||||
|
||||
dev->net = net;
|
||||
dev->next = net->slave;
|
||||
net->slave = dev;
|
||||
}
|
||||
|
||||
void bt_device_done(struct bt_device_s *dev)
|
||||
{
|
||||
struct bt_device_s **p = &dev->net->slave;
|
||||
|
||||
while (*p && *p != dev)
|
||||
p = &(*p)->next;
|
||||
if (*p != dev) {
|
||||
error_report("%s: bad bt device \"%s\"", __func__,
|
||||
dev->lmp_name ?: "(null)");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
*p = dev->next;
|
||||
}
|
||||
|
||||
static struct bt_vlan_s {
|
||||
struct bt_scatternet_s net;
|
||||
int id;
|
||||
struct bt_vlan_s *next;
|
||||
} *first_bt_vlan;
|
||||
|
||||
/* find or alloc a new bluetooth "VLAN" */
|
||||
struct bt_scatternet_s *qemu_find_bt_vlan(int id)
|
||||
{
|
||||
struct bt_vlan_s **pvlan, *vlan;
|
||||
for (vlan = first_bt_vlan; vlan != NULL; vlan = vlan->next) {
|
||||
if (vlan->id == id)
|
||||
return &vlan->net;
|
||||
}
|
||||
vlan = g_malloc0(sizeof(struct bt_vlan_s));
|
||||
vlan->id = id;
|
||||
pvlan = &first_bt_vlan;
|
||||
while (*pvlan != NULL)
|
||||
pvlan = &(*pvlan)->next;
|
||||
*pvlan = vlan;
|
||||
return &vlan->net;
|
||||
}
|
512
hw/bt/hci-csr.c
512
hw/bt/hci-csr.c
|
@ -1,512 +0,0 @@
|
|||
/*
|
||||
* Bluetooth serial HCI transport.
|
||||
* CSR41814 HCI with H4p vendor extensions.
|
||||
*
|
||||
* Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 or
|
||||
* (at your option) version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/module.h"
|
||||
#include "chardev/char-serial.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "qemu/bswap.h"
|
||||
#include "hw/irq.h"
|
||||
#include "sysemu/bt.h"
|
||||
#include "hw/bt.h"
|
||||
#include "qapi/error.h"
|
||||
|
||||
struct csrhci_s {
|
||||
Chardev parent;
|
||||
int enable;
|
||||
qemu_irq *pins;
|
||||
int pin_state;
|
||||
int modem_state;
|
||||
#define FIFO_LEN 4096
|
||||
int out_start;
|
||||
int out_len;
|
||||
int out_size;
|
||||
uint8_t outfifo[FIFO_LEN * 2];
|
||||
uint8_t inpkt[FIFO_LEN];
|
||||
enum {
|
||||
CSR_HDR_LEN,
|
||||
CSR_DATA_LEN,
|
||||
CSR_DATA
|
||||
} in_state;
|
||||
int in_len;
|
||||
int in_hdr;
|
||||
int in_needed;
|
||||
QEMUTimer *out_tm;
|
||||
int64_t baud_delay;
|
||||
|
||||
bdaddr_t bd_addr;
|
||||
struct HCIInfo *hci;
|
||||
};
|
||||
|
||||
#define TYPE_CHARDEV_HCI "chardev-hci"
|
||||
#define HCI_CHARDEV(obj) OBJECT_CHECK(struct csrhci_s, (obj), TYPE_CHARDEV_HCI)
|
||||
|
||||
/* H4+ packet types */
|
||||
enum {
|
||||
H4_CMD_PKT = 1,
|
||||
H4_ACL_PKT = 2,
|
||||
H4_SCO_PKT = 3,
|
||||
H4_EVT_PKT = 4,
|
||||
H4_NEG_PKT = 6,
|
||||
H4_ALIVE_PKT = 7,
|
||||
};
|
||||
|
||||
/* CSR41814 negotiation start magic packet */
|
||||
static const uint8_t csrhci_neg_packet[] = {
|
||||
H4_NEG_PKT, 10,
|
||||
0x00, 0xa0, 0x01, 0x00, 0x00,
|
||||
0x4c, 0x00, 0x96, 0x00, 0x00,
|
||||
};
|
||||
|
||||
/* CSR41814 vendor-specific command OCFs */
|
||||
enum {
|
||||
OCF_CSR_SEND_FIRMWARE = 0x000,
|
||||
};
|
||||
|
||||
static inline void csrhci_fifo_wake(struct csrhci_s *s)
|
||||
{
|
||||
Chardev *chr = CHARDEV(s);
|
||||
|
||||
if (!s->enable || !s->out_len)
|
||||
return;
|
||||
|
||||
/* XXX: Should wait for s->modem_state & CHR_TIOCM_RTS? */
|
||||
if (qemu_chr_be_can_write(chr)) {
|
||||
qemu_chr_be_write(chr, s->outfifo + s->out_start++, 1);
|
||||
s->out_len--;
|
||||
if (s->out_start >= s->out_size) {
|
||||
s->out_start = 0;
|
||||
s->out_size = FIFO_LEN;
|
||||
}
|
||||
}
|
||||
|
||||
if (s->out_len)
|
||||
timer_mod(s->out_tm, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->baud_delay);
|
||||
}
|
||||
|
||||
#define csrhci_out_packetz(s, len) memset(csrhci_out_packet(s, len), 0, len)
|
||||
static uint8_t *csrhci_out_packet(struct csrhci_s *s, int len)
|
||||
{
|
||||
int off = s->out_start + s->out_len;
|
||||
|
||||
/* TODO: do the padding here, i.e. align len */
|
||||
s->out_len += len;
|
||||
|
||||
if (off < FIFO_LEN) {
|
||||
if (off + len > FIFO_LEN && (s->out_size = off + len) > FIFO_LEN * 2) {
|
||||
error_report("%s: can't alloc %i bytes", __func__, len);
|
||||
exit(-1);
|
||||
}
|
||||
return s->outfifo + off;
|
||||
}
|
||||
|
||||
if (s->out_len > s->out_size) {
|
||||
error_report("%s: can't alloc %i bytes", __func__, len);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
return s->outfifo + off - s->out_size;
|
||||
}
|
||||
|
||||
static inline uint8_t *csrhci_out_packet_csr(struct csrhci_s *s,
|
||||
int type, int len)
|
||||
{
|
||||
uint8_t *ret = csrhci_out_packetz(s, len + 2);
|
||||
|
||||
*ret ++ = type;
|
||||
*ret ++ = len;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline uint8_t *csrhci_out_packet_event(struct csrhci_s *s,
|
||||
int evt, int len)
|
||||
{
|
||||
uint8_t *ret = csrhci_out_packetz(s,
|
||||
len + 1 + sizeof(struct hci_event_hdr));
|
||||
|
||||
*ret ++ = H4_EVT_PKT;
|
||||
((struct hci_event_hdr *) ret)->evt = evt;
|
||||
((struct hci_event_hdr *) ret)->plen = len;
|
||||
|
||||
return ret + sizeof(struct hci_event_hdr);
|
||||
}
|
||||
|
||||
static void csrhci_in_packet_vendor(struct csrhci_s *s, int ocf,
|
||||
uint8_t *data, int len)
|
||||
{
|
||||
int offset;
|
||||
uint8_t *rpkt;
|
||||
|
||||
switch (ocf) {
|
||||
case OCF_CSR_SEND_FIRMWARE:
|
||||
/* Check if this is the bd_address packet */
|
||||
if (len >= 18 + 8 && data[12] == 0x01 && data[13] == 0x00) {
|
||||
offset = 18;
|
||||
s->bd_addr.b[0] = data[offset + 7]; /* Beyond cmd packet end(!?) */
|
||||
s->bd_addr.b[1] = data[offset + 6];
|
||||
s->bd_addr.b[2] = data[offset + 4];
|
||||
s->bd_addr.b[3] = data[offset + 0];
|
||||
s->bd_addr.b[4] = data[offset + 3];
|
||||
s->bd_addr.b[5] = data[offset + 2];
|
||||
|
||||
s->hci->bdaddr_set(s->hci, s->bd_addr.b);
|
||||
error_report("%s: bd_address loaded from firmware: "
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x", __func__,
|
||||
s->bd_addr.b[0], s->bd_addr.b[1], s->bd_addr.b[2],
|
||||
s->bd_addr.b[3], s->bd_addr.b[4], s->bd_addr.b[5]);
|
||||
}
|
||||
|
||||
rpkt = csrhci_out_packet_event(s, EVT_VENDOR, 11);
|
||||
/* Status bytes: no error */
|
||||
rpkt[9] = 0x00;
|
||||
rpkt[10] = 0x00;
|
||||
break;
|
||||
|
||||
default:
|
||||
error_report("%s: got a bad CMD packet", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
csrhci_fifo_wake(s);
|
||||
}
|
||||
|
||||
static void csrhci_in_packet(struct csrhci_s *s, uint8_t *pkt)
|
||||
{
|
||||
uint8_t *rpkt;
|
||||
int opc;
|
||||
|
||||
switch (*pkt ++) {
|
||||
case H4_CMD_PKT:
|
||||
opc = le16_to_cpu(((struct hci_command_hdr *) pkt)->opcode);
|
||||
if (cmd_opcode_ogf(opc) == OGF_VENDOR_CMD) {
|
||||
csrhci_in_packet_vendor(s, cmd_opcode_ocf(opc),
|
||||
pkt + sizeof(struct hci_command_hdr),
|
||||
s->in_len - sizeof(struct hci_command_hdr) - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
/* TODO: if the command is OCF_READ_LOCAL_COMMANDS or the likes,
|
||||
* we need to send it to the HCI layer and then add our supported
|
||||
* commands to the returned mask (such as OGF_VENDOR_CMD). With
|
||||
* bt-hci.c we could just have hooks for this kind of commands but
|
||||
* we can't with bt-host.c. */
|
||||
|
||||
s->hci->cmd_send(s->hci, pkt, s->in_len - 1);
|
||||
break;
|
||||
|
||||
case H4_EVT_PKT:
|
||||
goto bad_pkt;
|
||||
|
||||
case H4_ACL_PKT:
|
||||
s->hci->acl_send(s->hci, pkt, s->in_len - 1);
|
||||
break;
|
||||
|
||||
case H4_SCO_PKT:
|
||||
s->hci->sco_send(s->hci, pkt, s->in_len - 1);
|
||||
break;
|
||||
|
||||
case H4_NEG_PKT:
|
||||
if (s->in_hdr != sizeof(csrhci_neg_packet) ||
|
||||
memcmp(pkt - 1, csrhci_neg_packet, s->in_hdr)) {
|
||||
error_report("%s: got a bad NEG packet", __func__);
|
||||
return;
|
||||
}
|
||||
pkt += 2;
|
||||
|
||||
rpkt = csrhci_out_packet_csr(s, H4_NEG_PKT, 10);
|
||||
|
||||
*rpkt ++ = 0x20; /* Operational settings negotiation Ok */
|
||||
memcpy(rpkt, pkt, 7); rpkt += 7;
|
||||
*rpkt ++ = 0xff;
|
||||
*rpkt = 0xff;
|
||||
break;
|
||||
|
||||
case H4_ALIVE_PKT:
|
||||
if (s->in_hdr != 4 || pkt[1] != 0x55 || pkt[2] != 0x00) {
|
||||
error_report("%s: got a bad ALIVE packet", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
rpkt = csrhci_out_packet_csr(s, H4_ALIVE_PKT, 2);
|
||||
|
||||
*rpkt ++ = 0xcc;
|
||||
*rpkt = 0x00;
|
||||
break;
|
||||
|
||||
default:
|
||||
bad_pkt:
|
||||
/* TODO: error out */
|
||||
error_report("%s: got a bad packet", __func__);
|
||||
break;
|
||||
}
|
||||
|
||||
csrhci_fifo_wake(s);
|
||||
}
|
||||
|
||||
static int csrhci_header_len(const uint8_t *pkt)
|
||||
{
|
||||
switch (pkt[0]) {
|
||||
case H4_CMD_PKT:
|
||||
return HCI_COMMAND_HDR_SIZE;
|
||||
case H4_EVT_PKT:
|
||||
return HCI_EVENT_HDR_SIZE;
|
||||
case H4_ACL_PKT:
|
||||
return HCI_ACL_HDR_SIZE;
|
||||
case H4_SCO_PKT:
|
||||
return HCI_SCO_HDR_SIZE;
|
||||
case H4_NEG_PKT:
|
||||
return pkt[1] + 1;
|
||||
case H4_ALIVE_PKT:
|
||||
return 3;
|
||||
}
|
||||
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
static int csrhci_data_len(const uint8_t *pkt)
|
||||
{
|
||||
switch (*pkt ++) {
|
||||
case H4_CMD_PKT:
|
||||
/* It seems that vendor-specific command packets for H4+ are all
|
||||
* one byte longer than indicated in the standard header. */
|
||||
if (le16_to_cpu(((struct hci_command_hdr *) pkt)->opcode) == 0xfc00)
|
||||
return (((struct hci_command_hdr *) pkt)->plen + 1) & ~1;
|
||||
|
||||
return ((struct hci_command_hdr *) pkt)->plen;
|
||||
case H4_EVT_PKT:
|
||||
return ((struct hci_event_hdr *) pkt)->plen;
|
||||
case H4_ACL_PKT:
|
||||
return le16_to_cpu(((struct hci_acl_hdr *) pkt)->dlen);
|
||||
case H4_SCO_PKT:
|
||||
return ((struct hci_sco_hdr *) pkt)->dlen;
|
||||
case H4_NEG_PKT:
|
||||
case H4_ALIVE_PKT:
|
||||
return 0;
|
||||
}
|
||||
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
static void csrhci_ready_for_next_inpkt(struct csrhci_s *s)
|
||||
{
|
||||
s->in_state = CSR_HDR_LEN;
|
||||
s->in_len = 0;
|
||||
s->in_needed = 2;
|
||||
s->in_hdr = INT_MAX;
|
||||
}
|
||||
|
||||
static int csrhci_write(struct Chardev *chr,
|
||||
const uint8_t *buf, int len)
|
||||
{
|
||||
struct csrhci_s *s = (struct csrhci_s *)chr;
|
||||
int total = 0;
|
||||
|
||||
if (!s->enable)
|
||||
return 0;
|
||||
|
||||
for (;;) {
|
||||
int cnt = MIN(len, s->in_needed - s->in_len);
|
||||
if (cnt) {
|
||||
memcpy(s->inpkt + s->in_len, buf, cnt);
|
||||
s->in_len += cnt;
|
||||
buf += cnt;
|
||||
len -= cnt;
|
||||
total += cnt;
|
||||
}
|
||||
|
||||
if (s->in_len < s->in_needed) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (s->in_state == CSR_HDR_LEN) {
|
||||
s->in_hdr = csrhci_header_len(s->inpkt) + 1;
|
||||
assert(s->in_hdr >= s->in_needed);
|
||||
s->in_needed = s->in_hdr;
|
||||
s->in_state = CSR_DATA_LEN;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s->in_state == CSR_DATA_LEN) {
|
||||
s->in_needed += csrhci_data_len(s->inpkt);
|
||||
/* hci_acl_hdr could specify more than 4096 bytes, so assert. */
|
||||
assert(s->in_needed <= sizeof(s->inpkt));
|
||||
s->in_state = CSR_DATA;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s->in_state == CSR_DATA) {
|
||||
csrhci_in_packet(s, s->inpkt);
|
||||
csrhci_ready_for_next_inpkt(s);
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
static void csrhci_out_hci_packet_event(void *opaque,
|
||||
const uint8_t *data, int len)
|
||||
{
|
||||
struct csrhci_s *s = (struct csrhci_s *) opaque;
|
||||
uint8_t *pkt = csrhci_out_packet(s, (len + 2) & ~1); /* Align */
|
||||
|
||||
*pkt ++ = H4_EVT_PKT;
|
||||
memcpy(pkt, data, len);
|
||||
|
||||
csrhci_fifo_wake(s);
|
||||
}
|
||||
|
||||
static void csrhci_out_hci_packet_acl(void *opaque,
|
||||
const uint8_t *data, int len)
|
||||
{
|
||||
struct csrhci_s *s = (struct csrhci_s *) opaque;
|
||||
uint8_t *pkt = csrhci_out_packet(s, (len + 2) & ~1); /* Align */
|
||||
|
||||
*pkt ++ = H4_ACL_PKT;
|
||||
pkt[len & ~1] = 0;
|
||||
memcpy(pkt, data, len);
|
||||
|
||||
csrhci_fifo_wake(s);
|
||||
}
|
||||
|
||||
static int csrhci_ioctl(struct Chardev *chr, int cmd, void *arg)
|
||||
{
|
||||
QEMUSerialSetParams *ssp;
|
||||
struct csrhci_s *s = (struct csrhci_s *) chr;
|
||||
int prev_state = s->modem_state;
|
||||
|
||||
switch (cmd) {
|
||||
case CHR_IOCTL_SERIAL_SET_PARAMS:
|
||||
ssp = (QEMUSerialSetParams *) arg;
|
||||
s->baud_delay = NANOSECONDS_PER_SECOND / ssp->speed;
|
||||
/* Moments later... (but shorter than 100ms) */
|
||||
s->modem_state |= CHR_TIOCM_CTS;
|
||||
break;
|
||||
|
||||
case CHR_IOCTL_SERIAL_GET_TIOCM:
|
||||
*(int *) arg = s->modem_state;
|
||||
break;
|
||||
|
||||
case CHR_IOCTL_SERIAL_SET_TIOCM:
|
||||
s->modem_state = *(int *) arg;
|
||||
if (~s->modem_state & prev_state & CHR_TIOCM_RTS)
|
||||
s->modem_state &= ~CHR_TIOCM_CTS;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -ENOTSUP;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void csrhci_reset(struct csrhci_s *s)
|
||||
{
|
||||
s->out_len = 0;
|
||||
s->out_size = FIFO_LEN;
|
||||
csrhci_ready_for_next_inpkt(s);
|
||||
s->baud_delay = NANOSECONDS_PER_SECOND;
|
||||
s->enable = 0;
|
||||
|
||||
s->modem_state = 0;
|
||||
/* After a while... (but sooner than 10ms) */
|
||||
s->modem_state |= CHR_TIOCM_CTS;
|
||||
|
||||
memset(&s->bd_addr, 0, sizeof(bdaddr_t));
|
||||
}
|
||||
|
||||
static void csrhci_out_tick(void *opaque)
|
||||
{
|
||||
csrhci_fifo_wake((struct csrhci_s *) opaque);
|
||||
}
|
||||
|
||||
static void csrhci_pins(void *opaque, int line, int level)
|
||||
{
|
||||
struct csrhci_s *s = (struct csrhci_s *) opaque;
|
||||
int state = s->pin_state;
|
||||
|
||||
s->pin_state &= ~(1 << line);
|
||||
s->pin_state |= (!!level) << line;
|
||||
|
||||
if ((state & ~s->pin_state) & (1 << csrhci_pin_reset)) {
|
||||
/* TODO: Disappear from lower layers */
|
||||
csrhci_reset(s);
|
||||
}
|
||||
|
||||
if (s->pin_state == 3 && state != 3) {
|
||||
s->enable = 1;
|
||||
/* TODO: Wake lower layers up */
|
||||
}
|
||||
}
|
||||
|
||||
qemu_irq *csrhci_pins_get(Chardev *chr)
|
||||
{
|
||||
struct csrhci_s *s = (struct csrhci_s *) chr;
|
||||
|
||||
return s->pins;
|
||||
}
|
||||
|
||||
static void csrhci_open(Chardev *chr,
|
||||
ChardevBackend *backend,
|
||||
bool *be_opened,
|
||||
Error **errp)
|
||||
{
|
||||
struct csrhci_s *s = HCI_CHARDEV(chr);
|
||||
|
||||
s->hci = qemu_next_hci();
|
||||
s->hci->opaque = s;
|
||||
s->hci->evt_recv = csrhci_out_hci_packet_event;
|
||||
s->hci->acl_recv = csrhci_out_hci_packet_acl;
|
||||
|
||||
s->out_tm = timer_new_ns(QEMU_CLOCK_VIRTUAL, csrhci_out_tick, s);
|
||||
s->pins = qemu_allocate_irqs(csrhci_pins, s, __csrhci_pins);
|
||||
csrhci_reset(s);
|
||||
*be_opened = false;
|
||||
}
|
||||
|
||||
static void char_hci_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
ChardevClass *cc = CHARDEV_CLASS(oc);
|
||||
|
||||
cc->internal = true;
|
||||
cc->open = csrhci_open;
|
||||
cc->chr_write = csrhci_write;
|
||||
cc->chr_ioctl = csrhci_ioctl;
|
||||
}
|
||||
|
||||
static const TypeInfo char_hci_type_info = {
|
||||
.name = TYPE_CHARDEV_HCI,
|
||||
.parent = TYPE_CHARDEV,
|
||||
.instance_size = sizeof(struct csrhci_s),
|
||||
.class_init = char_hci_class_init,
|
||||
};
|
||||
|
||||
Chardev *uart_hci_init(void)
|
||||
{
|
||||
return qemu_chardev_new(NULL, TYPE_CHARDEV_HCI,
|
||||
NULL, NULL, &error_abort);
|
||||
}
|
||||
|
||||
static void register_types(void)
|
||||
{
|
||||
type_register_static(&char_hci_type_info);
|
||||
}
|
||||
|
||||
type_init(register_types);
|
2263
hw/bt/hci.c
2263
hw/bt/hci.c
File diff suppressed because it is too large
Load Diff
553
hw/bt/hid.c
553
hw/bt/hid.c
|
@ -1,553 +0,0 @@
|
|||
/*
|
||||
* QEMU Bluetooth HID Profile wrapper for USB HID.
|
||||
*
|
||||
* Copyright (C) 2007-2008 OpenMoko, Inc.
|
||||
* Written by Andrzej Zaborowski <andrew@openedhand.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 or
|
||||
* (at your option) version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "ui/console.h"
|
||||
#include "hw/input/hid.h"
|
||||
#include "hw/bt.h"
|
||||
|
||||
enum hid_transaction_req {
|
||||
BT_HANDSHAKE = 0x0,
|
||||
BT_HID_CONTROL = 0x1,
|
||||
BT_GET_REPORT = 0x4,
|
||||
BT_SET_REPORT = 0x5,
|
||||
BT_GET_PROTOCOL = 0x6,
|
||||
BT_SET_PROTOCOL = 0x7,
|
||||
BT_GET_IDLE = 0x8,
|
||||
BT_SET_IDLE = 0x9,
|
||||
BT_DATA = 0xa,
|
||||
BT_DATC = 0xb,
|
||||
};
|
||||
|
||||
enum hid_transaction_handshake {
|
||||
BT_HS_SUCCESSFUL = 0x0,
|
||||
BT_HS_NOT_READY = 0x1,
|
||||
BT_HS_ERR_INVALID_REPORT_ID = 0x2,
|
||||
BT_HS_ERR_UNSUPPORTED_REQUEST = 0x3,
|
||||
BT_HS_ERR_INVALID_PARAMETER = 0x4,
|
||||
BT_HS_ERR_UNKNOWN = 0xe,
|
||||
BT_HS_ERR_FATAL = 0xf,
|
||||
};
|
||||
|
||||
enum hid_transaction_control {
|
||||
BT_HC_NOP = 0x0,
|
||||
BT_HC_HARD_RESET = 0x1,
|
||||
BT_HC_SOFT_RESET = 0x2,
|
||||
BT_HC_SUSPEND = 0x3,
|
||||
BT_HC_EXIT_SUSPEND = 0x4,
|
||||
BT_HC_VIRTUAL_CABLE_UNPLUG = 0x5,
|
||||
};
|
||||
|
||||
enum hid_protocol {
|
||||
BT_HID_PROTO_BOOT = 0,
|
||||
BT_HID_PROTO_REPORT = 1,
|
||||
};
|
||||
|
||||
enum hid_boot_reportid {
|
||||
BT_HID_BOOT_INVALID = 0,
|
||||
BT_HID_BOOT_KEYBOARD,
|
||||
BT_HID_BOOT_MOUSE,
|
||||
};
|
||||
|
||||
enum hid_data_pkt {
|
||||
BT_DATA_OTHER = 0,
|
||||
BT_DATA_INPUT,
|
||||
BT_DATA_OUTPUT,
|
||||
BT_DATA_FEATURE,
|
||||
};
|
||||
|
||||
#define BT_HID_MTU 48
|
||||
|
||||
/* HID interface requests */
|
||||
#define GET_REPORT 0xa101
|
||||
#define GET_IDLE 0xa102
|
||||
#define GET_PROTOCOL 0xa103
|
||||
#define SET_REPORT 0x2109
|
||||
#define SET_IDLE 0x210a
|
||||
#define SET_PROTOCOL 0x210b
|
||||
|
||||
struct bt_hid_device_s {
|
||||
struct bt_l2cap_device_s btdev;
|
||||
struct bt_l2cap_conn_params_s *control;
|
||||
struct bt_l2cap_conn_params_s *interrupt;
|
||||
HIDState hid;
|
||||
|
||||
int proto;
|
||||
int connected;
|
||||
int data_type;
|
||||
int intr_state;
|
||||
struct {
|
||||
int len;
|
||||
uint8_t buffer[1024];
|
||||
} dataother, datain, dataout, feature, intrdataout;
|
||||
enum {
|
||||
bt_state_ready,
|
||||
bt_state_transaction,
|
||||
bt_state_suspend,
|
||||
} state;
|
||||
};
|
||||
|
||||
static void bt_hid_reset(struct bt_hid_device_s *s)
|
||||
{
|
||||
struct bt_scatternet_s *net = s->btdev.device.net;
|
||||
|
||||
/* Go as far as... */
|
||||
bt_l2cap_device_done(&s->btdev);
|
||||
bt_l2cap_device_init(&s->btdev, net);
|
||||
|
||||
hid_reset(&s->hid);
|
||||
s->proto = BT_HID_PROTO_REPORT;
|
||||
s->state = bt_state_ready;
|
||||
s->dataother.len = 0;
|
||||
s->datain.len = 0;
|
||||
s->dataout.len = 0;
|
||||
s->feature.len = 0;
|
||||
s->intrdataout.len = 0;
|
||||
s->intr_state = 0;
|
||||
}
|
||||
|
||||
static int bt_hid_out(struct bt_hid_device_s *s)
|
||||
{
|
||||
if (s->data_type == BT_DATA_OUTPUT) {
|
||||
/* nothing */
|
||||
;
|
||||
}
|
||||
|
||||
if (s->data_type == BT_DATA_FEATURE) {
|
||||
/* XXX:
|
||||
* does this send a USB_REQ_CLEAR_FEATURE/USB_REQ_SET_FEATURE
|
||||
* or a SET_REPORT? */
|
||||
;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int bt_hid_in(struct bt_hid_device_s *s)
|
||||
{
|
||||
s->datain.len = hid_keyboard_poll(&s->hid, s->datain.buffer,
|
||||
sizeof(s->datain.buffer));
|
||||
return s->datain.len;
|
||||
}
|
||||
|
||||
static void bt_hid_send_handshake(struct bt_hid_device_s *s, int result)
|
||||
{
|
||||
*s->control->sdu_out(s->control, 1) =
|
||||
(BT_HANDSHAKE << 4) | result;
|
||||
s->control->sdu_submit(s->control);
|
||||
}
|
||||
|
||||
static void bt_hid_send_control(struct bt_hid_device_s *s, int operation)
|
||||
{
|
||||
*s->control->sdu_out(s->control, 1) =
|
||||
(BT_HID_CONTROL << 4) | operation;
|
||||
s->control->sdu_submit(s->control);
|
||||
}
|
||||
|
||||
static void bt_hid_disconnect(struct bt_hid_device_s *s)
|
||||
{
|
||||
/* Disconnect s->control and s->interrupt */
|
||||
}
|
||||
|
||||
static void bt_hid_send_data(struct bt_l2cap_conn_params_s *ch, int type,
|
||||
const uint8_t *data, int len)
|
||||
{
|
||||
uint8_t *pkt, hdr = (BT_DATA << 4) | type;
|
||||
int plen;
|
||||
|
||||
do {
|
||||
plen = MIN(len, ch->remote_mtu - 1);
|
||||
pkt = ch->sdu_out(ch, plen + 1);
|
||||
|
||||
pkt[0] = hdr;
|
||||
if (plen)
|
||||
memcpy(pkt + 1, data, plen);
|
||||
ch->sdu_submit(ch);
|
||||
|
||||
len -= plen;
|
||||
data += plen;
|
||||
hdr = (BT_DATC << 4) | type;
|
||||
} while (plen == ch->remote_mtu - 1);
|
||||
}
|
||||
|
||||
static void bt_hid_control_transaction(struct bt_hid_device_s *s,
|
||||
const uint8_t *data, int len)
|
||||
{
|
||||
uint8_t type, parameter;
|
||||
int rlen, ret = -1;
|
||||
if (len < 1)
|
||||
return;
|
||||
|
||||
type = data[0] >> 4;
|
||||
parameter = data[0] & 0xf;
|
||||
|
||||
switch (type) {
|
||||
case BT_HANDSHAKE:
|
||||
case BT_DATA:
|
||||
switch (parameter) {
|
||||
default:
|
||||
/* These are not expected to be sent this direction. */
|
||||
ret = BT_HS_ERR_INVALID_PARAMETER;
|
||||
}
|
||||
break;
|
||||
|
||||
case BT_HID_CONTROL:
|
||||
if (len != 1 || (parameter != BT_HC_VIRTUAL_CABLE_UNPLUG &&
|
||||
s->state == bt_state_transaction)) {
|
||||
ret = BT_HS_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
}
|
||||
switch (parameter) {
|
||||
case BT_HC_NOP:
|
||||
break;
|
||||
case BT_HC_HARD_RESET:
|
||||
case BT_HC_SOFT_RESET:
|
||||
bt_hid_reset(s);
|
||||
break;
|
||||
case BT_HC_SUSPEND:
|
||||
if (s->state == bt_state_ready)
|
||||
s->state = bt_state_suspend;
|
||||
else
|
||||
ret = BT_HS_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
case BT_HC_EXIT_SUSPEND:
|
||||
if (s->state == bt_state_suspend)
|
||||
s->state = bt_state_ready;
|
||||
else
|
||||
ret = BT_HS_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
case BT_HC_VIRTUAL_CABLE_UNPLUG:
|
||||
bt_hid_disconnect(s);
|
||||
break;
|
||||
default:
|
||||
ret = BT_HS_ERR_INVALID_PARAMETER;
|
||||
}
|
||||
break;
|
||||
|
||||
case BT_GET_REPORT:
|
||||
/* No ReportIDs declared. */
|
||||
if (((parameter & 8) && len != 3) ||
|
||||
(!(parameter & 8) && len != 1) ||
|
||||
s->state != bt_state_ready) {
|
||||
ret = BT_HS_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
}
|
||||
if (parameter & 8)
|
||||
rlen = data[2] | (data[3] << 8);
|
||||
else
|
||||
rlen = INT_MAX;
|
||||
switch (parameter & 3) {
|
||||
case BT_DATA_OTHER:
|
||||
ret = BT_HS_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
case BT_DATA_INPUT:
|
||||
/* Here we can as well poll s->usbdev */
|
||||
bt_hid_send_data(s->control, BT_DATA_INPUT,
|
||||
s->datain.buffer, MIN(rlen, s->datain.len));
|
||||
break;
|
||||
case BT_DATA_OUTPUT:
|
||||
bt_hid_send_data(s->control, BT_DATA_OUTPUT,
|
||||
s->dataout.buffer, MIN(rlen, s->dataout.len));
|
||||
break;
|
||||
case BT_DATA_FEATURE:
|
||||
bt_hid_send_data(s->control, BT_DATA_FEATURE,
|
||||
s->feature.buffer, MIN(rlen, s->feature.len));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case BT_SET_REPORT:
|
||||
if (len < 2 || len > BT_HID_MTU || s->state != bt_state_ready ||
|
||||
(parameter & 3) == BT_DATA_OTHER ||
|
||||
(parameter & 3) == BT_DATA_INPUT) {
|
||||
ret = BT_HS_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
}
|
||||
s->data_type = parameter & 3;
|
||||
if (s->data_type == BT_DATA_OUTPUT) {
|
||||
s->dataout.len = len - 1;
|
||||
memcpy(s->dataout.buffer, data + 1, s->dataout.len);
|
||||
} else {
|
||||
s->feature.len = len - 1;
|
||||
memcpy(s->feature.buffer, data + 1, s->feature.len);
|
||||
}
|
||||
if (len == BT_HID_MTU)
|
||||
s->state = bt_state_transaction;
|
||||
else
|
||||
bt_hid_out(s);
|
||||
break;
|
||||
|
||||
case BT_GET_PROTOCOL:
|
||||
if (len != 1 || s->state == bt_state_transaction) {
|
||||
ret = BT_HS_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
}
|
||||
*s->control->sdu_out(s->control, 1) = s->proto;
|
||||
s->control->sdu_submit(s->control);
|
||||
break;
|
||||
|
||||
case BT_SET_PROTOCOL:
|
||||
if (len != 1 || s->state == bt_state_transaction ||
|
||||
(parameter != BT_HID_PROTO_BOOT &&
|
||||
parameter != BT_HID_PROTO_REPORT)) {
|
||||
ret = BT_HS_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
}
|
||||
s->proto = parameter;
|
||||
s->hid.protocol = parameter;
|
||||
ret = BT_HS_SUCCESSFUL;
|
||||
break;
|
||||
|
||||
case BT_GET_IDLE:
|
||||
if (len != 1 || s->state == bt_state_transaction) {
|
||||
ret = BT_HS_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
}
|
||||
*s->control->sdu_out(s->control, 1) = s->hid.idle;
|
||||
s->control->sdu_submit(s->control);
|
||||
break;
|
||||
|
||||
case BT_SET_IDLE:
|
||||
if (len != 2 || s->state == bt_state_transaction) {
|
||||
ret = BT_HS_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
}
|
||||
|
||||
s->hid.idle = data[1];
|
||||
/* XXX: Does this generate a handshake? */
|
||||
break;
|
||||
|
||||
case BT_DATC:
|
||||
if (len > BT_HID_MTU || s->state != bt_state_transaction) {
|
||||
ret = BT_HS_ERR_INVALID_PARAMETER;
|
||||
break;
|
||||
}
|
||||
if (s->data_type == BT_DATA_OUTPUT) {
|
||||
memcpy(s->dataout.buffer + s->dataout.len, data + 1, len - 1);
|
||||
s->dataout.len += len - 1;
|
||||
} else {
|
||||
memcpy(s->feature.buffer + s->feature.len, data + 1, len - 1);
|
||||
s->feature.len += len - 1;
|
||||
}
|
||||
if (len < BT_HID_MTU) {
|
||||
bt_hid_out(s);
|
||||
s->state = bt_state_ready;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = BT_HS_ERR_UNSUPPORTED_REQUEST;
|
||||
}
|
||||
|
||||
if (ret != -1)
|
||||
bt_hid_send_handshake(s, ret);
|
||||
}
|
||||
|
||||
static void bt_hid_control_sdu(void *opaque, const uint8_t *data, int len)
|
||||
{
|
||||
struct bt_hid_device_s *hid = opaque;
|
||||
|
||||
bt_hid_control_transaction(hid, data, len);
|
||||
}
|
||||
|
||||
static void bt_hid_datain(HIDState *hs)
|
||||
{
|
||||
struct bt_hid_device_s *hid =
|
||||
container_of(hs, struct bt_hid_device_s, hid);
|
||||
|
||||
/* If suspended, wake-up and send a wake-up event first. We might
|
||||
* want to also inspect the input report and ignore event like
|
||||
* mouse movements until a button event occurs. */
|
||||
if (hid->state == bt_state_suspend) {
|
||||
hid->state = bt_state_ready;
|
||||
}
|
||||
|
||||
if (bt_hid_in(hid) > 0)
|
||||
/* TODO: when in boot-mode precede any Input reports with the ReportID
|
||||
* byte, here and in GetReport/SetReport on the Control channel. */
|
||||
bt_hid_send_data(hid->interrupt, BT_DATA_INPUT,
|
||||
hid->datain.buffer, hid->datain.len);
|
||||
}
|
||||
|
||||
static void bt_hid_interrupt_sdu(void *opaque, const uint8_t *data, int len)
|
||||
{
|
||||
struct bt_hid_device_s *hid = opaque;
|
||||
|
||||
if (len > BT_HID_MTU || len < 1)
|
||||
goto bad;
|
||||
if ((data[0] & 3) != BT_DATA_OUTPUT)
|
||||
goto bad;
|
||||
if ((data[0] >> 4) == BT_DATA) {
|
||||
if (hid->intr_state)
|
||||
goto bad;
|
||||
|
||||
hid->data_type = BT_DATA_OUTPUT;
|
||||
hid->intrdataout.len = 0;
|
||||
} else if ((data[0] >> 4) == BT_DATC) {
|
||||
if (!hid->intr_state)
|
||||
goto bad;
|
||||
} else
|
||||
goto bad;
|
||||
|
||||
memcpy(hid->intrdataout.buffer + hid->intrdataout.len, data + 1, len - 1);
|
||||
hid->intrdataout.len += len - 1;
|
||||
hid->intr_state = (len == BT_HID_MTU);
|
||||
if (!hid->intr_state) {
|
||||
memcpy(hid->dataout.buffer, hid->intrdataout.buffer,
|
||||
hid->dataout.len = hid->intrdataout.len);
|
||||
bt_hid_out(hid);
|
||||
}
|
||||
|
||||
return;
|
||||
bad:
|
||||
error_report("%s: bad transaction on Interrupt channel.",
|
||||
__func__);
|
||||
}
|
||||
|
||||
/* "Virtual cable" plug/unplug event. */
|
||||
static void bt_hid_connected_update(struct bt_hid_device_s *hid)
|
||||
{
|
||||
int prev = hid->connected;
|
||||
|
||||
hid->connected = hid->control && hid->interrupt;
|
||||
|
||||
/* Stop page-/inquiry-scanning when a host is connected. */
|
||||
hid->btdev.device.page_scan = !hid->connected;
|
||||
hid->btdev.device.inquiry_scan = !hid->connected;
|
||||
|
||||
if (hid->connected && !prev) {
|
||||
hid_reset(&hid->hid);
|
||||
hid->proto = BT_HID_PROTO_REPORT;
|
||||
}
|
||||
|
||||
/* Should set HIDVirtualCable in SDP (possibly need to check that SDP
|
||||
* isn't destroyed yet, in case we're being called from handle_destroy) */
|
||||
}
|
||||
|
||||
static void bt_hid_close_control(void *opaque)
|
||||
{
|
||||
struct bt_hid_device_s *hid = opaque;
|
||||
|
||||
hid->control = NULL;
|
||||
bt_hid_connected_update(hid);
|
||||
}
|
||||
|
||||
static void bt_hid_close_interrupt(void *opaque)
|
||||
{
|
||||
struct bt_hid_device_s *hid = opaque;
|
||||
|
||||
hid->interrupt = NULL;
|
||||
bt_hid_connected_update(hid);
|
||||
}
|
||||
|
||||
static int bt_hid_new_control_ch(struct bt_l2cap_device_s *dev,
|
||||
struct bt_l2cap_conn_params_s *params)
|
||||
{
|
||||
struct bt_hid_device_s *hid = (struct bt_hid_device_s *) dev;
|
||||
|
||||
if (hid->control)
|
||||
return 1;
|
||||
|
||||
hid->control = params;
|
||||
hid->control->opaque = hid;
|
||||
hid->control->close = bt_hid_close_control;
|
||||
hid->control->sdu_in = bt_hid_control_sdu;
|
||||
|
||||
bt_hid_connected_update(hid);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bt_hid_new_interrupt_ch(struct bt_l2cap_device_s *dev,
|
||||
struct bt_l2cap_conn_params_s *params)
|
||||
{
|
||||
struct bt_hid_device_s *hid = (struct bt_hid_device_s *) dev;
|
||||
|
||||
if (hid->interrupt)
|
||||
return 1;
|
||||
|
||||
hid->interrupt = params;
|
||||
hid->interrupt->opaque = hid;
|
||||
hid->interrupt->close = bt_hid_close_interrupt;
|
||||
hid->interrupt->sdu_in = bt_hid_interrupt_sdu;
|
||||
|
||||
bt_hid_connected_update(hid);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bt_hid_destroy(struct bt_device_s *dev)
|
||||
{
|
||||
struct bt_hid_device_s *hid = (struct bt_hid_device_s *) dev;
|
||||
|
||||
if (hid->connected)
|
||||
bt_hid_send_control(hid, BT_HC_VIRTUAL_CABLE_UNPLUG);
|
||||
bt_l2cap_device_done(&hid->btdev);
|
||||
|
||||
hid_free(&hid->hid);
|
||||
|
||||
g_free(hid);
|
||||
}
|
||||
|
||||
enum peripheral_minor_class {
|
||||
class_other = 0 << 4,
|
||||
class_keyboard = 1 << 4,
|
||||
class_pointing = 2 << 4,
|
||||
class_combo = 3 << 4,
|
||||
};
|
||||
|
||||
static struct bt_device_s *bt_hid_init(struct bt_scatternet_s *net,
|
||||
enum peripheral_minor_class minor)
|
||||
{
|
||||
struct bt_hid_device_s *s = g_malloc0(sizeof(*s));
|
||||
uint32_t class =
|
||||
/* Format type */
|
||||
(0 << 0) |
|
||||
/* Device class */
|
||||
(minor << 2) |
|
||||
(5 << 8) | /* "Peripheral" */
|
||||
/* Service classes */
|
||||
(1 << 13) | /* Limited discoverable mode */
|
||||
(1 << 19); /* Capturing device (?) */
|
||||
|
||||
bt_l2cap_device_init(&s->btdev, net);
|
||||
bt_l2cap_sdp_init(&s->btdev);
|
||||
bt_l2cap_psm_register(&s->btdev, BT_PSM_HID_CTRL,
|
||||
BT_HID_MTU, bt_hid_new_control_ch);
|
||||
bt_l2cap_psm_register(&s->btdev, BT_PSM_HID_INTR,
|
||||
BT_HID_MTU, bt_hid_new_interrupt_ch);
|
||||
|
||||
hid_init(&s->hid, HID_KEYBOARD, bt_hid_datain);
|
||||
s->btdev.device.lmp_name = "BT Keyboard";
|
||||
|
||||
s->btdev.device.handle_destroy = bt_hid_destroy;
|
||||
|
||||
s->btdev.device.class[0] = (class >> 0) & 0xff;
|
||||
s->btdev.device.class[1] = (class >> 8) & 0xff;
|
||||
s->btdev.device.class[2] = (class >> 16) & 0xff;
|
||||
|
||||
return &s->btdev.device;
|
||||
}
|
||||
|
||||
struct bt_device_s *bt_keyboard_init(struct bt_scatternet_s *net)
|
||||
{
|
||||
return bt_hid_init(net, class_keyboard);
|
||||
}
|
1367
hw/bt/l2cap.c
1367
hw/bt/l2cap.c
File diff suppressed because it is too large
Load Diff
989
hw/bt/sdp.c
989
hw/bt/sdp.c
|
@ -1,989 +0,0 @@
|
|||
/*
|
||||
* Service Discover Protocol server for QEMU L2CAP devices
|
||||
*
|
||||
* Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/host-utils.h"
|
||||
#include "hw/bt.h"
|
||||
|
||||
struct bt_l2cap_sdp_state_s {
|
||||
struct bt_l2cap_conn_params_s *channel;
|
||||
|
||||
struct sdp_service_record_s {
|
||||
int match;
|
||||
|
||||
int *uuid;
|
||||
int uuids;
|
||||
struct sdp_service_attribute_s {
|
||||
int match;
|
||||
|
||||
int attribute_id;
|
||||
int len;
|
||||
void *pair;
|
||||
} *attribute_list;
|
||||
int attributes;
|
||||
} *service_list;
|
||||
int services;
|
||||
};
|
||||
|
||||
static ssize_t sdp_datalen(const uint8_t **element, ssize_t *left)
|
||||
{
|
||||
uint32_t len = *(*element) ++ & SDP_DSIZE_MASK;
|
||||
|
||||
if (!*left)
|
||||
return -1;
|
||||
(*left) --;
|
||||
|
||||
if (len < SDP_DSIZE_NEXT1)
|
||||
return 1 << len;
|
||||
else if (len == SDP_DSIZE_NEXT1) {
|
||||
if (*left < 1)
|
||||
return -1;
|
||||
(*left) --;
|
||||
|
||||
return *(*element) ++;
|
||||
} else if (len == SDP_DSIZE_NEXT2) {
|
||||
if (*left < 2)
|
||||
return -1;
|
||||
(*left) -= 2;
|
||||
|
||||
len = (*(*element) ++) << 8;
|
||||
return len | (*(*element) ++);
|
||||
} else {
|
||||
if (*left < 4)
|
||||
return -1;
|
||||
(*left) -= 4;
|
||||
|
||||
len = (*(*element) ++) << 24;
|
||||
len |= (*(*element) ++) << 16;
|
||||
len |= (*(*element) ++) << 8;
|
||||
return len | (*(*element) ++);
|
||||
}
|
||||
}
|
||||
|
||||
static const uint8_t bt_base_uuid[12] = {
|
||||
0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb,
|
||||
};
|
||||
|
||||
static int sdp_uuid_match(struct sdp_service_record_s *record,
|
||||
const uint8_t *uuid, ssize_t datalen)
|
||||
{
|
||||
int *lo, hi, val;
|
||||
|
||||
if (datalen == 16 || datalen == 4) {
|
||||
if (datalen == 16 && memcmp(uuid + 4, bt_base_uuid, 12))
|
||||
return 0;
|
||||
|
||||
if (uuid[0] | uuid[1])
|
||||
return 0;
|
||||
uuid += 2;
|
||||
}
|
||||
|
||||
val = (uuid[0] << 8) | uuid[1];
|
||||
lo = record->uuid;
|
||||
hi = record->uuids;
|
||||
while (hi >>= 1)
|
||||
if (lo[hi] <= val)
|
||||
lo += hi;
|
||||
|
||||
return *lo == val;
|
||||
}
|
||||
|
||||
#define CONTINUATION_PARAM_SIZE (1 + sizeof(int))
|
||||
#define MAX_PDU_OUT_SIZE 96 /* Arbitrary */
|
||||
#define PDU_HEADER_SIZE 5
|
||||
#define MAX_RSP_PARAM_SIZE (MAX_PDU_OUT_SIZE - PDU_HEADER_SIZE - \
|
||||
CONTINUATION_PARAM_SIZE)
|
||||
|
||||
static int sdp_svc_match(struct bt_l2cap_sdp_state_s *sdp,
|
||||
const uint8_t **req, ssize_t *len)
|
||||
{
|
||||
size_t datalen;
|
||||
int i;
|
||||
|
||||
if ((**req & ~SDP_DSIZE_MASK) != SDP_DTYPE_UUID)
|
||||
return 1;
|
||||
|
||||
datalen = sdp_datalen(req, len);
|
||||
if (datalen != 2 && datalen != 4 && datalen != 16)
|
||||
return 1;
|
||||
|
||||
for (i = 0; i < sdp->services; i ++)
|
||||
if (sdp_uuid_match(&sdp->service_list[i], *req, datalen))
|
||||
sdp->service_list[i].match = 1;
|
||||
|
||||
(*req) += datalen;
|
||||
(*len) -= datalen;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t sdp_svc_search(struct bt_l2cap_sdp_state_s *sdp,
|
||||
uint8_t *rsp, const uint8_t *req, ssize_t len)
|
||||
{
|
||||
ssize_t seqlen;
|
||||
int i, count, start, end, max;
|
||||
int32_t handle;
|
||||
|
||||
/* Perform the search */
|
||||
for (i = 0; i < sdp->services; i ++)
|
||||
sdp->service_list[i].match = 0;
|
||||
|
||||
if (len < 1)
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
if ((*req & ~SDP_DSIZE_MASK) == SDP_DTYPE_SEQ) {
|
||||
seqlen = sdp_datalen(&req, &len);
|
||||
if (seqlen < 3 || len < seqlen)
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
len -= seqlen;
|
||||
while (seqlen)
|
||||
if (sdp_svc_match(sdp, &req, &seqlen))
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
} else {
|
||||
if (sdp_svc_match(sdp, &req, &len)) {
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
}
|
||||
}
|
||||
|
||||
if (len < 3)
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
max = (req[0] << 8) | req[1];
|
||||
req += 2;
|
||||
len -= 2;
|
||||
|
||||
if (*req) {
|
||||
if (len <= sizeof(int))
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
len -= sizeof(int);
|
||||
memcpy(&start, req + 1, sizeof(int));
|
||||
} else
|
||||
start = 0;
|
||||
|
||||
if (len > 1)
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
|
||||
/* Output the results */
|
||||
len = 4;
|
||||
count = 0;
|
||||
end = start;
|
||||
for (i = 0; i < sdp->services; i ++)
|
||||
if (sdp->service_list[i].match) {
|
||||
if (count >= start && count < max && len + 4 < MAX_RSP_PARAM_SIZE) {
|
||||
handle = i;
|
||||
memcpy(rsp + len, &handle, 4);
|
||||
len += 4;
|
||||
end = count + 1;
|
||||
}
|
||||
|
||||
count ++;
|
||||
}
|
||||
|
||||
rsp[0] = count >> 8;
|
||||
rsp[1] = count & 0xff;
|
||||
rsp[2] = (end - start) >> 8;
|
||||
rsp[3] = (end - start) & 0xff;
|
||||
|
||||
if (end < count) {
|
||||
rsp[len ++] = sizeof(int);
|
||||
memcpy(rsp + len, &end, sizeof(int));
|
||||
len += 4;
|
||||
} else
|
||||
rsp[len ++] = 0;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int sdp_attr_match(struct sdp_service_record_s *record,
|
||||
const uint8_t **req, ssize_t *len)
|
||||
{
|
||||
int i, start, end;
|
||||
|
||||
if (**req == (SDP_DTYPE_UINT | SDP_DSIZE_2)) {
|
||||
(*req) ++;
|
||||
if (*len < 3)
|
||||
return 1;
|
||||
|
||||
start = (*(*req) ++) << 8;
|
||||
start |= *(*req) ++;
|
||||
end = start;
|
||||
*len -= 3;
|
||||
} else if (**req == (SDP_DTYPE_UINT | SDP_DSIZE_4)) {
|
||||
(*req) ++;
|
||||
if (*len < 5)
|
||||
return 1;
|
||||
|
||||
start = (*(*req) ++) << 8;
|
||||
start |= *(*req) ++;
|
||||
end = (*(*req) ++) << 8;
|
||||
end |= *(*req) ++;
|
||||
*len -= 5;
|
||||
} else
|
||||
return 1;
|
||||
|
||||
for (i = 0; i < record->attributes; i ++)
|
||||
if (record->attribute_list[i].attribute_id >= start &&
|
||||
record->attribute_list[i].attribute_id <= end)
|
||||
record->attribute_list[i].match = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t sdp_attr_get(struct bt_l2cap_sdp_state_s *sdp,
|
||||
uint8_t *rsp, const uint8_t *req, ssize_t len)
|
||||
{
|
||||
ssize_t seqlen;
|
||||
int i, start, end, max;
|
||||
int32_t handle;
|
||||
struct sdp_service_record_s *record;
|
||||
uint8_t *lst;
|
||||
|
||||
/* Perform the search */
|
||||
if (len < 7)
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
memcpy(&handle, req, 4);
|
||||
req += 4;
|
||||
len -= 4;
|
||||
|
||||
if (handle < 0 || handle > sdp->services)
|
||||
return -SDP_INVALID_RECORD_HANDLE;
|
||||
record = &sdp->service_list[handle];
|
||||
|
||||
for (i = 0; i < record->attributes; i ++)
|
||||
record->attribute_list[i].match = 0;
|
||||
|
||||
max = (req[0] << 8) | req[1];
|
||||
req += 2;
|
||||
len -= 2;
|
||||
if (max < 0x0007)
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
|
||||
if ((*req & ~SDP_DSIZE_MASK) == SDP_DTYPE_SEQ) {
|
||||
seqlen = sdp_datalen(&req, &len);
|
||||
if (seqlen < 3 || len < seqlen)
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
len -= seqlen;
|
||||
|
||||
while (seqlen)
|
||||
if (sdp_attr_match(record, &req, &seqlen))
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
} else {
|
||||
if (sdp_attr_match(record, &req, &len)) {
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
}
|
||||
}
|
||||
|
||||
if (len < 1)
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
|
||||
if (*req) {
|
||||
if (len <= sizeof(int))
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
len -= sizeof(int);
|
||||
memcpy(&start, req + 1, sizeof(int));
|
||||
} else
|
||||
start = 0;
|
||||
|
||||
if (len > 1)
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
|
||||
/* Output the results */
|
||||
lst = rsp + 2;
|
||||
max = MIN(max, MAX_RSP_PARAM_SIZE);
|
||||
len = 3 - start;
|
||||
end = 0;
|
||||
for (i = 0; i < record->attributes; i ++)
|
||||
if (record->attribute_list[i].match) {
|
||||
if (len >= 0 && len + record->attribute_list[i].len < max) {
|
||||
memcpy(lst + len, record->attribute_list[i].pair,
|
||||
record->attribute_list[i].len);
|
||||
end = len + record->attribute_list[i].len;
|
||||
}
|
||||
len += record->attribute_list[i].len;
|
||||
}
|
||||
if (0 >= start) {
|
||||
lst[0] = SDP_DTYPE_SEQ | SDP_DSIZE_NEXT2;
|
||||
lst[1] = (len + start - 3) >> 8;
|
||||
lst[2] = (len + start - 3) & 0xff;
|
||||
}
|
||||
|
||||
rsp[0] = end >> 8;
|
||||
rsp[1] = end & 0xff;
|
||||
|
||||
if (end < len) {
|
||||
len = end + start;
|
||||
lst[end ++] = sizeof(int);
|
||||
memcpy(lst + end, &len, sizeof(int));
|
||||
end += sizeof(int);
|
||||
} else
|
||||
lst[end ++] = 0;
|
||||
|
||||
return end + 2;
|
||||
}
|
||||
|
||||
static int sdp_svc_attr_match(struct bt_l2cap_sdp_state_s *sdp,
|
||||
const uint8_t **req, ssize_t *len)
|
||||
{
|
||||
int i, j, start, end;
|
||||
struct sdp_service_record_s *record;
|
||||
|
||||
if (**req == (SDP_DTYPE_UINT | SDP_DSIZE_2)) {
|
||||
(*req) ++;
|
||||
if (*len < 3)
|
||||
return 1;
|
||||
|
||||
start = (*(*req) ++) << 8;
|
||||
start |= *(*req) ++;
|
||||
end = start;
|
||||
*len -= 3;
|
||||
} else if (**req == (SDP_DTYPE_UINT | SDP_DSIZE_4)) {
|
||||
(*req) ++;
|
||||
if (*len < 5)
|
||||
return 1;
|
||||
|
||||
start = (*(*req) ++) << 8;
|
||||
start |= *(*req) ++;
|
||||
end = (*(*req) ++) << 8;
|
||||
end |= *(*req) ++;
|
||||
*len -= 5;
|
||||
} else
|
||||
return 1;
|
||||
|
||||
for (i = 0; i < sdp->services; i ++)
|
||||
if ((record = &sdp->service_list[i])->match)
|
||||
for (j = 0; j < record->attributes; j ++)
|
||||
if (record->attribute_list[j].attribute_id >= start &&
|
||||
record->attribute_list[j].attribute_id <= end)
|
||||
record->attribute_list[j].match = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t sdp_svc_search_attr_get(struct bt_l2cap_sdp_state_s *sdp,
|
||||
uint8_t *rsp, const uint8_t *req, ssize_t len)
|
||||
{
|
||||
ssize_t seqlen;
|
||||
int i, j, start, end, max;
|
||||
struct sdp_service_record_s *record;
|
||||
uint8_t *lst;
|
||||
|
||||
/* Perform the search */
|
||||
for (i = 0; i < sdp->services; i ++) {
|
||||
sdp->service_list[i].match = 0;
|
||||
for (j = 0; j < sdp->service_list[i].attributes; j ++)
|
||||
sdp->service_list[i].attribute_list[j].match = 0;
|
||||
}
|
||||
|
||||
if (len < 1)
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
if ((*req & ~SDP_DSIZE_MASK) == SDP_DTYPE_SEQ) {
|
||||
seqlen = sdp_datalen(&req, &len);
|
||||
if (seqlen < 3 || len < seqlen)
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
len -= seqlen;
|
||||
|
||||
while (seqlen)
|
||||
if (sdp_svc_match(sdp, &req, &seqlen))
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
} else {
|
||||
if (sdp_svc_match(sdp, &req, &len)) {
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
}
|
||||
}
|
||||
|
||||
if (len < 3)
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
max = (req[0] << 8) | req[1];
|
||||
req += 2;
|
||||
len -= 2;
|
||||
if (max < 0x0007)
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
|
||||
if ((*req & ~SDP_DSIZE_MASK) == SDP_DTYPE_SEQ) {
|
||||
seqlen = sdp_datalen(&req, &len);
|
||||
if (seqlen < 3 || len < seqlen)
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
len -= seqlen;
|
||||
|
||||
while (seqlen)
|
||||
if (sdp_svc_attr_match(sdp, &req, &seqlen))
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
} else {
|
||||
if (sdp_svc_attr_match(sdp, &req, &len)) {
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
}
|
||||
}
|
||||
|
||||
if (len < 1)
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
|
||||
if (*req) {
|
||||
if (len <= sizeof(int))
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
len -= sizeof(int);
|
||||
memcpy(&start, req + 1, sizeof(int));
|
||||
} else
|
||||
start = 0;
|
||||
|
||||
if (len > 1)
|
||||
return -SDP_INVALID_SYNTAX;
|
||||
|
||||
/* Output the results */
|
||||
/* This assumes empty attribute lists are never to be returned even
|
||||
* for matching Service Records. In practice this shouldn't happen
|
||||
* as the requestor will usually include the always present
|
||||
* ServiceRecordHandle AttributeID in AttributeIDList. */
|
||||
lst = rsp + 2;
|
||||
max = MIN(max, MAX_RSP_PARAM_SIZE);
|
||||
len = 3 - start;
|
||||
end = 0;
|
||||
for (i = 0; i < sdp->services; i ++)
|
||||
if ((record = &sdp->service_list[i])->match) {
|
||||
len += 3;
|
||||
seqlen = len;
|
||||
for (j = 0; j < record->attributes; j ++)
|
||||
if (record->attribute_list[j].match) {
|
||||
if (len >= 0)
|
||||
if (len + record->attribute_list[j].len < max) {
|
||||
memcpy(lst + len, record->attribute_list[j].pair,
|
||||
record->attribute_list[j].len);
|
||||
end = len + record->attribute_list[j].len;
|
||||
}
|
||||
len += record->attribute_list[j].len;
|
||||
}
|
||||
if (seqlen == len)
|
||||
len -= 3;
|
||||
else if (seqlen >= 3 && seqlen < max) {
|
||||
lst[seqlen - 3] = SDP_DTYPE_SEQ | SDP_DSIZE_NEXT2;
|
||||
lst[seqlen - 2] = (len - seqlen) >> 8;
|
||||
lst[seqlen - 1] = (len - seqlen) & 0xff;
|
||||
}
|
||||
}
|
||||
if (len == 3 - start)
|
||||
len -= 3;
|
||||
else if (0 >= start) {
|
||||
lst[0] = SDP_DTYPE_SEQ | SDP_DSIZE_NEXT2;
|
||||
lst[1] = (len + start - 3) >> 8;
|
||||
lst[2] = (len + start - 3) & 0xff;
|
||||
}
|
||||
|
||||
rsp[0] = end >> 8;
|
||||
rsp[1] = end & 0xff;
|
||||
|
||||
if (end < len) {
|
||||
len = end + start;
|
||||
lst[end ++] = sizeof(int);
|
||||
memcpy(lst + end, &len, sizeof(int));
|
||||
end += sizeof(int);
|
||||
} else
|
||||
lst[end ++] = 0;
|
||||
|
||||
return end + 2;
|
||||
}
|
||||
|
||||
static void bt_l2cap_sdp_sdu_in(void *opaque, const uint8_t *data, int len)
|
||||
{
|
||||
struct bt_l2cap_sdp_state_s *sdp = opaque;
|
||||
enum bt_sdp_cmd pdu_id;
|
||||
uint8_t rsp[MAX_PDU_OUT_SIZE - PDU_HEADER_SIZE], *sdu_out;
|
||||
int transaction_id, plen;
|
||||
int err = 0;
|
||||
int rsp_len = 0;
|
||||
|
||||
if (len < 5) {
|
||||
error_report("%s: short SDP PDU (%iB).", __func__, len);
|
||||
return;
|
||||
}
|
||||
|
||||
pdu_id = *data ++;
|
||||
transaction_id = (data[0] << 8) | data[1];
|
||||
plen = (data[2] << 8) | data[3];
|
||||
data += 4;
|
||||
len -= 5;
|
||||
|
||||
if (len != plen) {
|
||||
error_report("%s: wrong SDP PDU length (%iB != %iB).",
|
||||
__func__, plen, len);
|
||||
err = SDP_INVALID_PDU_SIZE;
|
||||
goto respond;
|
||||
}
|
||||
|
||||
switch (pdu_id) {
|
||||
case SDP_SVC_SEARCH_REQ:
|
||||
rsp_len = sdp_svc_search(sdp, rsp, data, len);
|
||||
pdu_id = SDP_SVC_SEARCH_RSP;
|
||||
break;
|
||||
|
||||
case SDP_SVC_ATTR_REQ:
|
||||
rsp_len = sdp_attr_get(sdp, rsp, data, len);
|
||||
pdu_id = SDP_SVC_ATTR_RSP;
|
||||
break;
|
||||
|
||||
case SDP_SVC_SEARCH_ATTR_REQ:
|
||||
rsp_len = sdp_svc_search_attr_get(sdp, rsp, data, len);
|
||||
pdu_id = SDP_SVC_SEARCH_ATTR_RSP;
|
||||
break;
|
||||
|
||||
case SDP_ERROR_RSP:
|
||||
case SDP_SVC_ATTR_RSP:
|
||||
case SDP_SVC_SEARCH_RSP:
|
||||
case SDP_SVC_SEARCH_ATTR_RSP:
|
||||
default:
|
||||
error_report("%s: unexpected SDP PDU ID %02x.",
|
||||
__func__, pdu_id);
|
||||
err = SDP_INVALID_SYNTAX;
|
||||
break;
|
||||
}
|
||||
|
||||
if (rsp_len < 0) {
|
||||
err = -rsp_len;
|
||||
rsp_len = 0;
|
||||
}
|
||||
|
||||
respond:
|
||||
if (err) {
|
||||
pdu_id = SDP_ERROR_RSP;
|
||||
rsp[rsp_len ++] = err >> 8;
|
||||
rsp[rsp_len ++] = err & 0xff;
|
||||
}
|
||||
|
||||
sdu_out = sdp->channel->sdu_out(sdp->channel, rsp_len + PDU_HEADER_SIZE);
|
||||
|
||||
sdu_out[0] = pdu_id;
|
||||
sdu_out[1] = transaction_id >> 8;
|
||||
sdu_out[2] = transaction_id & 0xff;
|
||||
sdu_out[3] = rsp_len >> 8;
|
||||
sdu_out[4] = rsp_len & 0xff;
|
||||
memcpy(sdu_out + PDU_HEADER_SIZE, rsp, rsp_len);
|
||||
|
||||
sdp->channel->sdu_submit(sdp->channel);
|
||||
}
|
||||
|
||||
static void bt_l2cap_sdp_close_ch(void *opaque)
|
||||
{
|
||||
struct bt_l2cap_sdp_state_s *sdp = opaque;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < sdp->services; i ++) {
|
||||
g_free(sdp->service_list[i].attribute_list[0].pair);
|
||||
g_free(sdp->service_list[i].attribute_list);
|
||||
g_free(sdp->service_list[i].uuid);
|
||||
}
|
||||
g_free(sdp->service_list);
|
||||
g_free(sdp);
|
||||
}
|
||||
|
||||
struct sdp_def_service_s {
|
||||
uint16_t class_uuid;
|
||||
struct sdp_def_attribute_s {
|
||||
uint16_t id;
|
||||
struct sdp_def_data_element_s {
|
||||
uint8_t type;
|
||||
union {
|
||||
uint32_t uint;
|
||||
const char *str;
|
||||
struct sdp_def_data_element_s *list;
|
||||
} value;
|
||||
} data;
|
||||
} attributes[];
|
||||
};
|
||||
|
||||
/* Calculate a safe byte count to allocate that will store the given
|
||||
* element, at the same time count elements of a UUID type. */
|
||||
static int sdp_attr_max_size(struct sdp_def_data_element_s *element,
|
||||
int *uuids)
|
||||
{
|
||||
int type = element->type & ~SDP_DSIZE_MASK;
|
||||
int len;
|
||||
|
||||
if (type == SDP_DTYPE_UINT || type == SDP_DTYPE_UUID ||
|
||||
type == SDP_DTYPE_BOOL) {
|
||||
if (type == SDP_DTYPE_UUID)
|
||||
(*uuids) ++;
|
||||
return 1 + (1 << (element->type & SDP_DSIZE_MASK));
|
||||
}
|
||||
|
||||
if (type == SDP_DTYPE_STRING || type == SDP_DTYPE_URL) {
|
||||
if (element->type & SDP_DSIZE_MASK) {
|
||||
for (len = 0; element->value.str[len] |
|
||||
element->value.str[len + 1]; len ++);
|
||||
return len;
|
||||
} else
|
||||
return 2 + strlen(element->value.str);
|
||||
}
|
||||
|
||||
if (type != SDP_DTYPE_SEQ)
|
||||
exit(-1);
|
||||
len = 2;
|
||||
element = element->value.list;
|
||||
while (element->type)
|
||||
len += sdp_attr_max_size(element ++, uuids);
|
||||
if (len > 255)
|
||||
exit (-1);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int sdp_attr_write(uint8_t *data,
|
||||
struct sdp_def_data_element_s *element, int **uuid)
|
||||
{
|
||||
int type = element->type & ~SDP_DSIZE_MASK;
|
||||
int len = 0;
|
||||
|
||||
if (type == SDP_DTYPE_UINT || type == SDP_DTYPE_BOOL) {
|
||||
data[len ++] = element->type;
|
||||
if ((element->type & SDP_DSIZE_MASK) == SDP_DSIZE_1)
|
||||
data[len ++] = (element->value.uint >> 0) & 0xff;
|
||||
else if ((element->type & SDP_DSIZE_MASK) == SDP_DSIZE_2) {
|
||||
data[len ++] = (element->value.uint >> 8) & 0xff;
|
||||
data[len ++] = (element->value.uint >> 0) & 0xff;
|
||||
} else if ((element->type & SDP_DSIZE_MASK) == SDP_DSIZE_4) {
|
||||
data[len ++] = (element->value.uint >> 24) & 0xff;
|
||||
data[len ++] = (element->value.uint >> 16) & 0xff;
|
||||
data[len ++] = (element->value.uint >> 8) & 0xff;
|
||||
data[len ++] = (element->value.uint >> 0) & 0xff;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
if (type == SDP_DTYPE_UUID) {
|
||||
*(*uuid) ++ = element->value.uint;
|
||||
|
||||
data[len ++] = element->type;
|
||||
data[len ++] = (element->value.uint >> 24) & 0xff;
|
||||
data[len ++] = (element->value.uint >> 16) & 0xff;
|
||||
data[len ++] = (element->value.uint >> 8) & 0xff;
|
||||
data[len ++] = (element->value.uint >> 0) & 0xff;
|
||||
memcpy(data + len, bt_base_uuid, 12);
|
||||
|
||||
return len + 12;
|
||||
}
|
||||
|
||||
data[0] = type | SDP_DSIZE_NEXT1;
|
||||
if (type == SDP_DTYPE_STRING || type == SDP_DTYPE_URL) {
|
||||
if (element->type & SDP_DSIZE_MASK)
|
||||
for (len = 0; element->value.str[len] |
|
||||
element->value.str[len + 1]; len ++);
|
||||
else
|
||||
len = strlen(element->value.str);
|
||||
memcpy(data + 2, element->value.str, data[1] = len);
|
||||
|
||||
return len + 2;
|
||||
}
|
||||
|
||||
len = 2;
|
||||
element = element->value.list;
|
||||
while (element->type)
|
||||
len += sdp_attr_write(data + len, element ++, uuid);
|
||||
data[1] = len - 2;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int sdp_attributeid_compare(const struct sdp_service_attribute_s *a,
|
||||
const struct sdp_service_attribute_s *b)
|
||||
{
|
||||
return (int) b->attribute_id - a->attribute_id;
|
||||
}
|
||||
|
||||
static int sdp_uuid_compare(const int *a, const int *b)
|
||||
{
|
||||
return *a - *b;
|
||||
}
|
||||
|
||||
static void sdp_service_record_build(struct sdp_service_record_s *record,
|
||||
struct sdp_def_service_s *def, int handle)
|
||||
{
|
||||
int len = 0;
|
||||
uint8_t *data;
|
||||
int *uuid;
|
||||
|
||||
record->uuids = 0;
|
||||
while (def->attributes[record->attributes].data.type) {
|
||||
len += 3;
|
||||
len += sdp_attr_max_size(&def->attributes[record->attributes ++].data,
|
||||
&record->uuids);
|
||||
}
|
||||
|
||||
assert(len > 0);
|
||||
record->uuids = pow2ceil(record->uuids);
|
||||
record->attribute_list =
|
||||
g_malloc0(record->attributes * sizeof(*record->attribute_list));
|
||||
record->uuid =
|
||||
g_malloc0(record->uuids * sizeof(*record->uuid));
|
||||
data = g_malloc(len);
|
||||
|
||||
record->attributes = 0;
|
||||
uuid = record->uuid;
|
||||
while (def->attributes[record->attributes].data.type) {
|
||||
int attribute_id = def->attributes[record->attributes].id;
|
||||
record->attribute_list[record->attributes].pair = data;
|
||||
record->attribute_list[record->attributes].attribute_id = attribute_id;
|
||||
|
||||
len = 0;
|
||||
data[len ++] = SDP_DTYPE_UINT | SDP_DSIZE_2;
|
||||
data[len ++] = attribute_id >> 8;
|
||||
data[len ++] = attribute_id & 0xff;
|
||||
len += sdp_attr_write(data + len,
|
||||
&def->attributes[record->attributes].data, &uuid);
|
||||
|
||||
/* Special case: assign a ServiceRecordHandle in sequence */
|
||||
if (def->attributes[record->attributes].id == SDP_ATTR_RECORD_HANDLE)
|
||||
def->attributes[record->attributes].data.value.uint = handle;
|
||||
/* Note: we could also assign a ServiceDescription based on
|
||||
* sdp->device.device->lmp_name. */
|
||||
|
||||
record->attribute_list[record->attributes ++].len = len;
|
||||
data += len;
|
||||
}
|
||||
|
||||
/* Sort the attribute list by the AttributeID. The first must be
|
||||
* SDP_ATTR_RECORD_HANDLE so that bt_l2cap_sdp_close_ch can free
|
||||
* the buffer.
|
||||
*/
|
||||
qsort(record->attribute_list, record->attributes,
|
||||
sizeof(*record->attribute_list),
|
||||
(void *) sdp_attributeid_compare);
|
||||
assert(record->attribute_list[0].pair == data);
|
||||
|
||||
/* Sort the searchable UUIDs list for bisection */
|
||||
qsort(record->uuid, record->uuids,
|
||||
sizeof(*record->uuid),
|
||||
(void *) sdp_uuid_compare);
|
||||
}
|
||||
|
||||
static void sdp_service_db_build(struct bt_l2cap_sdp_state_s *sdp,
|
||||
struct sdp_def_service_s **service)
|
||||
{
|
||||
sdp->services = 0;
|
||||
while (service[sdp->services])
|
||||
sdp->services ++;
|
||||
sdp->service_list =
|
||||
g_malloc0(sdp->services * sizeof(*sdp->service_list));
|
||||
|
||||
sdp->services = 0;
|
||||
while (*service) {
|
||||
sdp_service_record_build(&sdp->service_list[sdp->services],
|
||||
*service, sdp->services);
|
||||
service ++;
|
||||
sdp->services ++;
|
||||
}
|
||||
}
|
||||
|
||||
#define LAST { .type = 0 }
|
||||
#define SERVICE(name, attrs) \
|
||||
static struct sdp_def_service_s glue(glue(sdp_service_, name), _s) = { \
|
||||
.attributes = { attrs { .data = LAST } }, \
|
||||
};
|
||||
#define ATTRIBUTE(attrid, val) { .id = glue(SDP_ATTR_, attrid), .data = val },
|
||||
#define UINT8(val) { \
|
||||
.type = SDP_DTYPE_UINT | SDP_DSIZE_1, \
|
||||
.value.uint = val, \
|
||||
},
|
||||
#define UINT16(val) { \
|
||||
.type = SDP_DTYPE_UINT | SDP_DSIZE_2, \
|
||||
.value.uint = val, \
|
||||
},
|
||||
#define UINT32(val) { \
|
||||
.type = SDP_DTYPE_UINT | SDP_DSIZE_4, \
|
||||
.value.uint = val, \
|
||||
},
|
||||
#define UUID128(val) { \
|
||||
.type = SDP_DTYPE_UUID | SDP_DSIZE_16, \
|
||||
.value.uint = val, \
|
||||
},
|
||||
#define SDP_TRUE { \
|
||||
.type = SDP_DTYPE_BOOL | SDP_DSIZE_1, \
|
||||
.value.uint = 1, \
|
||||
},
|
||||
#define SDP_FALSE { \
|
||||
.type = SDP_DTYPE_BOOL | SDP_DSIZE_1, \
|
||||
.value.uint = 0, \
|
||||
},
|
||||
#define STRING(val) { \
|
||||
.type = SDP_DTYPE_STRING, \
|
||||
.value.str = val, \
|
||||
},
|
||||
#define ARRAY(...) { \
|
||||
.type = SDP_DTYPE_STRING | SDP_DSIZE_2, \
|
||||
.value.str = (char []) { __VA_ARGS__, 0, 0 }, \
|
||||
},
|
||||
#define URL(val) { \
|
||||
.type = SDP_DTYPE_URL, \
|
||||
.value.str = val, \
|
||||
},
|
||||
#if 1
|
||||
#define LIST(val) { \
|
||||
.type = SDP_DTYPE_SEQ, \
|
||||
.value.list = (struct sdp_def_data_element_s []) { val LAST }, \
|
||||
},
|
||||
#endif
|
||||
|
||||
/* Try to keep each single attribute below MAX_PDU_OUT_SIZE bytes
|
||||
* in resulting SDP data representation size. */
|
||||
|
||||
SERVICE(hid,
|
||||
ATTRIBUTE(RECORD_HANDLE, UINT32(0)) /* Filled in later */
|
||||
ATTRIBUTE(SVCLASS_ID_LIST, LIST(UUID128(HID_SVCLASS_ID)))
|
||||
ATTRIBUTE(RECORD_STATE, UINT32(1))
|
||||
ATTRIBUTE(PROTO_DESC_LIST, LIST(
|
||||
LIST(UUID128(L2CAP_UUID) UINT16(BT_PSM_HID_CTRL))
|
||||
LIST(UUID128(HIDP_UUID))
|
||||
))
|
||||
ATTRIBUTE(BROWSE_GRP_LIST, LIST(UUID128(0x1002)))
|
||||
ATTRIBUTE(LANG_BASE_ATTR_ID_LIST, LIST(
|
||||
UINT16(0x656e) UINT16(0x006a) UINT16(0x0100)
|
||||
))
|
||||
ATTRIBUTE(PFILE_DESC_LIST, LIST(
|
||||
LIST(UUID128(HID_PROFILE_ID) UINT16(0x0100))
|
||||
))
|
||||
ATTRIBUTE(DOC_URL, URL("http://bellard.org/qemu/user-doc.html"))
|
||||
ATTRIBUTE(SVCNAME_PRIMARY, STRING("QEMU Bluetooth HID"))
|
||||
ATTRIBUTE(SVCDESC_PRIMARY, STRING("QEMU Keyboard/Mouse"))
|
||||
ATTRIBUTE(SVCPROV_PRIMARY, STRING("QEMU"))
|
||||
|
||||
/* Profile specific */
|
||||
ATTRIBUTE(DEVICE_RELEASE_NUMBER, UINT16(0x0091)) /* Deprecated, remove */
|
||||
ATTRIBUTE(PARSER_VERSION, UINT16(0x0111))
|
||||
/* TODO: extract from l2cap_device->device.class[0] */
|
||||
ATTRIBUTE(DEVICE_SUBCLASS, UINT8(0x40))
|
||||
ATTRIBUTE(COUNTRY_CODE, UINT8(0x15))
|
||||
ATTRIBUTE(VIRTUAL_CABLE, SDP_TRUE)
|
||||
ATTRIBUTE(RECONNECT_INITIATE, SDP_FALSE)
|
||||
/* TODO: extract from hid->usbdev->report_desc */
|
||||
ATTRIBUTE(DESCRIPTOR_LIST, LIST(
|
||||
LIST(UINT8(0x22) ARRAY(
|
||||
0x05, 0x01, /* Usage Page (Generic Desktop) */
|
||||
0x09, 0x06, /* Usage (Keyboard) */
|
||||
0xa1, 0x01, /* Collection (Application) */
|
||||
0x75, 0x01, /* Report Size (1) */
|
||||
0x95, 0x08, /* Report Count (8) */
|
||||
0x05, 0x07, /* Usage Page (Key Codes) */
|
||||
0x19, 0xe0, /* Usage Minimum (224) */
|
||||
0x29, 0xe7, /* Usage Maximum (231) */
|
||||
0x15, 0x00, /* Logical Minimum (0) */
|
||||
0x25, 0x01, /* Logical Maximum (1) */
|
||||
0x81, 0x02, /* Input (Data, Variable, Absolute) */
|
||||
0x95, 0x01, /* Report Count (1) */
|
||||
0x75, 0x08, /* Report Size (8) */
|
||||
0x81, 0x01, /* Input (Constant) */
|
||||
0x95, 0x05, /* Report Count (5) */
|
||||
0x75, 0x01, /* Report Size (1) */
|
||||
0x05, 0x08, /* Usage Page (LEDs) */
|
||||
0x19, 0x01, /* Usage Minimum (1) */
|
||||
0x29, 0x05, /* Usage Maximum (5) */
|
||||
0x91, 0x02, /* Output (Data, Variable, Absolute) */
|
||||
0x95, 0x01, /* Report Count (1) */
|
||||
0x75, 0x03, /* Report Size (3) */
|
||||
0x91, 0x01, /* Output (Constant) */
|
||||
0x95, 0x06, /* Report Count (6) */
|
||||
0x75, 0x08, /* Report Size (8) */
|
||||
0x15, 0x00, /* Logical Minimum (0) */
|
||||
0x25, 0xff, /* Logical Maximum (255) */
|
||||
0x05, 0x07, /* Usage Page (Key Codes) */
|
||||
0x19, 0x00, /* Usage Minimum (0) */
|
||||
0x29, 0xff, /* Usage Maximum (255) */
|
||||
0x81, 0x00, /* Input (Data, Array) */
|
||||
0xc0 /* End Collection */
|
||||
))))
|
||||
ATTRIBUTE(LANG_ID_BASE_LIST, LIST(
|
||||
LIST(UINT16(0x0409) UINT16(0x0100))
|
||||
))
|
||||
ATTRIBUTE(SDP_DISABLE, SDP_FALSE)
|
||||
ATTRIBUTE(BATTERY_POWER, SDP_TRUE)
|
||||
ATTRIBUTE(REMOTE_WAKEUP, SDP_TRUE)
|
||||
ATTRIBUTE(BOOT_DEVICE, SDP_TRUE) /* XXX: untested */
|
||||
ATTRIBUTE(SUPERVISION_TIMEOUT, UINT16(0x0c80))
|
||||
ATTRIBUTE(NORMALLY_CONNECTABLE, SDP_TRUE)
|
||||
ATTRIBUTE(PROFILE_VERSION, UINT16(0x0100))
|
||||
)
|
||||
|
||||
SERVICE(sdp,
|
||||
ATTRIBUTE(RECORD_HANDLE, UINT32(0)) /* Filled in later */
|
||||
ATTRIBUTE(SVCLASS_ID_LIST, LIST(UUID128(SDP_SERVER_SVCLASS_ID)))
|
||||
ATTRIBUTE(RECORD_STATE, UINT32(1))
|
||||
ATTRIBUTE(PROTO_DESC_LIST, LIST(
|
||||
LIST(UUID128(L2CAP_UUID) UINT16(BT_PSM_SDP))
|
||||
LIST(UUID128(SDP_UUID))
|
||||
))
|
||||
ATTRIBUTE(BROWSE_GRP_LIST, LIST(UUID128(0x1002)))
|
||||
ATTRIBUTE(LANG_BASE_ATTR_ID_LIST, LIST(
|
||||
UINT16(0x656e) UINT16(0x006a) UINT16(0x0100)
|
||||
))
|
||||
ATTRIBUTE(PFILE_DESC_LIST, LIST(
|
||||
LIST(UUID128(SDP_SERVER_PROFILE_ID) UINT16(0x0100))
|
||||
))
|
||||
ATTRIBUTE(DOC_URL, URL("http://bellard.org/qemu/user-doc.html"))
|
||||
ATTRIBUTE(SVCPROV_PRIMARY, STRING("QEMU"))
|
||||
|
||||
/* Profile specific */
|
||||
ATTRIBUTE(VERSION_NUM_LIST, LIST(UINT16(0x0100)))
|
||||
ATTRIBUTE(SVCDB_STATE , UINT32(1))
|
||||
)
|
||||
|
||||
SERVICE(pnp,
|
||||
ATTRIBUTE(RECORD_HANDLE, UINT32(0)) /* Filled in later */
|
||||
ATTRIBUTE(SVCLASS_ID_LIST, LIST(UUID128(PNP_INFO_SVCLASS_ID)))
|
||||
ATTRIBUTE(RECORD_STATE, UINT32(1))
|
||||
ATTRIBUTE(PROTO_DESC_LIST, LIST(
|
||||
LIST(UUID128(L2CAP_UUID) UINT16(BT_PSM_SDP))
|
||||
LIST(UUID128(SDP_UUID))
|
||||
))
|
||||
ATTRIBUTE(BROWSE_GRP_LIST, LIST(UUID128(0x1002)))
|
||||
ATTRIBUTE(LANG_BASE_ATTR_ID_LIST, LIST(
|
||||
UINT16(0x656e) UINT16(0x006a) UINT16(0x0100)
|
||||
))
|
||||
ATTRIBUTE(PFILE_DESC_LIST, LIST(
|
||||
LIST(UUID128(PNP_INFO_PROFILE_ID) UINT16(0x0100))
|
||||
))
|
||||
ATTRIBUTE(DOC_URL, URL("http://bellard.org/qemu/user-doc.html"))
|
||||
ATTRIBUTE(SVCPROV_PRIMARY, STRING("QEMU"))
|
||||
|
||||
/* Profile specific */
|
||||
ATTRIBUTE(SPECIFICATION_ID, UINT16(0x0100))
|
||||
ATTRIBUTE(VERSION, UINT16(0x0100))
|
||||
ATTRIBUTE(PRIMARY_RECORD, SDP_TRUE)
|
||||
)
|
||||
|
||||
static int bt_l2cap_sdp_new_ch(struct bt_l2cap_device_s *dev,
|
||||
struct bt_l2cap_conn_params_s *params)
|
||||
{
|
||||
struct bt_l2cap_sdp_state_s *sdp = g_malloc0(sizeof(*sdp));
|
||||
struct sdp_def_service_s *services[] = {
|
||||
&sdp_service_sdp_s,
|
||||
&sdp_service_hid_s,
|
||||
&sdp_service_pnp_s,
|
||||
NULL,
|
||||
};
|
||||
|
||||
sdp->channel = params;
|
||||
sdp->channel->opaque = sdp;
|
||||
sdp->channel->close = bt_l2cap_sdp_close_ch;
|
||||
sdp->channel->sdu_in = bt_l2cap_sdp_sdu_in;
|
||||
|
||||
sdp_service_db_build(sdp, services);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void bt_l2cap_sdp_init(struct bt_l2cap_device_s *dev)
|
||||
{
|
||||
bt_l2cap_psm_register(dev, BT_PSM_SDP,
|
||||
MAX_PDU_OUT_SIZE, bt_l2cap_sdp_new_ch);
|
||||
}
|
|
@ -136,44 +136,11 @@ static inline bool ivshmem_is_master(IVShmemState *s)
|
|||
return s->master == ON_OFF_AUTO_ON;
|
||||
}
|
||||
|
||||
static void ivshmem_update_irq(IVShmemState *s)
|
||||
{
|
||||
PCIDevice *d = PCI_DEVICE(s);
|
||||
uint32_t isr = s->intrstatus & s->intrmask;
|
||||
|
||||
/*
|
||||
* Do nothing unless the device actually uses INTx. Here's how
|
||||
* the device variants signal interrupts, what they put in PCI
|
||||
* config space:
|
||||
* Device variant Interrupt Interrupt Pin MSI-X cap.
|
||||
* ivshmem-plain none 0 no
|
||||
* ivshmem-doorbell MSI-X 1 yes(1)
|
||||
* ivshmem,msi=off INTx 1 no
|
||||
* ivshmem,msi=on MSI-X 1(2) yes(1)
|
||||
* (1) if guest enabled MSI-X
|
||||
* (2) the device lies
|
||||
* Leads to the condition for doing nothing:
|
||||
*/
|
||||
if (ivshmem_has_feature(s, IVSHMEM_MSI)
|
||||
|| !d->config[PCI_INTERRUPT_PIN]) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* don't print ISR resets */
|
||||
if (isr) {
|
||||
IVSHMEM_DPRINTF("Set IRQ to %d (%04x %04x)\n",
|
||||
isr ? 1 : 0, s->intrstatus, s->intrmask);
|
||||
}
|
||||
|
||||
pci_set_irq(d, isr != 0);
|
||||
}
|
||||
|
||||
static void ivshmem_IntrMask_write(IVShmemState *s, uint32_t val)
|
||||
{
|
||||
IVSHMEM_DPRINTF("IntrMask write(w) val = 0x%04x\n", val);
|
||||
|
||||
s->intrmask = val;
|
||||
ivshmem_update_irq(s);
|
||||
}
|
||||
|
||||
static uint32_t ivshmem_IntrMask_read(IVShmemState *s)
|
||||
|
@ -189,7 +156,6 @@ static void ivshmem_IntrStatus_write(IVShmemState *s, uint32_t val)
|
|||
IVSHMEM_DPRINTF("IntrStatus write(w) val = 0x%04x\n", val);
|
||||
|
||||
s->intrstatus = val;
|
||||
ivshmem_update_irq(s);
|
||||
}
|
||||
|
||||
static uint32_t ivshmem_IntrStatus_read(IVShmemState *s)
|
||||
|
@ -198,7 +164,6 @@ static uint32_t ivshmem_IntrStatus_read(IVShmemState *s)
|
|||
|
||||
/* reading ISR clears all interrupts */
|
||||
s->intrstatus = 0;
|
||||
ivshmem_update_irq(s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -82,11 +82,6 @@ config USB_NETWORK
|
|||
default y
|
||||
depends on USB
|
||||
|
||||
config USB_BLUETOOTH
|
||||
bool
|
||||
default y
|
||||
depends on USB
|
||||
|
||||
config USB_SMARTCARD
|
||||
bool
|
||||
default y
|
||||
|
|
|
@ -25,7 +25,6 @@ common-obj-$(CONFIG_USB_STORAGE_UAS) += dev-uas.o
|
|||
common-obj-$(CONFIG_USB_AUDIO) += dev-audio.o
|
||||
common-obj-$(CONFIG_USB_SERIAL) += dev-serial.o
|
||||
common-obj-$(CONFIG_USB_NETWORK) += dev-network.o
|
||||
common-obj-$(CONFIG_USB_BLUETOOTH) += dev-bluetooth.o
|
||||
|
||||
ifeq ($(CONFIG_USB_SMARTCARD),y)
|
||||
common-obj-y += dev-smartcard-reader.o
|
||||
|
|
|
@ -1,581 +0,0 @@
|
|||
/*
|
||||
* QEMU Bluetooth HCI USB Transport Layer v1.0
|
||||
*
|
||||
* Copyright (C) 2007 OpenMoko, Inc.
|
||||
* Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 or
|
||||
* (at your option) version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/module.h"
|
||||
#include "hw/usb.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "desc.h"
|
||||
#include "sysemu/bt.h"
|
||||
#include "hw/bt.h"
|
||||
|
||||
struct USBBtState {
|
||||
USBDevice dev;
|
||||
struct HCIInfo *hci;
|
||||
USBEndpoint *intr;
|
||||
|
||||
int config;
|
||||
|
||||
#define CFIFO_LEN_MASK 255
|
||||
#define DFIFO_LEN_MASK 4095
|
||||
struct usb_hci_in_fifo_s {
|
||||
uint8_t data[(DFIFO_LEN_MASK + 1) * 2];
|
||||
struct {
|
||||
uint8_t *data;
|
||||
int len;
|
||||
} fifo[CFIFO_LEN_MASK + 1];
|
||||
int dstart, dlen, dsize, start, len;
|
||||
} evt, acl, sco;
|
||||
|
||||
struct usb_hci_out_fifo_s {
|
||||
uint8_t data[4096];
|
||||
int len;
|
||||
} outcmd, outacl, outsco;
|
||||
};
|
||||
|
||||
#define TYPE_USB_BT "usb-bt-dongle"
|
||||
#define USB_BT(obj) OBJECT_CHECK(struct USBBtState, (obj), TYPE_USB_BT)
|
||||
|
||||
#define USB_EVT_EP 1
|
||||
#define USB_ACL_EP 2
|
||||
#define USB_SCO_EP 3
|
||||
|
||||
enum {
|
||||
STR_MANUFACTURER = 1,
|
||||
STR_SERIALNUMBER,
|
||||
};
|
||||
|
||||
static const USBDescStrings desc_strings = {
|
||||
[STR_MANUFACTURER] = "QEMU",
|
||||
[STR_SERIALNUMBER] = "1",
|
||||
};
|
||||
|
||||
static const USBDescIface desc_iface_bluetooth[] = {
|
||||
{
|
||||
.bInterfaceNumber = 0,
|
||||
.bNumEndpoints = 3,
|
||||
.bInterfaceClass = 0xe0, /* Wireless */
|
||||
.bInterfaceSubClass = 0x01, /* Radio Frequency */
|
||||
.bInterfaceProtocol = 0x01, /* Bluetooth */
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | USB_EVT_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_INT,
|
||||
.wMaxPacketSize = 0x10,
|
||||
.bInterval = 0x02,
|
||||
},
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_OUT | USB_ACL_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = 0x40,
|
||||
.bInterval = 0x0a,
|
||||
},
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | USB_ACL_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_BULK,
|
||||
.wMaxPacketSize = 0x40,
|
||||
.bInterval = 0x0a,
|
||||
},
|
||||
},
|
||||
},{
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 0,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = 0xe0, /* Wireless */
|
||||
.bInterfaceSubClass = 0x01, /* Radio Frequency */
|
||||
.bInterfaceProtocol = 0x01, /* Bluetooth */
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
},
|
||||
},{
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 1,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = 0xe0, /* Wireless */
|
||||
.bInterfaceSubClass = 0x01, /* Radio Frequency */
|
||||
.bInterfaceProtocol = 0x01, /* Bluetooth */
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x09,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x09,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
},
|
||||
},{
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 2,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = 0xe0, /* Wireless */
|
||||
.bInterfaceSubClass = 0x01, /* Radio Frequency */
|
||||
.bInterfaceProtocol = 0x01, /* Bluetooth */
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x11,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x11,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
},
|
||||
},{
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 3,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = 0xe0, /* Wireless */
|
||||
.bInterfaceSubClass = 0x01, /* Radio Frequency */
|
||||
.bInterfaceProtocol = 0x01, /* Bluetooth */
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x19,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x19,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
},
|
||||
},{
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 4,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = 0xe0, /* Wireless */
|
||||
.bInterfaceSubClass = 0x01, /* Radio Frequency */
|
||||
.bInterfaceProtocol = 0x01, /* Bluetooth */
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x21,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x21,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
},
|
||||
},{
|
||||
.bInterfaceNumber = 1,
|
||||
.bAlternateSetting = 5,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = 0xe0, /* Wireless */
|
||||
.bInterfaceSubClass = 0x01, /* Radio Frequency */
|
||||
.bInterfaceProtocol = 0x01, /* Bluetooth */
|
||||
.eps = (USBDescEndpoint[]) {
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_OUT | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x31,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bEndpointAddress = USB_DIR_IN | USB_SCO_EP,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
|
||||
.wMaxPacketSize = 0x31,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
static const USBDescDevice desc_device_bluetooth = {
|
||||
.bcdUSB = 0x0110,
|
||||
.bDeviceClass = 0xe0, /* Wireless */
|
||||
.bDeviceSubClass = 0x01, /* Radio Frequency */
|
||||
.bDeviceProtocol = 0x01, /* Bluetooth */
|
||||
.bMaxPacketSize0 = 64,
|
||||
.bNumConfigurations = 1,
|
||||
.confs = (USBDescConfig[]) {
|
||||
{
|
||||
.bNumInterfaces = 2,
|
||||
.bConfigurationValue = 1,
|
||||
.bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER,
|
||||
.bMaxPower = 0,
|
||||
.nif = ARRAY_SIZE(desc_iface_bluetooth),
|
||||
.ifs = desc_iface_bluetooth,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const USBDesc desc_bluetooth = {
|
||||
.id = {
|
||||
.idVendor = 0x0a12,
|
||||
.idProduct = 0x0001,
|
||||
.bcdDevice = 0x1958,
|
||||
.iManufacturer = STR_MANUFACTURER,
|
||||
.iProduct = 0,
|
||||
.iSerialNumber = STR_SERIALNUMBER,
|
||||
},
|
||||
.full = &desc_device_bluetooth,
|
||||
.str = desc_strings,
|
||||
};
|
||||
|
||||
static void usb_bt_fifo_reset(struct usb_hci_in_fifo_s *fifo)
|
||||
{
|
||||
fifo->dstart = 0;
|
||||
fifo->dlen = 0;
|
||||
fifo->dsize = DFIFO_LEN_MASK + 1;
|
||||
fifo->start = 0;
|
||||
fifo->len = 0;
|
||||
}
|
||||
|
||||
static void usb_bt_fifo_enqueue(struct usb_hci_in_fifo_s *fifo,
|
||||
const uint8_t *data, int len)
|
||||
{
|
||||
int off = fifo->dstart + fifo->dlen;
|
||||
uint8_t *buf;
|
||||
|
||||
fifo->dlen += len;
|
||||
if (off <= DFIFO_LEN_MASK) {
|
||||
if (off + len > DFIFO_LEN_MASK + 1 &&
|
||||
(fifo->dsize = off + len) > (DFIFO_LEN_MASK + 1) * 2) {
|
||||
fprintf(stderr, "%s: can't alloc %i bytes\n", __func__, len);
|
||||
exit(-1);
|
||||
}
|
||||
buf = fifo->data + off;
|
||||
} else {
|
||||
if (fifo->dlen > fifo->dsize) {
|
||||
fprintf(stderr, "%s: can't alloc %i bytes\n", __func__, len);
|
||||
exit(-1);
|
||||
}
|
||||
buf = fifo->data + off - fifo->dsize;
|
||||
}
|
||||
|
||||
off = (fifo->start + fifo->len ++) & CFIFO_LEN_MASK;
|
||||
fifo->fifo[off].data = memcpy(buf, data, len);
|
||||
fifo->fifo[off].len = len;
|
||||
}
|
||||
|
||||
static inline void usb_bt_fifo_dequeue(struct usb_hci_in_fifo_s *fifo,
|
||||
USBPacket *p)
|
||||
{
|
||||
int len;
|
||||
|
||||
assert(fifo->len != 0);
|
||||
|
||||
len = MIN(p->iov.size, fifo->fifo[fifo->start].len);
|
||||
usb_packet_copy(p, fifo->fifo[fifo->start].data, len);
|
||||
if (len == p->iov.size) {
|
||||
fifo->fifo[fifo->start].len -= len;
|
||||
fifo->fifo[fifo->start].data += len;
|
||||
} else {
|
||||
fifo->start ++;
|
||||
fifo->start &= CFIFO_LEN_MASK;
|
||||
fifo->len --;
|
||||
}
|
||||
|
||||
fifo->dstart += len;
|
||||
fifo->dlen -= len;
|
||||
if (fifo->dstart >= fifo->dsize) {
|
||||
fifo->dstart = 0;
|
||||
fifo->dsize = DFIFO_LEN_MASK + 1;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void usb_bt_fifo_out_enqueue(struct USBBtState *s,
|
||||
struct usb_hci_out_fifo_s *fifo,
|
||||
void (*send)(struct HCIInfo *, const uint8_t *, int),
|
||||
int (*complete)(const uint8_t *, int),
|
||||
USBPacket *p)
|
||||
{
|
||||
usb_packet_copy(p, fifo->data + fifo->len, p->iov.size);
|
||||
fifo->len += p->iov.size;
|
||||
if (complete(fifo->data, fifo->len)) {
|
||||
send(s->hci, fifo->data, fifo->len);
|
||||
fifo->len = 0;
|
||||
}
|
||||
|
||||
/* TODO: do we need to loop? */
|
||||
}
|
||||
|
||||
static int usb_bt_hci_cmd_complete(const uint8_t *data, int len)
|
||||
{
|
||||
len -= HCI_COMMAND_HDR_SIZE;
|
||||
return len >= 0 &&
|
||||
len >= ((struct hci_command_hdr *) data)->plen;
|
||||
}
|
||||
|
||||
static int usb_bt_hci_acl_complete(const uint8_t *data, int len)
|
||||
{
|
||||
len -= HCI_ACL_HDR_SIZE;
|
||||
return len >= 0 &&
|
||||
len >= le16_to_cpu(((struct hci_acl_hdr *) data)->dlen);
|
||||
}
|
||||
|
||||
static int usb_bt_hci_sco_complete(const uint8_t *data, int len)
|
||||
{
|
||||
len -= HCI_SCO_HDR_SIZE;
|
||||
return len >= 0 &&
|
||||
len >= ((struct hci_sco_hdr *) data)->dlen;
|
||||
}
|
||||
|
||||
static void usb_bt_handle_reset(USBDevice *dev)
|
||||
{
|
||||
struct USBBtState *s = (struct USBBtState *) dev->opaque;
|
||||
|
||||
usb_bt_fifo_reset(&s->evt);
|
||||
usb_bt_fifo_reset(&s->acl);
|
||||
usb_bt_fifo_reset(&s->sco);
|
||||
s->outcmd.len = 0;
|
||||
s->outacl.len = 0;
|
||||
s->outsco.len = 0;
|
||||
}
|
||||
|
||||
static void usb_bt_handle_control(USBDevice *dev, USBPacket *p,
|
||||
int request, int value, int index, int length, uint8_t *data)
|
||||
{
|
||||
struct USBBtState *s = (struct USBBtState *) dev->opaque;
|
||||
int ret;
|
||||
|
||||
ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
|
||||
if (ret >= 0) {
|
||||
switch (request) {
|
||||
case DeviceRequest | USB_REQ_GET_CONFIGURATION:
|
||||
s->config = 0;
|
||||
break;
|
||||
case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
|
||||
s->config = 1;
|
||||
usb_bt_fifo_reset(&s->evt);
|
||||
usb_bt_fifo_reset(&s->acl);
|
||||
usb_bt_fifo_reset(&s->sco);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (request) {
|
||||
case InterfaceRequest | USB_REQ_GET_STATUS:
|
||||
case EndpointRequest | USB_REQ_GET_STATUS:
|
||||
data[0] = 0x00;
|
||||
data[1] = 0x00;
|
||||
p->actual_length = 2;
|
||||
break;
|
||||
case InterfaceOutRequest | USB_REQ_CLEAR_FEATURE:
|
||||
case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
|
||||
goto fail;
|
||||
case InterfaceOutRequest | USB_REQ_SET_FEATURE:
|
||||
case EndpointOutRequest | USB_REQ_SET_FEATURE:
|
||||
goto fail;
|
||||
break;
|
||||
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_DEVICE) << 8):
|
||||
if (s->config)
|
||||
usb_bt_fifo_out_enqueue(s, &s->outcmd, s->hci->cmd_send,
|
||||
usb_bt_hci_cmd_complete, p);
|
||||
break;
|
||||
default:
|
||||
fail:
|
||||
p->status = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_bt_handle_data(USBDevice *dev, USBPacket *p)
|
||||
{
|
||||
struct USBBtState *s = (struct USBBtState *) dev->opaque;
|
||||
|
||||
if (!s->config)
|
||||
goto fail;
|
||||
|
||||
switch (p->pid) {
|
||||
case USB_TOKEN_IN:
|
||||
switch (p->ep->nr) {
|
||||
case USB_EVT_EP:
|
||||
if (s->evt.len == 0) {
|
||||
p->status = USB_RET_NAK;
|
||||
break;
|
||||
}
|
||||
usb_bt_fifo_dequeue(&s->evt, p);
|
||||
break;
|
||||
|
||||
case USB_ACL_EP:
|
||||
if (s->evt.len == 0) {
|
||||
p->status = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
usb_bt_fifo_dequeue(&s->acl, p);
|
||||
break;
|
||||
|
||||
case USB_SCO_EP:
|
||||
if (s->evt.len == 0) {
|
||||
p->status = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
usb_bt_fifo_dequeue(&s->sco, p);
|
||||
break;
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_TOKEN_OUT:
|
||||
switch (p->ep->nr) {
|
||||
case USB_ACL_EP:
|
||||
usb_bt_fifo_out_enqueue(s, &s->outacl, s->hci->acl_send,
|
||||
usb_bt_hci_acl_complete, p);
|
||||
break;
|
||||
|
||||
case USB_SCO_EP:
|
||||
usb_bt_fifo_out_enqueue(s, &s->outsco, s->hci->sco_send,
|
||||
usb_bt_hci_sco_complete, p);
|
||||
break;
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
fail:
|
||||
p->status = USB_RET_STALL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_bt_out_hci_packet_event(void *opaque,
|
||||
const uint8_t *data, int len)
|
||||
{
|
||||
struct USBBtState *s = (struct USBBtState *) opaque;
|
||||
|
||||
if (s->evt.len == 0) {
|
||||
usb_wakeup(s->intr, 0);
|
||||
}
|
||||
usb_bt_fifo_enqueue(&s->evt, data, len);
|
||||
}
|
||||
|
||||
static void usb_bt_out_hci_packet_acl(void *opaque,
|
||||
const uint8_t *data, int len)
|
||||
{
|
||||
struct USBBtState *s = (struct USBBtState *) opaque;
|
||||
|
||||
usb_bt_fifo_enqueue(&s->acl, data, len);
|
||||
}
|
||||
|
||||
static void usb_bt_unrealize(USBDevice *dev, Error **errp)
|
||||
{
|
||||
struct USBBtState *s = (struct USBBtState *) dev->opaque;
|
||||
|
||||
s->hci->opaque = NULL;
|
||||
s->hci->evt_recv = NULL;
|
||||
s->hci->acl_recv = NULL;
|
||||
}
|
||||
|
||||
static void usb_bt_realize(USBDevice *dev, Error **errp)
|
||||
{
|
||||
struct USBBtState *s = USB_BT(dev);
|
||||
|
||||
usb_desc_create_serial(dev);
|
||||
usb_desc_init(dev);
|
||||
s->dev.opaque = s;
|
||||
if (!s->hci) {
|
||||
s->hci = bt_new_hci(qemu_find_bt_vlan(0));
|
||||
}
|
||||
s->hci->opaque = s;
|
||||
s->hci->evt_recv = usb_bt_out_hci_packet_event;
|
||||
s->hci->acl_recv = usb_bt_out_hci_packet_acl;
|
||||
usb_bt_handle_reset(&s->dev);
|
||||
s->intr = usb_ep_get(dev, USB_TOKEN_IN, USB_EVT_EP);
|
||||
}
|
||||
|
||||
static USBDevice *usb_bt_init(USBBus *bus, const char *cmdline)
|
||||
{
|
||||
USBDevice *dev;
|
||||
struct USBBtState *s;
|
||||
HCIInfo *hci;
|
||||
const char *name = TYPE_USB_BT;
|
||||
|
||||
if (*cmdline) {
|
||||
hci = hci_init(cmdline);
|
||||
} else {
|
||||
hci = bt_new_hci(qemu_find_bt_vlan(0));
|
||||
}
|
||||
if (!hci)
|
||||
return NULL;
|
||||
|
||||
dev = usb_create(bus, name);
|
||||
s = USB_BT(dev);
|
||||
s->hci = hci;
|
||||
return dev;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_usb_bt = {
|
||||
.name = "usb-bt",
|
||||
.unmigratable = 1,
|
||||
};
|
||||
|
||||
static void usb_bt_class_initfn(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
|
||||
|
||||
uc->realize = usb_bt_realize;
|
||||
uc->product_desc = "QEMU BT dongle";
|
||||
uc->usb_desc = &desc_bluetooth;
|
||||
uc->handle_reset = usb_bt_handle_reset;
|
||||
uc->handle_control = usb_bt_handle_control;
|
||||
uc->handle_data = usb_bt_handle_data;
|
||||
uc->unrealize = usb_bt_unrealize;
|
||||
dc->vmsd = &vmstate_usb_bt;
|
||||
set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
|
||||
}
|
||||
|
||||
static const TypeInfo bt_info = {
|
||||
.name = TYPE_USB_BT,
|
||||
.parent = TYPE_USB_DEVICE,
|
||||
.instance_size = sizeof(struct USBBtState),
|
||||
.class_init = usb_bt_class_initfn,
|
||||
};
|
||||
|
||||
static void usb_bt_register_types(void)
|
||||
{
|
||||
type_register_static(&bt_info);
|
||||
usb_legacy_register(TYPE_USB_BT, "bt", usb_bt_init);
|
||||
}
|
||||
|
||||
type_init(usb_bt_register_types)
|
2177
include/hw/bt.h
2177
include/hw/bt.h
File diff suppressed because it is too large
Load Diff
|
@ -1,20 +0,0 @@
|
|||
#ifndef SYSEMU_BT_H
|
||||
#define SYSEMU_BT_H
|
||||
|
||||
/* BT HCI info */
|
||||
|
||||
typedef struct HCIInfo {
|
||||
int (*bdaddr_set)(struct HCIInfo *hci, const uint8_t *bd_addr);
|
||||
void (*cmd_send)(struct HCIInfo *hci, const uint8_t *data, int len);
|
||||
void (*sco_send)(struct HCIInfo *hci, const uint8_t *data, int len);
|
||||
void (*acl_send)(struct HCIInfo *hci, const uint8_t *data, int len);
|
||||
void *opaque;
|
||||
void (*evt_recv)(void *opaque, const uint8_t *data, int len);
|
||||
void (*acl_recv)(void *opaque, const uint8_t *data, int len);
|
||||
} HCIInfo;
|
||||
|
||||
/* bt-host.c */
|
||||
struct HCIInfo *bt_host_hci(const char *id);
|
||||
struct HCIInfo *qemu_next_hci(void);
|
||||
|
||||
#endif
|
|
@ -67,8 +67,6 @@ int getpagesize(void);
|
|||
# define EPROTONOSUPPORT EINVAL
|
||||
#endif
|
||||
|
||||
int setenv(const char *name, const char *value, int overwrite);
|
||||
|
||||
typedef struct {
|
||||
long tv_sec;
|
||||
long tv_usec;
|
||||
|
|
22
os-win32.c
22
os-win32.c
|
@ -30,28 +30,6 @@
|
|||
#include "qemu-options.h"
|
||||
#include "sysemu/runstate.h"
|
||||
|
||||
/***********************************************************/
|
||||
/* Functions missing in mingw */
|
||||
|
||||
int setenv(const char *name, const char *value, int overwrite)
|
||||
{
|
||||
int result = 0;
|
||||
if (overwrite || !getenv(name)) {
|
||||
size_t length = strlen(name) + strlen(value) + 2;
|
||||
char *string = g_malloc(length);
|
||||
snprintf(string, length, "%s=%s", name, value);
|
||||
result = putenv(string);
|
||||
|
||||
/* Windows takes a copy and does not continue to use our string.
|
||||
* Therefore it can be safely freed on this platform. POSIX code
|
||||
* typically has to leak the string because according to the spec it
|
||||
* becomes part of the environment.
|
||||
*/
|
||||
g_free(string);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static BOOL WINAPI qemu_ctrl_handler(DWORD type)
|
||||
{
|
||||
qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_SIGNAL);
|
||||
|
|
|
@ -242,13 +242,6 @@ via the CPU ``mmu`` option when using the ``rv32`` or ``rv64`` CPUs.
|
|||
|
||||
@section System emulator devices
|
||||
|
||||
@subsection bluetooth (since 3.1)
|
||||
|
||||
The bluetooth subsystem is unmaintained since many years and likely bitrotten
|
||||
quite a bit. It will be removed without replacement unless some users speaks
|
||||
up at the @email{qemu-devel@@nongnu.org} mailing list with information about
|
||||
their usecases.
|
||||
|
||||
@subsection ide-drive (since 4.2)
|
||||
|
||||
The 'ide-drive' device is deprecated. Users should use 'ide-hd' or
|
||||
|
|
|
@ -983,21 +983,6 @@ For instance, user-mode networking can be used with
|
|||
Smartcard reader device
|
||||
@item usb-audio
|
||||
USB audio device
|
||||
@item usb-bt-dongle
|
||||
Bluetooth dongle for the transport layer of HCI. It is connected to HCI
|
||||
scatternet 0 by default (corresponds to @code{-bt hci,vlan=0}).
|
||||
Note that the syntax for the @code{-device usb-bt-dongle} option is not as
|
||||
useful yet as it was with the legacy @code{-usbdevice} option. So to
|
||||
configure an USB bluetooth device, you might need to use
|
||||
"@code{-usbdevice bt}[:@var{hci-type}]" instead. This configures a
|
||||
bluetooth dongle whose type is specified in the same format as with
|
||||
the @option{-bt hci} option, @pxref{bt-hcis,,allowed HCI types}. If
|
||||
no type is given, the HCI logic corresponds to @code{-bt hci,vlan=0}.
|
||||
This USB device implements the USB Transport Layer of HCI. Example
|
||||
usage:
|
||||
@example
|
||||
@command{@value{qemu_system}} [...@var{OPTIONS}...] @option{-usbdevice} bt:hci,vlan=3 @option{-bt} device:keyboard,vlan=3
|
||||
@end example
|
||||
@end table
|
||||
|
||||
@node host_usb_devices
|
||||
|
@ -2308,8 +2293,6 @@ Secure Digital card connected to OMAP MMC/SD host
|
|||
@item
|
||||
Three OMAP on-chip UARTs and on-chip STI debugging console
|
||||
@item
|
||||
A Bluetooth(R) transceiver and HCI connected to an UART
|
||||
@item
|
||||
Mentor Graphics "Inventra" dual-role USB controller embedded in a TI
|
||||
TUSB6010 chip - only USB host mode is supported
|
||||
@item
|
||||
|
|
|
@ -3115,85 +3115,6 @@ STEXI
|
|||
ETEXI
|
||||
DEFHEADING()
|
||||
|
||||
DEFHEADING(Bluetooth(R) options:)
|
||||
STEXI
|
||||
@table @option
|
||||
ETEXI
|
||||
|
||||
DEF("bt", HAS_ARG, QEMU_OPTION_bt, \
|
||||
"-bt hci,null dumb bluetooth HCI - doesn't respond to commands\n" \
|
||||
"-bt hci,host[:id]\n" \
|
||||
" use host's HCI with the given name\n" \
|
||||
"-bt hci[,vlan=n]\n" \
|
||||
" emulate a standard HCI in virtual scatternet 'n'\n" \
|
||||
"-bt vhci[,vlan=n]\n" \
|
||||
" add host computer to virtual scatternet 'n' using VHCI\n" \
|
||||
"-bt device:dev[,vlan=n]\n" \
|
||||
" emulate a bluetooth device 'dev' in scatternet 'n'\n",
|
||||
QEMU_ARCH_ALL)
|
||||
STEXI
|
||||
@item -bt hci[...]
|
||||
@findex -bt
|
||||
Defines the function of the corresponding Bluetooth HCI. -bt options
|
||||
are matched with the HCIs present in the chosen machine type. For
|
||||
example when emulating a machine with only one HCI built into it, only
|
||||
the first @code{-bt hci[...]} option is valid and defines the HCI's
|
||||
logic. The Transport Layer is decided by the machine type. Currently
|
||||
the machines @code{n800} and @code{n810} have one HCI and all other
|
||||
machines have none.
|
||||
|
||||
Note: This option and the whole bluetooth subsystem is considered as deprecated.
|
||||
If you still use it, please send a mail to @email{qemu-devel@@nongnu.org} where
|
||||
you describe your usecase.
|
||||
|
||||
@anchor{bt-hcis}
|
||||
The following three types are recognized:
|
||||
|
||||
@table @option
|
||||
@item -bt hci,null
|
||||
(default) The corresponding Bluetooth HCI assumes no internal logic
|
||||
and will not respond to any HCI commands or emit events.
|
||||
|
||||
@item -bt hci,host[:@var{id}]
|
||||
(@code{bluez} only) The corresponding HCI passes commands / events
|
||||
to / from the physical HCI identified by the name @var{id} (default:
|
||||
@code{hci0}) on the computer running QEMU. Only available on @code{bluez}
|
||||
capable systems like Linux.
|
||||
|
||||
@item -bt hci[,vlan=@var{n}]
|
||||
Add a virtual, standard HCI that will participate in the Bluetooth
|
||||
scatternet @var{n} (default @code{0}). Similarly to @option{-net}
|
||||
VLANs, devices inside a bluetooth network @var{n} can only communicate
|
||||
with other devices in the same network (scatternet).
|
||||
@end table
|
||||
|
||||
@item -bt vhci[,vlan=@var{n}]
|
||||
(Linux-host only) Create a HCI in scatternet @var{n} (default 0) attached
|
||||
to the host bluetooth stack instead of to the emulated target. This
|
||||
allows the host and target machines to participate in a common scatternet
|
||||
and communicate. Requires the Linux @code{vhci} driver installed. Can
|
||||
be used as following:
|
||||
|
||||
@example
|
||||
@value{qemu_system} [...OPTIONS...] -bt hci,vlan=5 -bt vhci,vlan=5
|
||||
@end example
|
||||
|
||||
@item -bt device:@var{dev}[,vlan=@var{n}]
|
||||
Emulate a bluetooth device @var{dev} and place it in network @var{n}
|
||||
(default @code{0}). QEMU can only emulate one type of bluetooth devices
|
||||
currently:
|
||||
|
||||
@table @option
|
||||
@item keyboard
|
||||
Virtual wireless keyboard implementing the HIDP bluetooth profile.
|
||||
@end table
|
||||
ETEXI
|
||||
|
||||
STEXI
|
||||
@end table
|
||||
ETEXI
|
||||
DEFHEADING()
|
||||
|
||||
#ifdef CONFIG_TPM
|
||||
DEFHEADING(TPM device options:)
|
||||
|
||||
|
|
|
@ -16,12 +16,10 @@ check-help:
|
|||
@echo " $(MAKE) check-softfloat Run FPU emulation tests"
|
||||
@echo " $(MAKE) check-acceptance Run all acceptance (functional) tests"
|
||||
@echo
|
||||
@echo " $(MAKE) check-report.html Generates an HTML test report"
|
||||
@echo " $(MAKE) check-report.tap Generates an aggregated TAP test report"
|
||||
@echo " $(MAKE) check-venv Creates a Python venv for tests"
|
||||
@echo " $(MAKE) check-clean Clean the tests and related data"
|
||||
@echo
|
||||
@echo "Please note that HTML reports do not regenerate if the unit tests"
|
||||
@echo "have not changed."
|
||||
@echo
|
||||
@echo "The variable SPEED can be set to control the gtester speed setting."
|
||||
@echo "Default options are -k and (for $(MAKE) V=1) --verbose; they can be"
|
||||
|
|
|
@ -2,7 +2,6 @@ FROM fedora:30
|
|||
ENV PACKAGES \
|
||||
bc \
|
||||
bison \
|
||||
bluez-libs-devel \
|
||||
brlapi-devel \
|
||||
bzip2 \
|
||||
bzip2-devel \
|
||||
|
|
|
@ -19,7 +19,6 @@ ENV PACKAGES flex bison \
|
|||
glusterfs-common \
|
||||
libaio-dev \
|
||||
libattr1-dev \
|
||||
libbluetooth-dev \
|
||||
libbrlapi-dev \
|
||||
libbz2-dev \
|
||||
libcacard-dev \
|
||||
|
|
|
@ -8,7 +8,6 @@ ENV PACKAGES flex bison \
|
|||
glusterfs-common \
|
||||
libaio-dev \
|
||||
libattr1-dev \
|
||||
libbluetooth-dev \
|
||||
libbrlapi-dev \
|
||||
libbz2-dev \
|
||||
libcacard-dev \
|
||||
|
|
|
@ -443,7 +443,7 @@ static gchar *mktempshm(int size, int *fd)
|
|||
while (true) {
|
||||
gchar *name;
|
||||
|
||||
name = g_strdup_printf("/qtest-%u-%u", getpid(), g_random_int());
|
||||
name = g_strdup_printf("/qtest-%u-%u", getpid(), g_test_rand_int());
|
||||
*fd = shm_open(name, O_CREAT|O_RDWR|O_EXCL,
|
||||
S_IRWXU|S_IRWXG|S_IRWXO);
|
||||
if (*fd > 0) {
|
||||
|
|
|
@ -254,7 +254,7 @@ QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
|
|||
s->expected_status = 0;
|
||||
s->qemu_pid = fork();
|
||||
if (s->qemu_pid == 0) {
|
||||
setenv("QEMU_AUDIO_DRV", "none", true);
|
||||
g_setenv("QEMU_AUDIO_DRV", "none", true);
|
||||
execlp("/bin/sh", "sh", "-c", command, NULL);
|
||||
exit(1);
|
||||
}
|
||||
|
|
|
@ -1349,7 +1349,8 @@ int main(int argc, char **argv)
|
|||
* some reason)
|
||||
*/
|
||||
if (g_str_equal(qtest_get_arch(), "ppc64") &&
|
||||
access("/sys/module/kvm_hv", F_OK)) {
|
||||
(access("/sys/module/kvm_hv", F_OK) ||
|
||||
access("/dev/kvm", R_OK | W_OK))) {
|
||||
g_test_message("Skipping test: kvm_hv not available");
|
||||
return g_test_run();
|
||||
}
|
||||
|
|
|
@ -64,7 +64,8 @@ int main(int argc, char *argv[])
|
|||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS(modules); i += 2) {
|
||||
char *testname = g_strdup_printf("/module/load/%s", modules[i + 1]);
|
||||
char *testname = g_strdup_printf("/module/load/%s%s",
|
||||
modules[i], modules[i + 1]);
|
||||
qtest_add_data_func(testname, modules + i, test_modules_load);
|
||||
g_free(testname);
|
||||
}
|
||||
|
|
|
@ -22,10 +22,10 @@ static void check_bitmap_copy_with_offset(void)
|
|||
bmap2 = bitmap_new(BMAP_SIZE);
|
||||
bmap3 = bitmap_new(BMAP_SIZE);
|
||||
|
||||
bmap1[0] = random();
|
||||
bmap1[1] = random();
|
||||
bmap1[2] = random();
|
||||
bmap1[3] = random();
|
||||
bmap1[0] = g_test_rand_int();
|
||||
bmap1[1] = g_test_rand_int();
|
||||
bmap1[2] = g_test_rand_int();
|
||||
bmap1[3] = g_test_rand_int();
|
||||
total = BITS_PER_LONG * 4;
|
||||
|
||||
/* Shift 115 bits into bmap2 */
|
||||
|
|
|
@ -141,7 +141,7 @@ int main(int argc, char **argv)
|
|||
|
||||
module_call_init(MODULE_INIT_QOM);
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
setenv("GNUTLS_FORCE_FIPS_MODE", "2", 1);
|
||||
g_setenv("GNUTLS_FORCE_FIPS_MODE", "2", 1);
|
||||
|
||||
mkdir(WORKDIR, 0700);
|
||||
|
||||
|
|
|
@ -398,7 +398,7 @@ int main(int argc, char **argv)
|
|||
|
||||
module_call_init(MODULE_INIT_QOM);
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
setenv("GNUTLS_FORCE_FIPS_MODE", "2", 1);
|
||||
g_setenv("GNUTLS_FORCE_FIPS_MODE", "2", 1);
|
||||
|
||||
mkdir(WORKDIR, 0700);
|
||||
|
||||
|
|
|
@ -273,7 +273,7 @@ int main(int argc, char **argv)
|
|||
|
||||
module_call_init(MODULE_INIT_QOM);
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
setenv("GNUTLS_FORCE_FIPS_MODE", "2", 1);
|
||||
g_setenv("GNUTLS_FORCE_FIPS_MODE", "2", 1);
|
||||
|
||||
mkdir(WORKDIR, 0700);
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ static void qmp_assertion_message_error(const char *domain,
|
|||
static void test_qga_sync_delimited(gconstpointer fix)
|
||||
{
|
||||
const TestFixture *fixture = fix;
|
||||
guint32 v, r = g_random_int();
|
||||
guint32 v, r = g_test_rand_int();
|
||||
unsigned char c;
|
||||
QDict *ret;
|
||||
|
||||
|
@ -186,7 +186,7 @@ static void test_qga_sync_delimited(gconstpointer fix)
|
|||
static void test_qga_sync(gconstpointer fix)
|
||||
{
|
||||
const TestFixture *fixture = fix;
|
||||
guint32 v, r = g_random_int();
|
||||
guint32 v, r = g_test_rand_int();
|
||||
QDict *ret;
|
||||
|
||||
/*
|
||||
|
|
|
@ -1325,7 +1325,7 @@ int main(int argc, char **argv)
|
|||
|
||||
module_call_init(MODULE_INIT_QOM);
|
||||
|
||||
setenv("QTEST_SILENT_ERRORS", "1", 1);
|
||||
g_setenv("QTEST_SILENT_ERRORS", "1", 1);
|
||||
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
g_test_add_func("/vmstate/simple/primitive", test_simple_primitive);
|
||||
|
|
|
@ -772,7 +772,7 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
|
|||
* This is a bit hackish but saves us from bigger problem.
|
||||
* Maybe it's a good idea to fix this in SDL instead.
|
||||
*/
|
||||
setenv("SDL_VIDEODRIVER", "x11", 0);
|
||||
g_setenv("SDL_VIDEODRIVER", "x11", 0);
|
||||
#endif
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO)) {
|
||||
|
|
136
vl.c
136
vl.c
|
@ -62,14 +62,12 @@ int main(int argc, char **argv)
|
|||
#include "hw/isa/isa.h"
|
||||
#include "hw/scsi/scsi.h"
|
||||
#include "hw/display/vga.h"
|
||||
#include "hw/bt.h"
|
||||
#include "sysemu/watchdog.h"
|
||||
#include "hw/firmware/smbios.h"
|
||||
#include "hw/acpi/acpi.h"
|
||||
#include "hw/xen/xen.h"
|
||||
#include "hw/loader.h"
|
||||
#include "monitor/qdev.h"
|
||||
#include "sysemu/bt.h"
|
||||
#include "net/net.h"
|
||||
#include "net/slirp.h"
|
||||
#include "monitor/monitor.h"
|
||||
|
@ -914,128 +912,6 @@ static void configure_rtc(QemuOpts *opts)
|
|||
}
|
||||
}
|
||||
|
||||
/***********************************************************/
|
||||
/* Bluetooth support */
|
||||
static int nb_hcis;
|
||||
static int cur_hci;
|
||||
static struct HCIInfo *hci_table[MAX_NICS];
|
||||
|
||||
struct HCIInfo *qemu_next_hci(void)
|
||||
{
|
||||
if (cur_hci == nb_hcis)
|
||||
return &null_hci;
|
||||
|
||||
return hci_table[cur_hci++];
|
||||
}
|
||||
|
||||
static int bt_hci_parse(const char *str)
|
||||
{
|
||||
struct HCIInfo *hci;
|
||||
bdaddr_t bdaddr;
|
||||
|
||||
if (nb_hcis >= MAX_NICS) {
|
||||
error_report("too many bluetooth HCIs (max %i)", MAX_NICS);
|
||||
return -1;
|
||||
}
|
||||
|
||||
hci = hci_init(str);
|
||||
if (!hci)
|
||||
return -1;
|
||||
|
||||
bdaddr.b[0] = 0x52;
|
||||
bdaddr.b[1] = 0x54;
|
||||
bdaddr.b[2] = 0x00;
|
||||
bdaddr.b[3] = 0x12;
|
||||
bdaddr.b[4] = 0x34;
|
||||
bdaddr.b[5] = 0x56 + nb_hcis;
|
||||
hci->bdaddr_set(hci, bdaddr.b);
|
||||
|
||||
hci_table[nb_hcis++] = hci;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bt_vhci_add(int vlan_id)
|
||||
{
|
||||
struct bt_scatternet_s *vlan = qemu_find_bt_vlan(vlan_id);
|
||||
|
||||
if (!vlan->slave)
|
||||
warn_report("adding a VHCI to an empty scatternet %i",
|
||||
vlan_id);
|
||||
|
||||
bt_vhci_init(bt_new_hci(vlan));
|
||||
}
|
||||
|
||||
static struct bt_device_s *bt_device_add(const char *opt)
|
||||
{
|
||||
struct bt_scatternet_s *vlan;
|
||||
int vlan_id = 0;
|
||||
char *endp = strstr(opt, ",vlan=");
|
||||
int len = (endp ? endp - opt : strlen(opt)) + 1;
|
||||
char devname[10];
|
||||
|
||||
pstrcpy(devname, MIN(sizeof(devname), len), opt);
|
||||
|
||||
if (endp) {
|
||||
vlan_id = strtol(endp + 6, &endp, 0);
|
||||
if (*endp) {
|
||||
error_report("unrecognised bluetooth vlan Id");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
vlan = qemu_find_bt_vlan(vlan_id);
|
||||
|
||||
if (!vlan->slave)
|
||||
warn_report("adding a slave device to an empty scatternet %i",
|
||||
vlan_id);
|
||||
|
||||
if (!strcmp(devname, "keyboard"))
|
||||
return bt_keyboard_init(vlan);
|
||||
|
||||
error_report("unsupported bluetooth device '%s'", devname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bt_parse(const char *opt)
|
||||
{
|
||||
const char *endp, *p;
|
||||
int vlan;
|
||||
|
||||
if (strstart(opt, "hci", &endp)) {
|
||||
if (!*endp || *endp == ',') {
|
||||
if (*endp)
|
||||
if (!strstart(endp, ",vlan=", 0))
|
||||
opt = endp + 1;
|
||||
|
||||
return bt_hci_parse(opt);
|
||||
}
|
||||
} else if (strstart(opt, "vhci", &endp)) {
|
||||
if (!*endp || *endp == ',') {
|
||||
if (*endp) {
|
||||
if (strstart(endp, ",vlan=", &p)) {
|
||||
vlan = strtol(p, (char **) &endp, 0);
|
||||
if (*endp) {
|
||||
error_report("bad scatternet '%s'", p);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
error_report("bad parameter '%s'", endp + 1);
|
||||
return 1;
|
||||
}
|
||||
} else
|
||||
vlan = 0;
|
||||
|
||||
bt_vhci_add(vlan);
|
||||
return 0;
|
||||
}
|
||||
} else if (strstart(opt, "device:", &endp))
|
||||
return !bt_device_add(endp);
|
||||
|
||||
error_report("bad bluetooth parameter '%s'", opt);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int parse_name(void *opaque, QemuOpts *opts, Error **errp)
|
||||
{
|
||||
const char *proc_name;
|
||||
|
@ -2319,7 +2195,6 @@ static void monitor_parse(const char *optarg, const char *mode, bool pretty)
|
|||
struct device_config {
|
||||
enum {
|
||||
DEV_USB, /* -usbdevice */
|
||||
DEV_BT, /* -bt */
|
||||
DEV_SERIAL, /* -serial */
|
||||
DEV_PARALLEL, /* -parallel */
|
||||
DEV_DEBUGCON, /* -debugcon */
|
||||
|
@ -3128,13 +3003,6 @@ int main(int argc, char **argv, char **envp)
|
|||
}
|
||||
break;
|
||||
#endif
|
||||
case QEMU_OPTION_bt:
|
||||
warn_report("The bluetooth subsystem is deprecated and will "
|
||||
"be removed soon. If the bluetooth subsystem is "
|
||||
"still useful for you, please send a mail to "
|
||||
"qemu-devel@nongnu.org with your usecase.");
|
||||
add_device_config(DEV_BT, optarg);
|
||||
break;
|
||||
case QEMU_OPTION_audio_help:
|
||||
audio_legacy_help();
|
||||
exit (0);
|
||||
|
@ -4259,10 +4127,6 @@ int main(int argc, char **argv, char **envp)
|
|||
|
||||
tpm_init();
|
||||
|
||||
/* init the bluetooth world */
|
||||
if (foreach_device_config(DEV_BT, bt_parse))
|
||||
exit(1);
|
||||
|
||||
if (!xen_enabled()) {
|
||||
/* On 32-bit hosts, QEMU is limited by virtual address space */
|
||||
if (ram_size > (2047 << 20) && HOST_LONG_BITS == 32) {
|
||||
|
|
Loading…
Reference in New Issue