From fb599c9a030ac438755627f5919bfc40a57a0b9e Mon Sep 17 00:00:00 2001 From: balrog Date: Sun, 28 Sep 2008 23:49:55 +0000 Subject: [PATCH] Implement a HCI passthrough to host. This allows using a host's physical HCI as one of the HCIs attached to the virtual machine. This brings various limitations because not all commands/events are passed through by Linux kernel, some are interpreted by the host's kernel for a speed gain. git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@5344 c046a42c-6fe2-441c-8c8c-71466251a162 --- Makefile | 5 +- Makefile.target | 4 + bt-host.c | 198 ++++++++++++++++++++++++++++++++++++++++++++++++ configure | 20 +++++ 4 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 bt-host.c diff --git a/Makefile b/Makefile index 9e6f760f1..49e439883 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ OBJS+=scsi-generic.o OBJS+=usb.o usb-hub.o usb-linux.o usb-hid.o usb-msd.o usb-wacom.o OBJS+=usb-serial.o usb-net.o OBJS+=sd.o ssi-sd.o -OBJS+=bt.o +OBJS+=bt.o bt-host.o ifdef CONFIG_BRLAPI OBJS+= baum.o @@ -166,6 +166,9 @@ vnc.o: vnc.c keymaps.c sdl_keysym.h vnchextile.h d3des.c d3des.h curses.o: curses.c keymaps.c curses_keys.h $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< +bt-host.o: bt-host.c + $(CC) $(CFLAGS) $(CPPFLAGS) $(CONFIG_BLUEZ_CFLAGS) -c -o $@ $< + audio/sdlaudio.o: audio/sdlaudio.c $(CC) $(CFLAGS) $(CPPFLAGS) $(SDL_CFLAGS) -c -o $@ $< diff --git a/Makefile.target b/Makefile.target index 632afc18a..c2016d1e1 100644 --- a/Makefile.target +++ b/Makefile.target @@ -524,6 +524,10 @@ CPPFLAGS += $(CONFIG_VNC_TLS_CFLAGS) LIBS += $(CONFIG_VNC_TLS_LIBS) endif +ifdef CONFIG_BLUEZ +LIBS += $(CONFIG_BLUEZ_LIBS) +endif + # SCSI layer OBJS+= lsi53c895a.o esp.o diff --git a/bt-host.c b/bt-host.c new file mode 100644 index 000000000..5d3e8405b --- /dev/null +++ b/bt-host.c @@ -0,0 +1,198 @@ +/* + * Wrap a host Bluetooth HCI socket in a struct HCIInfo. + * + * Copyright (C) 2008 Andrzej Zaborowski + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include "qemu-common.h" +#include "qemu-char.h" +#include "sysemu.h" +#include "net.h" + +#include +#include +#include +#ifdef CONFIG_BLUEZ +# include +# include +# include +#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]; + int ret; + + iv[0].iov_base = &pkt; + iv[0].iov_len = 1; + iv[1].iov_base = (void *) data; + iv[1].iov_len = len; + + while ((ret = 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 int bt_host_read_poll(void *opaque) +{ + struct bt_host_hci_s *s = (struct bt_host_hci_s *) opaque; + + return !!s->hci.evt_recv; +} + +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; + + 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 0; + } + +#ifdef CONFIG_BLUEZ + hci_filter_clear(&flt); + hci_filter_all_ptypes(&flt); + hci_filter_all_events(&flt); + + if (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 = qemu_mallocz(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_handler2(s->fd, bt_host_read_poll, bt_host_read, 0, s); + + return &s->hci; +} diff --git a/configure b/configure index 5030558a4..826da7c01 100755 --- a/configure +++ b/configure @@ -110,6 +110,7 @@ curses="yes" aio="yes" nptl="yes" mixemu="no" +bluez="yes" signalfd="no" eventfd="no" @@ -293,6 +294,8 @@ for opt do ;; --disable-brlapi) brlapi="no" ;; + --disable-bluez) bluez="no" + ;; --enable-profiler) profiler="yes" ;; --enable-cocoa) @@ -429,6 +432,7 @@ echo " --enable-mixemu enable mixer emulation" echo " --disable-brlapi disable BrlAPI" echo " --disable-vnc-tls disable TLS encryption for VNC server" echo " --disable-curses disable curses output" +echo " --disable-bluez disable bluez stack connectivity" echo " --disable-nptl disable usermode NPTL support" echo " --enable-system enable all system emulation targets" echo " --disable-system disable all system emulation targets" @@ -890,6 +894,16 @@ EOF fi fi # test "$curses" +########################################## +# bluez support probe +if test "$bluez" = "yes" ; then + `pkg-config bluez` || bluez="no" +fi +if test "$bluez" = "yes" ; then + bluez_cflags=`pkg-config --cflags bluez` + bluez_libs=`pkg-config --libs bluez` +fi + ########################################## # AIO probe if test "$aio" = "yes" ; then @@ -1254,6 +1268,12 @@ if test "$brlapi" = "yes" ; then echo "#define CONFIG_BRLAPI 1" >> $config_h echo "BRLAPI_LIBS=-lbrlapi" >> $config_mak fi +if test "$bluez" = "yes" ; then + echo "CONFIG_BLUEZ=yes" >> $config_mak + echo "CONFIG_BLUEZ_CFLAGS=$bluez_cflags" >> $config_mak + echo "CONFIG_BLUEZ_LIBS=$bluez_libs" >> $config_mak + echo "#define CONFIG_BLUEZ 1" >> $config_h +fi if test "$aio" = "yes" ; then echo "#define CONFIG_AIO 1" >> $config_h echo "CONFIG_AIO=yes" >> $config_mak -- 2.39.2