1
0
mirror of https://gitlab.com/qemu-project/qemu synced 2024-07-08 20:17:27 +00:00

Remove the core bluetooth code

It's been deprecated since QEMU v3.1. We've explicitly asked in the
deprecation message that people should speak up on qemu-devel in case
they are still actively using the bluetooth part of QEMU, but nobody
ever replied that they are really still using it.

I've tried it on my own to use this bluetooth subsystem for one of my
guests, but I was also not able to get it running anymore: When I was
trying to pass-through a real bluetooth device, either the guest did
not see the device at all, or the guest crashed.

Even worse for the emulated device: When running

 qemu-system-x86_64 -bt device:keyboard

QEMU crashes once you hit a key.

So it seems like the bluetooth stack is not only neglected, it is
completely bitrotten, as far as I can tell. The only attention that
this code got during the past years were some CVEs that have been
spotted there. So this code is a burden for the developers, without
any real benefit anymore. Time to remove it.

Note: hw/bt/Kconfig only gets cleared but not removed here yet.
Otherwise there is a problem with the *-softmmu/config-devices.mak.d
dependency files - they still contain a reference to this file which
gets evaluated first on some build hosts, before the file gets
properly recreated. To avoid breaking these builders, we still need
the file around for some time. It will get removed in a couple of
weeks instead.

Message-Id: <20191120091014.16883-4-thuth@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
Acked-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Thomas Huth <thuth@redhat.com>
This commit is contained in:
Thomas Huth 2019-11-20 10:10:13 +01:00
parent 43d68d0a94
commit 1d4ffe8dc7
19 changed files with 0 additions and 8651 deletions

View File

@ -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
View File

@ -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
View File

@ -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);
}

31
configure vendored
View File

@ -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

View File

@ -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

View File

@ -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/

View File

@ -1,2 +0,0 @@
config BLUETOOTH
bool

View File

@ -1,3 +0,0 @@
common-obj-y += core.o l2cap.o sdp.o hci.o hid.o
common-obj-y += hci-csr.o

View File

@ -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;
}

View File

@ -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);

File diff suppressed because it is too large Load Diff

View File

@ -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);
}

File diff suppressed because it is too large Load Diff

View File

@ -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);
}

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -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:)

136
vl.c
View File

@ -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) {