diff options
Diffstat (limited to 'usr.sbin/bluetooth/bthidd')
-rw-r--r-- | usr.sbin/bluetooth/bthidd/Makefile | 16 | ||||
-rw-r--r-- | usr.sbin/bluetooth/bthidd/Makefile.depend | 27 | ||||
-rw-r--r-- | usr.sbin/bluetooth/bthidd/bthid_config.h | 70 | ||||
-rw-r--r-- | usr.sbin/bluetooth/bthidd/bthidd.8 | 127 | ||||
-rw-r--r-- | usr.sbin/bluetooth/bthidd/bthidd.c | 267 | ||||
-rw-r--r-- | usr.sbin/bluetooth/bthidd/bthidd.conf.sample | 72 | ||||
-rw-r--r-- | usr.sbin/bluetooth/bthidd/bthidd.h | 93 | ||||
-rw-r--r-- | usr.sbin/bluetooth/bthidd/client.c | 259 | ||||
-rw-r--r-- | usr.sbin/bluetooth/bthidd/hid.c | 415 | ||||
-rw-r--r-- | usr.sbin/bluetooth/bthidd/kbd.c | 581 | ||||
-rw-r--r-- | usr.sbin/bluetooth/bthidd/kbd.h | 41 | ||||
-rw-r--r-- | usr.sbin/bluetooth/bthidd/lexer.l | 106 | ||||
-rw-r--r-- | usr.sbin/bluetooth/bthidd/parser.y | 476 | ||||
-rw-r--r-- | usr.sbin/bluetooth/bthidd/server.c | 352 | ||||
-rw-r--r-- | usr.sbin/bluetooth/bthidd/session.c | 185 |
15 files changed, 3087 insertions, 0 deletions
diff --git a/usr.sbin/bluetooth/bthidd/Makefile b/usr.sbin/bluetooth/bthidd/Makefile new file mode 100644 index 0000000..f36d216 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/Makefile @@ -0,0 +1,16 @@ +# $Id: Makefile,v 1.6 2006/09/07 21:36:55 max Exp $ +# $FreeBSD$ + +PROG= bthidd +MAN= bthidd.8 +# bthidd.conf.5 +SRCS= bthidd.c client.c hid.c kbd.c lexer.l parser.y server.c \ + session.c + +CFLAGS+= -I${.CURDIR} + +LIBADD+= bluetooth usbhid + +NO_WMISSING_VARIABLE_DECLARATIONS= + +.include <bsd.prog.mk> diff --git a/usr.sbin/bluetooth/bthidd/Makefile.depend b/usr.sbin/bluetooth/bthidd/Makefile.depend new file mode 100644 index 0000000..a1ed9d7 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/Makefile.depend @@ -0,0 +1,27 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + gnu/lib/libgcc \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libbluetooth \ + lib/libc \ + lib/libcompiler_rt \ + lib/libusbhid \ + usr.bin/yacc.host \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +lexer.o: lexer.c +lexer.o: parser.h +lexer.po: lexer.c +lexer.po: parser.h +parser.o: parser.c +parser.po: parser.c +.endif diff --git a/usr.sbin/bluetooth/bthidd/bthid_config.h b/usr.sbin/bluetooth/bthidd/bthid_config.h new file mode 100644 index 0000000..71cb425 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/bthid_config.h @@ -0,0 +1,70 @@ +/* + * bthid_config.h + */ + +/*- + * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: bthid_config.h,v 1.4 2006/09/07 21:06:53 max Exp $ + * $FreeBSD$ + */ + +#ifndef _BTHID_CONFIG_H_ +#define _BTHID_CONFIG_H_ 1 + +#define BTHIDD_CONFFILE "/etc/bluetooth/bthidd.conf" +#define BTHIDD_HIDSFILE "/var/db/bthidd.hids" + +struct hid_device +{ + bdaddr_t bdaddr; /* HID device BDADDR */ + uint16_t control_psm; /* control PSM */ + uint16_t interrupt_psm; /* interrupt PSM */ + unsigned new_device : 1; + unsigned reconnect_initiate : 1; + unsigned battery_power : 1; + unsigned normally_connectable : 1; + unsigned keyboard : 1; + unsigned reserved : 11; + report_desc_t desc; /* HID report descriptor */ + LIST_ENTRY(hid_device) next; /* link to the next */ +}; +typedef struct hid_device hid_device_t; +typedef struct hid_device * hid_device_p; + +extern char const *config_file; +extern char const *hids_file; + +int32_t read_config_file (void); +void clean_config (void); +hid_device_p get_hid_device (bdaddr_p bdaddr); +hid_device_p get_next_hid_device (hid_device_p d); +void print_hid_device (hid_device_p hid_device, FILE *f); + +int32_t read_hids_file (void); +int32_t write_hids_file (void); + +#endif /* ndef _BTHID_CONFIG_H_ */ + diff --git a/usr.sbin/bluetooth/bthidd/bthidd.8 b/usr.sbin/bluetooth/bthidd/bthidd.8 new file mode 100644 index 0000000..53db4cb --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/bthidd.8 @@ -0,0 +1,127 @@ +.\" Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com> +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $Id: bthidd.8,v 1.1 2006/09/07 21:36:55 max Exp $ +.\" $FreeBSD$ +.\" +.Dd September 7, 2006 +.Dt BTHIDD 8 +.Os +.Sh NAME +.Nm bthidd +.Nd Bluetooth HID daemon +.Sh SYNOPSIS +.Nm +.Fl h +.Nm +.Op Fl a Ar BD_ADDR +.Op Fl c Ar file +.Op Fl H Ar file +.Op Fl p Ar file +.Op Fl t Ar val +.Sh DESCRIPTION +The +.Nm +daemon handles remote Bluetooth HID devices. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl a Ar BD_ADDR +Specify the local address to listen on. +By default, the server will listen on +.Dv ANY +address. +The address can be specified as BD_ADDR or name. +If a name was specified, the +.Nm +daemon will attempt to resolve the name via +.Xr bt_gethostbyname 3 . +.It Fl c Ar file +Specify path to the configuration file. +The default path is +.Pa /etc/bluetooth/bthidd.conf . +.It Fl d +Do not detach from the controlling terminal, i.e., run in foreground. +.It Fl H Ar file +Specify path to the known HIDs file. +The default path is +.Pa /var/db/bthidd.hids . +.It Fl h +Display usage message and exit. +.It Fl p Ar file +Specify path to the PID file. +The default path is +.Pa /var/run/bthidd.pid . +.It Fl t Ar val +Specify client rescan interval in seconds. +The +.Nm +daemon will periodically scan for newly configured Bluetooth HID devices or +disconnected +.Dq passive +Bluetooth HID devices and will attempt to establish an outgoing connection. +The default rescan interval is 10 seconds. +.El +.Sh KNOWN LIMITATIONS +The +.Nm +daemon currently does not handle key auto repeat and double click mouse events. +Those events work under +.Xr X 7 +just fine, +but not in text console. +.Pp +This manual page needs more work. +A manual page documenting the format of the +.Pa /etc/bluetooth/bthidd.conf +configuration file is needed as well. +.Sh FILES +.Bl -tag -width ".Pa /etc/bluetooth/bthidd.conf" -compact +.It Pa /etc/bluetooth/bthidd.conf +.It Pa /var/db/bthidd.hids +.It Pa /var/run/bthidd.pid +.El +.Sh SEE ALSO +.Xr kbdmux 4 , +.Xr vkbd 4 , +.Xr bthidcontrol 8 +.Sh AUTHORS +.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com +.Sh CAVEATS +Any Bluetooth HID device that has +.Dv HUP_KEYBOARD +or +.Dv HUP_CONSUMER +entries in its descriptor is considered as +.Dq keyboard . +For each +.Dq keyboard +Bluetooth HID device, +the +.Nm +daemon will use a separate instance of the virtual keyboard interface +.Xr vkbd 4 . +Therefore the +.Xr kbdmux 4 +driver must be used to properly multiplex input from multiple keyboards. diff --git a/usr.sbin/bluetooth/bthidd/bthidd.c b/usr.sbin/bluetooth/bthidd/bthidd.c new file mode 100644 index 0000000..7e988fc --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/bthidd.c @@ -0,0 +1,267 @@ +/* + * bthidd.c + */ + +/*- + * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: bthidd.c,v 1.8 2006/09/07 21:06:53 max Exp $ + * $FreeBSD$ + */ + +#include <sys/time.h> +#include <sys/queue.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <err.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <usbhid.h> +#include "bthid_config.h" +#include "bthidd.h" + +static int32_t write_pid_file (char const *file); +static int32_t remove_pid_file (char const *file); +static int32_t elapsed (int32_t tval); +static void sighandler (int32_t s); +static void usage (void); + +/* + * bthidd + */ + +static int32_t done = 0; /* are we done? */ + +int32_t +main(int32_t argc, char *argv[]) +{ + struct bthid_server srv; + struct sigaction sa; + char const *pid_file = BTHIDD_PIDFILE; + char *ep; + int32_t opt, detach, tval; + + memset(&srv, 0, sizeof(srv)); + memset(&srv.bdaddr, 0, sizeof(srv.bdaddr)); + detach = 1; + tval = 10; /* sec */ + + while ((opt = getopt(argc, argv, "a:c:dH:hp:t:")) != -1) { + switch (opt) { + case 'a': /* BDADDR */ + if (!bt_aton(optarg, &srv.bdaddr)) { + struct hostent *he; + + if ((he = bt_gethostbyname(optarg)) == NULL) + errx(1, "%s: %s", optarg, hstrerror(h_errno)); + + memcpy(&srv.bdaddr, he->h_addr, sizeof(srv.bdaddr)); + } + break; + + case 'c': /* config file */ + config_file = optarg; + break; + + case 'd': /* do not detach */ + detach = 0; + break; + + case 'H': /* hids file */ + hids_file = optarg; + break; + + case 'p': /* pid file */ + pid_file = optarg; + break; + + case 't': /* rescan interval */ + tval = strtol(optarg, (char **) &ep, 10); + if (*ep != '\0' || tval <= 0) + usage(); + break; + + case 'h': + default: + usage(); + /* NOT REACHED */ + } + } + + openlog(BTHIDD_IDENT, LOG_PID|LOG_PERROR|LOG_NDELAY, LOG_USER); + + /* Become daemon if required */ + if (detach && daemon(0, 0) < 0) { + syslog(LOG_CRIT, "Could not become daemon. %s (%d)", + strerror(errno), errno); + exit(1); + } + + /* Install signal handler */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sighandler; + + if (sigaction(SIGTERM, &sa, NULL) < 0 || + sigaction(SIGHUP, &sa, NULL) < 0 || + sigaction(SIGINT, &sa, NULL) < 0) { + syslog(LOG_CRIT, "Could not install signal handlers. %s (%d)", + strerror(errno), errno); + exit(1); + } + + sa.sa_handler = SIG_IGN; + if (sigaction(SIGPIPE, &sa, NULL) < 0) { + syslog(LOG_CRIT, "Could not install signal handlers. %s (%d)", + strerror(errno), errno); + exit(1); + } + + sa.sa_handler = SIG_IGN; + sa.sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT; + if (sigaction(SIGCHLD, &sa, NULL) < 0) { + syslog(LOG_CRIT, "Could not install signal handlers. %s (%d)", + strerror(errno), errno); + exit(1); + } + + if (read_config_file() < 0 || read_hids_file() < 0 || + server_init(&srv) < 0 || write_pid_file(pid_file) < 0) + exit(1); + + for (done = 0; !done; ) { + if (elapsed(tval)) + client_rescan(&srv); + + if (server_do(&srv) < 0) + break; + } + + server_shutdown(&srv); + remove_pid_file(pid_file); + clean_config(); + closelog(); + + return (0); +} + +/* + * Write pid file + */ + +static int32_t +write_pid_file(char const *file) +{ + FILE *pid; + + assert(file != NULL); + + if ((pid = fopen(file, "w")) == NULL) { + syslog(LOG_ERR, "Could not open file %s. %s (%d)", + file, strerror(errno), errno); + return (-1); + } + + fprintf(pid, "%d", getpid()); + fclose(pid); + + return (0); +} + +/* + * Remote pid file + */ + +static int32_t +remove_pid_file(char const *file) +{ + assert(file != NULL); + + if (unlink(file) < 0) { + syslog(LOG_ERR, "Could not unlink file %s. %s (%d)", + file, strerror(errno), errno); + return (-1); + } + + return (0); +} + +/* + * Returns true if desired time interval has elapsed + */ + +static int32_t +elapsed(int32_t tval) +{ + static struct timeval last = { 0, 0 }; + struct timeval now; + + gettimeofday(&now, NULL); + + if (now.tv_sec - last.tv_sec >= tval) { + last = now; + return (1); + } + + return (0); +} + +/* + * Signal handler + */ + +static void +sighandler(int32_t s) +{ + syslog(LOG_NOTICE, "Got signal %d, total number of signals %d", + s, ++ done); +} + +/* + * Display usage and exit + */ + +static void +usage(void) +{ + fprintf(stderr, +"Usage: %s [options]\n" \ +"Where options are:\n" \ +" -a address specify address to listen on (default ANY)\n" \ +" -c file specify config file name\n" \ +" -d run in foreground\n" \ +" -H file specify known HIDs file name\n" \ +" -h display this message\n" \ +" -p file specify PID file name\n" \ +" -t tval specify client rescan interval (sec)\n" \ +"", BTHIDD_IDENT); + exit(255); +} + diff --git a/usr.sbin/bluetooth/bthidd/bthidd.conf.sample b/usr.sbin/bluetooth/bthidd/bthidd.conf.sample new file mode 100644 index 0000000..e23dc12 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/bthidd.conf.sample @@ -0,0 +1,72 @@ +# $FreeBSD$ + +device { + bdaddr 00:50:f2:e5:68:84; + control_psm 0x11; + interrupt_psm 0x13; + reconnect_initiate true; + normally_connectable false; + hid_descriptor { + 0x05 0x01 0x09 0x02 0xa1 0x01 0x85 0x02 + 0x09 0x01 0xa1 0x00 0x05 0x09 0x19 0x01 + 0x29 0x05 0x15 0x00 0x25 0x01 0x75 0x01 + 0x95 0x05 0x81 0x02 0x75 0x03 0x95 0x01 + 0x81 0x01 0x05 0x01 0x09 0x30 0x09 0x31 + 0x09 0x38 0x15 0x81 0x25 0x7f 0x75 0x08 + 0x95 0x03 0x81 0x06 0xc0 0xc0 0x05 0x0c + 0x09 0x01 0xa1 0x01 0x85 0x03 0x05 0x01 + 0x09 0x02 0xa1 0x02 0x06 0x00 0xff 0x15 + 0x00 0x25 0x03 0x95 0x01 0x75 0x02 0x0a + 0x01 0xfe 0x81 0x02 0x75 0x06 0x81 0x01 + 0xc0 0xc0 + }; +} + +device { + bdaddr 00:50:f2:e3:fb:e1; + control_psm 0x11; + interrupt_psm 0x13; + reconnect_initiate true; + normally_connectable false; + hid_descriptor { + 0x05 0x01 0x09 0x06 0xa1 0x01 0x85 0x01 + 0x05 0x08 0x19 0x01 0x29 0x03 0x15 0x00 + 0x25 0x01 0x75 0x01 0x95 0x03 0x91 0x02 + 0x09 0x4b 0x95 0x01 0x91 0x02 0x95 0x04 + 0x91 0x01 0x05 0x07 0x19 0xe0 0x29 0xe7 + 0x95 0x08 0x81 0x02 0x75 0x08 0x95 0x01 + 0x81 0x01 0x19 0x00 0x29 0x91 0x26 0xff + 0x00 0x95 0x06 0x81 0x00 0xc0 0x05 0x0c + 0x09 0x01 0xa1 0x01 0x85 0x02 0x05 0x0c + 0x15 0x00 0x25 0x01 0x75 0x01 0x95 0x1c + 0x09 0xe2 0x09 0xb7 0x09 0xcd 0x09 0xea + 0x09 0xe9 0x09 0xb6 0x09 0xb5 0x0a 0x83 + 0x01 0x0a 0x1a 0x02 0x0a 0x79 0x02 0x0a + 0xab 0x01 0x0a 0x08 0x02 0x0a 0x02 0x02 + 0x0a 0x03 0x02 0x0a 0x07 0x02 0x0a 0x01 + 0x02 0x0a 0x92 0x01 0x0a 0x9c 0x01 0x09 + 0x95 0x0a 0x23 0x02 0x0a 0x89 0x02 0x0a + 0x8b 0x02 0x0a 0x8c 0x02 0x0a 0x8a 0x01 + 0x0a 0x99 0x01 0x0a 0xa7 0x01 0x0a 0xb6 + 0x01 0x0a 0xb7 0x01 0x81 0x02 0x75 0x01 + 0x95 0x04 0x81 0x01 0x06 0x00 0xff 0x0a + 0x02 0xff 0x26 0xff 0x00 0x95 0x01 0x75 + 0x08 0x81 0x02 0xc0 0x05 0x01 0x09 0x80 + 0xa1 0x01 0x85 0x03 0x19 0x81 0x29 0x83 + 0x25 0x01 0x95 0x03 0x75 0x01 0x81 0x02 + 0x95 0x05 0x81 0x01 0xc0 0x05 0x0c 0x09 + 0x01 0xa1 0x01 0x85 0x04 0x05 0x01 0x09 + 0x06 0xa1 0x02 0x06 0x00 0xff 0x15 0x00 + 0x25 0x03 0x95 0x01 0x75 0x02 0x0a 0x01 + 0xfe 0x81 0x02 0x75 0x06 0x81 0x01 0xc0 + 0xc0 0x05 0x0c 0x09 0x01 0xa1 0x01 0x85 + 0x05 0x05 0x01 0x09 0x06 0xa1 0x02 0x06 + 0x00 0xff 0x25 0x01 0x75 0x01 0x95 0x02 + 0x0a 0x03 0xfe 0x0a 0x04 0xfe 0x81 0x02 + 0x95 0x06 0x81 0x01 0xc0 0xc0 0x05 0x0c + 0x09 0x01 0xa1 0x01 0x85 0xff 0x05 0x06 + 0x95 0x01 0x75 0x02 0x19 0x24 0x29 0x26 + 0x81 0x02 0x75 0x06 0x81 0x01 0xc0 + }; +} + diff --git a/usr.sbin/bluetooth/bthidd/bthidd.h b/usr.sbin/bluetooth/bthidd/bthidd.h new file mode 100644 index 0000000..3485fc3 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/bthidd.h @@ -0,0 +1,93 @@ +/* + * bthidd.h + */ + +/*- + * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: bthidd.h,v 1.7 2006/09/07 21:06:53 max Exp $ + * $FreeBSD$ + */ + +#ifndef _BTHIDD_H_ +#define _BTHIDD_H_ 1 + +#define BTHIDD_IDENT "bthidd" +#define BTHIDD_PIDFILE "/var/run/" BTHIDD_IDENT ".pid" + +struct bthid_session; + +struct bthid_server +{ + bdaddr_t bdaddr; /* local bdaddr */ + int32_t cons; /* /dev/consolectl */ + int32_t ctrl; /* control channel (listen) */ + int32_t intr; /* intr. channel (listen) */ + int32_t maxfd; /* max fd in sets */ + fd_set rfdset; /* read descriptor set */ + fd_set wfdset; /* write descriptor set */ + LIST_HEAD(, bthid_session) sessions; +}; + +typedef struct bthid_server bthid_server_t; +typedef struct bthid_server * bthid_server_p; + +struct bthid_session +{ + bthid_server_p srv; /* pointer back to server */ + int32_t ctrl; /* control channel */ + int32_t intr; /* interrupt channel */ + int32_t vkbd; /* virual keyboard */ + bdaddr_t bdaddr;/* remote bdaddr */ + uint16_t state; /* session state */ +#define CLOSED 0 +#define W4CTRL 1 +#define W4INTR 2 +#define OPEN 3 + bitstr_t *keys1; /* keys map (new) */ + bitstr_t *keys2; /* keys map (old) */ + LIST_ENTRY(bthid_session) next; /* link to next */ +}; + +typedef struct bthid_session bthid_session_t; +typedef struct bthid_session * bthid_session_p; + +int32_t server_init (bthid_server_p srv); +void server_shutdown (bthid_server_p srv); +int32_t server_do (bthid_server_p srv); + +int32_t client_rescan (bthid_server_p srv); +int32_t client_connect (bthid_server_p srv, int fd); + +bthid_session_p session_open (bthid_server_p srv, hid_device_p const d); +bthid_session_p session_by_bdaddr(bthid_server_p srv, bdaddr_p bdaddr); +bthid_session_p session_by_fd (bthid_server_p srv, int32_t fd); +void session_close (bthid_session_p s); + +int32_t hid_control (bthid_session_p s, uint8_t *data, int32_t len); +int32_t hid_interrupt (bthid_session_p s, uint8_t *data, int32_t len); + +#endif /* ndef _BTHIDD_H_ */ + diff --git a/usr.sbin/bluetooth/bthidd/client.c b/usr.sbin/bluetooth/bthidd/client.c new file mode 100644 index 0000000..5f01133 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/client.c @@ -0,0 +1,259 @@ +/* + * client.c + */ + +/*- + * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: client.c,v 1.7 2006/09/07 21:06:53 max Exp $ + * $FreeBSD$ + */ + +#include <sys/queue.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <usbhid.h> +#include "bthid_config.h" +#include "bthidd.h" + +static int32_t client_socket(bdaddr_p bdaddr, uint16_t psm); + +/* + * Get next config entry and create outbound connection (if required) + * + * XXX Do only one device at a time. At least one of my devices (3COM + * Bluetooth PCCARD) rejects Create_Connection command if another + * Create_Connection command is still pending. Weird... + */ + +static int32_t connect_in_progress = 0; + +int32_t +client_rescan(bthid_server_p srv) +{ + static hid_device_p d; + bthid_session_p s; + + assert(srv != NULL); + + if (connect_in_progress) + return (0); /* another connect is still pending */ + + d = get_next_hid_device(d); + if (d == NULL) + return (0); /* XXX should not happen? empty config? */ + + if ((s = session_by_bdaddr(srv, &d->bdaddr)) != NULL) + return (0); /* session already active */ + + if (!d->new_device) { + if (d->reconnect_initiate) + return (0); /* device will initiate reconnect */ + } + + syslog(LOG_NOTICE, "Opening outbound session for %s " \ + "(new_device=%d, reconnect_initiate=%d)", + bt_ntoa(&d->bdaddr, NULL), d->new_device, d->reconnect_initiate); + + if ((s = session_open(srv, d)) == NULL) { + syslog(LOG_CRIT, "Could not create outbound session for %s", + bt_ntoa(&d->bdaddr, NULL)); + return (-1); + } + + /* Open control channel */ + s->ctrl = client_socket(&s->bdaddr, d->control_psm); + if (s->ctrl < 0) { + syslog(LOG_ERR, "Could not open control channel to %s. %s (%d)", + bt_ntoa(&s->bdaddr, NULL), strerror(errno), errno); + session_close(s); + return (-1); + } + + s->state = W4CTRL; + + FD_SET(s->ctrl, &srv->wfdset); + if (s->ctrl > srv->maxfd) + srv->maxfd = s->ctrl; + + connect_in_progress = 1; + + return (0); +} + +/* + * Process connect on the socket + */ + +int32_t +client_connect(bthid_server_p srv, int32_t fd) +{ + bthid_session_p s; + hid_device_p d; + int32_t error; + socklen_t len; + + assert(srv != NULL); + assert(fd >= 0); + + s = session_by_fd(srv, fd); + assert(s != NULL); + + d = get_hid_device(&s->bdaddr); + assert(d != NULL); + + error = 0; + len = sizeof(error); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { + syslog(LOG_ERR, "Could not get socket error for %s. %s (%d)", + bt_ntoa(&s->bdaddr, NULL), strerror(errno), errno); + session_close(s); + connect_in_progress = 0; + + return (-1); + } + + if (error != 0) { + syslog(LOG_ERR, "Could not connect to %s. %s (%d)", + bt_ntoa(&s->bdaddr, NULL), strerror(error), error); + session_close(s); + connect_in_progress = 0; + + return (0); + } + + switch (s->state) { + case W4CTRL: /* Control channel is open */ + assert(s->ctrl == fd); + assert(s->intr == -1); + + /* Open interrupt channel */ + s->intr = client_socket(&s->bdaddr, d->interrupt_psm); + if (s->intr < 0) { + syslog(LOG_ERR, "Could not open interrupt channel " \ + "to %s. %s (%d)", bt_ntoa(&s->bdaddr, NULL), + strerror(errno), errno); + session_close(s); + connect_in_progress = 0; + + return (-1); + } + + s->state = W4INTR; + + FD_SET(s->intr, &srv->wfdset); + if (s->intr > srv->maxfd) + srv->maxfd = s->intr; + + d->new_device = 0; /* reset new device flag */ + write_hids_file(); + break; + + case W4INTR: /* Interrupt channel is open */ + assert(s->ctrl != -1); + assert(s->intr == fd); + + s->state = OPEN; + connect_in_progress = 0; + + /* Register session's vkbd descriptor (if any) for read */ + if (s->state == OPEN && d->keyboard) { + assert(s->vkbd != -1); + + FD_SET(s->vkbd, &srv->rfdset); + if (s->vkbd > srv->maxfd) + srv->maxfd = s->vkbd; + } + break; + + default: + assert(0); + break; + } + + /* Move fd to from the write fd set into read fd set */ + FD_CLR(fd, &srv->wfdset); + FD_SET(fd, &srv->rfdset); + + return (0); +} + +/* + * Create bound non-blocking socket and initiate connect + */ + +static int +client_socket(bdaddr_p bdaddr, uint16_t psm) +{ + struct sockaddr_l2cap l2addr; + int32_t s, m; + + s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP); + if (s < 0) + return (-1); + + m = fcntl(s, F_GETFL); + if (m < 0) { + close(s); + return (-1); + } + + if (fcntl(s, F_SETFL, (m|O_NONBLOCK)) < 0) { + close(s); + return (-1); + } + + l2addr.l2cap_len = sizeof(l2addr); + l2addr.l2cap_family = AF_BLUETOOTH; + memset(&l2addr.l2cap_bdaddr, 0, sizeof(l2addr.l2cap_bdaddr)); + l2addr.l2cap_psm = 0; + l2addr.l2cap_bdaddr_type = BDADDR_BREDR; + l2addr.l2cap_cid = 0; + + if (bind(s, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) { + close(s); + return (-1); + } + + memcpy(&l2addr.l2cap_bdaddr, bdaddr, sizeof(l2addr.l2cap_bdaddr)); + l2addr.l2cap_psm = htole16(psm); + + if (connect(s, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0 && + errno != EINPROGRESS) { + close(s); + return (-1); + } + + return (s); +} + diff --git a/usr.sbin/bluetooth/bthidd/hid.c b/usr.sbin/bluetooth/bthidd/hid.c new file mode 100644 index 0000000..69a6fdc --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/hid.c @@ -0,0 +1,415 @@ +/* + * hid.c + */ + +/*- + * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: hid.c,v 1.5 2006/09/07 21:06:53 max Exp $ + * $FreeBSD$ + */ + +#include <sys/consio.h> +#include <sys/mouse.h> +#include <sys/queue.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <usbhid.h> +#include "bthid_config.h" +#include "bthidd.h" +#include "kbd.h" + +/* + * Process data from control channel + */ + +int32_t +hid_control(bthid_session_p s, uint8_t *data, int32_t len) +{ + assert(s != NULL); + assert(data != NULL); + assert(len > 0); + + switch (data[0] >> 4) { + case 0: /* Handshake (response to command) */ + if (data[0] & 0xf) + syslog(LOG_ERR, "Got handshake message with error " \ + "response 0x%x from %s", + data[0], bt_ntoa(&s->bdaddr, NULL)); + break; + + case 1: /* HID Control */ + switch (data[0] & 0xf) { + case 0: /* NOP */ + break; + + case 1: /* Hard reset */ + case 2: /* Soft reset */ + syslog(LOG_WARNING, "Device %s requested %s reset", + bt_ntoa(&s->bdaddr, NULL), + ((data[0] & 0xf) == 1)? "hard" : "soft"); + break; + + case 3: /* Suspend */ + syslog(LOG_NOTICE, "Device %s requested Suspend", + bt_ntoa(&s->bdaddr, NULL)); + break; + + case 4: /* Exit suspend */ + syslog(LOG_NOTICE, "Device %s requested Exit Suspend", + bt_ntoa(&s->bdaddr, NULL)); + break; + + case 5: /* Virtual cable unplug */ + syslog(LOG_NOTICE, "Device %s unplugged virtual cable", + bt_ntoa(&s->bdaddr, NULL)); + session_close(s); + break; + + default: + syslog(LOG_WARNING, "Device %s sent unknown " \ + "HID_Control message 0x%x", + bt_ntoa(&s->bdaddr, NULL), data[0]); + break; + } + break; + + default: + syslog(LOG_WARNING, "Got unexpected message 0x%x on Control " \ + "channel from %s", data[0], bt_ntoa(&s->bdaddr, NULL)); + break; + } + + return (0); +} + +/* + * Process data from the interrupt channel + */ + +int32_t +hid_interrupt(bthid_session_p s, uint8_t *data, int32_t len) +{ + hid_device_p hid_device; + hid_data_t d; + hid_item_t h; + int32_t report_id, usage, page, val, + mouse_x, mouse_y, mouse_z, mouse_butt, + mevents, kevents, i; + + assert(s != NULL); + assert(s->srv != NULL); + assert(data != NULL); + + if (len < 3) { + syslog(LOG_ERR, "Got short message (%d bytes) on Interrupt " \ + "channel from %s", len, bt_ntoa(&s->bdaddr, NULL)); + return (-1); + } + + if (data[0] != 0xa1) { + syslog(LOG_ERR, "Got unexpected message 0x%x on " \ + "Interrupt channel from %s", + data[0], bt_ntoa(&s->bdaddr, NULL)); + return (-1); + } + + report_id = data[1]; + data ++; + len --; + + hid_device = get_hid_device(&s->bdaddr); + assert(hid_device != NULL); + + mouse_x = mouse_y = mouse_z = mouse_butt = mevents = kevents = 0; + + for (d = hid_start_parse(hid_device->desc, 1 << hid_input, -1); + hid_get_item(d, &h) > 0; ) { + if ((h.flags & HIO_CONST) || (h.report_ID != report_id) || + (h.kind != hid_input)) + continue; + + page = HID_PAGE(h.usage); + val = hid_get_data(data, &h); + + /* + * When the input field is an array and the usage is specified + * with a range instead of an ID, we have to derive the actual + * usage by using the item value as an index in the usage range + * list. + */ + if ((h.flags & HIO_VARIABLE)) { + usage = HID_USAGE(h.usage); + } else { + const uint32_t usage_offset = val - h.logical_minimum; + usage = HID_USAGE(h.usage_minimum + usage_offset); + } + + switch (page) { + case HUP_GENERIC_DESKTOP: + switch (usage) { + case HUG_X: + mouse_x = val; + mevents ++; + break; + + case HUG_Y: + mouse_y = val; + mevents ++; + break; + + case HUG_WHEEL: + mouse_z = -val; + mevents ++; + break; + + case HUG_SYSTEM_SLEEP: + if (val) + syslog(LOG_NOTICE, "Sleep button pressed"); + break; + } + break; + + case HUP_KEYBOARD: + kevents ++; + + if (h.flags & HIO_VARIABLE) { + if (val && usage < kbd_maxkey()) + bit_set(s->keys1, usage); + } else { + if (val && val < kbd_maxkey()) + bit_set(s->keys1, val); + + for (i = 1; i < h.report_count; i++) { + h.pos += h.report_size; + val = hid_get_data(data, &h); + if (val && val < kbd_maxkey()) + bit_set(s->keys1, val); + } + } + break; + + case HUP_BUTTON: + if (usage != 0) { + if (usage == 2) + usage = 3; + else if (usage == 3) + usage = 2; + + mouse_butt |= (val << (usage - 1)); + mevents ++; + } + break; + + case HUP_CONSUMER: + if (!val) + break; + + switch (usage) { + case HUC_AC_PAN: + /* Horizontal scroll */ + if (val < 0) + mouse_butt |= (1 << 5); + else + mouse_butt |= (1 << 6); + + mevents ++; + val = 0; + break; + + case 0xb5: /* Scan Next Track */ + val = 0x19; + break; + + case 0xb6: /* Scan Previous Track */ + val = 0x10; + break; + + case 0xb7: /* Stop */ + val = 0x24; + break; + + case 0xcd: /* Play/Pause */ + val = 0x22; + break; + + case 0xe2: /* Mute */ + val = 0x20; + break; + + case 0xe9: /* Volume Up */ + val = 0x30; + break; + + case 0xea: /* Volume Down */ + val = 0x2E; + break; + + case 0x183: /* Media Select */ + val = 0x6D; + break; + + case 0x018a: /* Mail */ + val = 0x6C; + break; + + case 0x192: /* Calculator */ + val = 0x21; + break; + + case 0x194: /* My Computer */ + val = 0x6B; + break; + + case 0x221: /* WWW Search */ + val = 0x65; + break; + + case 0x223: /* WWW Home */ + val = 0x32; + break; + + case 0x224: /* WWW Back */ + val = 0x6A; + break; + + case 0x225: /* WWW Forward */ + val = 0x69; + break; + + case 0x226: /* WWW Stop */ + val = 0x68; + break; + + case 0x227: /* WWW Refresh */ + val = 0x67; + break; + + case 0x22a: /* WWW Favorites */ + val = 0x66; + break; + + default: + val = 0; + break; + } + + /* XXX FIXME - UGLY HACK */ + if (val != 0) { + if (hid_device->keyboard) { + int32_t buf[4] = { 0xe0, val, + 0xe0, val|0x80 }; + + assert(s->vkbd != -1); + write(s->vkbd, buf, sizeof(buf)); + } else + syslog(LOG_ERR, "Keyboard events " \ + "received from non-keyboard " \ + "device %s. Please report", + bt_ntoa(&s->bdaddr, NULL)); + } + break; + + case HUP_MICROSOFT: + switch (usage) { + case 0xfe01: + if (!hid_device->battery_power) + break; + + switch (val) { + case 1: + syslog(LOG_INFO, "Battery is OK on %s", + bt_ntoa(&s->bdaddr, NULL)); + break; + + case 2: + syslog(LOG_NOTICE, "Low battery on %s", + bt_ntoa(&s->bdaddr, NULL)); + break; + + case 3: + syslog(LOG_WARNING, "Very low battery "\ + "on %s", + bt_ntoa(&s->bdaddr, NULL)); + break; + } + break; + } + break; + } + } + hid_end_parse(d); + + /* + * XXX FIXME Feed keyboard events into kernel. + * The code below works, bit host also needs to track + * and handle repeat. + * + * Key repeat currently works in X, but not in console. + */ + + if (kevents > 0) { + if (hid_device->keyboard) { + assert(s->vkbd != -1); + kbd_process_keys(s); + } else + syslog(LOG_ERR, "Keyboard events received from " \ + "non-keyboard device %s. Please report", + bt_ntoa(&s->bdaddr, NULL)); + } + + /* + * XXX FIXME Feed mouse events into kernel. + * The code block below works, but it is not good enough. + * Need to track double-clicks etc. + * + * Double click currently works in X, but not in console. + */ + + if (mevents > 0) { + struct mouse_info mi; + + mi.operation = MOUSE_ACTION; + mi.u.data.x = mouse_x; + mi.u.data.y = mouse_y; + mi.u.data.z = mouse_z; + mi.u.data.buttons = mouse_butt; + + if (ioctl(s->srv->cons, CONS_MOUSECTL, &mi) < 0) + syslog(LOG_ERR, "Could not process mouse events from " \ + "%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL), + strerror(errno), errno); + } + + return (0); +} diff --git a/usr.sbin/bluetooth/bthidd/kbd.c b/usr.sbin/bluetooth/bthidd/kbd.c new file mode 100644 index 0000000..cd9f70b --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/kbd.c @@ -0,0 +1,581 @@ +/* + * kbd.c + */ + +/*- + * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: kbd.c,v 1.4 2006/09/07 21:06:53 max Exp $ + * $FreeBSD$ + */ + +#include <sys/consio.h> +#include <sys/ioctl.h> +#include <sys/kbio.h> +#include <sys/queue.h> +#include <sys/wait.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> +#include <dev/vkbd/vkbd_var.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <usbhid.h> +#include "bthid_config.h" +#include "bthidd.h" +#include "kbd.h" + +static void kbd_write(bitstr_t *m, int32_t fb, int32_t make, int32_t fd); +static int32_t kbd_xlate(int32_t code, int32_t make, int32_t *b, int32_t const *eob); + +/* + * HID code to PS/2 set 1 code translation table. + * + * http://www.microsoft.com/whdc/device/input/Scancode.mspx + * + * The table only contains "make" (key pressed) codes. + * The "break" (key released) code is generated as "make" | 0x80 + */ + +#define E0PREFIX (1U << 31) +#define NOBREAK (1 << 30) +#define CODEMASK (~(E0PREFIX|NOBREAK)) + +static int32_t const x[] = +{ +/*==================================================*/ +/* Name HID code Make Break*/ +/*==================================================*/ +/* No Event 00 */ -1, /* None */ +/* Overrun Error 01 */ NOBREAK|0xFF, /* None */ +/* POST Fail 02 */ NOBREAK|0xFC, /* None */ +/* ErrorUndefined 03 */ -1, /* Unassigned */ +/* a A 04 */ 0x1E, /* 9E */ +/* b B 05 */ 0x30, /* B0 */ +/* c C 06 */ 0x2E, /* AE */ +/* d D 07 */ 0x20, /* A0 */ +/* e E 08 */ 0x12, /* 92 */ +/* f F 09 */ 0x21, /* A1 */ +/* g G 0A */ 0x22, /* A2 */ +/* h H 0B */ 0x23, /* A3 */ +/* i I 0C */ 0x17, /* 97 */ +/* j J 0D */ 0x24, /* A4 */ +/* k K 0E */ 0x25, /* A5 */ +/* l L 0F */ 0x26, /* A6 */ +/* m M 10 */ 0x32, /* B2 */ +/* n N 11 */ 0x31, /* B1 */ +/* o O 12 */ 0x18, /* 98 */ +/* p P 13 */ 0x19, /* 99 */ +/* q Q 14 */ 0x10, /* 90 */ +/* r R 15 */ 0x13, /* 93 */ +/* s S 16 */ 0x1F, /* 9F */ +/* t T 17 */ 0x14, /* 94 */ +/* u U 18 */ 0x16, /* 96 */ +/* v V 19 */ 0x2F, /* AF */ +/* w W 1A */ 0x11, /* 91 */ +/* x X 1B */ 0x2D, /* AD */ +/* y Y 1C */ 0x15, /* 95 */ +/* z Z 1D */ 0x2C, /* AC */ +/* 1 ! 1E */ 0x02, /* 82 */ +/* 2 @ 1F */ 0x03, /* 83 */ +/* 3 # 20 */ 0x04, /* 84 */ +/* 4 $ 21 */ 0x05, /* 85 */ +/* 5 % 22 */ 0x06, /* 86 */ +/* 6 ^ 23 */ 0x07, /* 87 */ +/* 7 & 24 */ 0x08, /* 88 */ +/* 8 * 25 */ 0x09, /* 89 */ +/* 9 ( 26 */ 0x0A, /* 8A */ +/* 0 ) 27 */ 0x0B, /* 8B */ +/* Return 28 */ 0x1C, /* 9C */ +/* Escape 29 */ 0x01, /* 81 */ +/* Backspace 2A */ 0x0E, /* 8E */ +/* Tab 2B */ 0x0F, /* 8F */ +/* Space 2C */ 0x39, /* B9 */ +/* - _ 2D */ 0x0C, /* 8C */ +/* = + 2E */ 0x0D, /* 8D */ +/* [ { 2F */ 0x1A, /* 9A */ +/* ] } 30 */ 0x1B, /* 9B */ +/* \ | 31 */ 0x2B, /* AB */ +/* Europe 1 32 */ 0x2B, /* AB */ +/* ; : 33 */ 0x27, /* A7 */ +/* " ' 34 */ 0x28, /* A8 */ +/* ` ~ 35 */ 0x29, /* A9 */ +/* comma < 36 */ 0x33, /* B3 */ +/* . > 37 */ 0x34, /* B4 */ +/* / ? 38 */ 0x35, /* B5 */ +/* Caps Lock 39 */ 0x3A, /* BA */ +/* F1 3A */ 0x3B, /* BB */ +/* F2 3B */ 0x3C, /* BC */ +/* F3 3C */ 0x3D, /* BD */ +/* F4 3D */ 0x3E, /* BE */ +/* F5 3E */ 0x3F, /* BF */ +/* F6 3F */ 0x40, /* C0 */ +/* F7 40 */ 0x41, /* C1 */ +/* F8 41 */ 0x42, /* C2 */ +/* F9 42 */ 0x43, /* C3 */ +/* F10 43 */ 0x44, /* C4 */ +/* F11 44 */ 0x57, /* D7 */ +/* F12 45 */ 0x58, /* D8 */ +/* Print Screen 46 */ E0PREFIX|0x37, /* E0 B7 */ +/* Scroll Lock 47 */ 0x46, /* C6 */ +#if 0 +/* Break (Ctrl-Pause) 48 */ E0 46 E0 C6, /* None */ +/* Pause 48 */ E1 1D 45 E1 9D C5, /* None */ +#else +/* Break (Ctrl-Pause)/Pause 48 */ NOBREAK /* Special case */, /* None */ +#endif +/* Insert 49 */ E0PREFIX|0x52, /* E0 D2 */ +/* Home 4A */ E0PREFIX|0x47, /* E0 C7 */ +/* Page Up 4B */ E0PREFIX|0x49, /* E0 C9 */ +/* Delete 4C */ E0PREFIX|0x53, /* E0 D3 */ +/* End 4D */ E0PREFIX|0x4F, /* E0 CF */ +/* Page Down 4E */ E0PREFIX|0x51, /* E0 D1 */ +/* Right Arrow 4F */ E0PREFIX|0x4D, /* E0 CD */ +/* Left Arrow 50 */ E0PREFIX|0x4B, /* E0 CB */ +/* Down Arrow 51 */ E0PREFIX|0x50, /* E0 D0 */ +/* Up Arrow 52 */ E0PREFIX|0x48, /* E0 C8 */ +/* Num Lock 53 */ 0x45, /* C5 */ +/* Keypad / 54 */ E0PREFIX|0x35, /* E0 B5 */ +/* Keypad * 55 */ 0x37, /* B7 */ +/* Keypad - 56 */ 0x4A, /* CA */ +/* Keypad + 57 */ 0x4E, /* CE */ +/* Keypad Enter 58 */ E0PREFIX|0x1C, /* E0 9C */ +/* Keypad 1 End 59 */ 0x4F, /* CF */ +/* Keypad 2 Down 5A */ 0x50, /* D0 */ +/* Keypad 3 PageDn 5B */ 0x51, /* D1 */ +/* Keypad 4 Left 5C */ 0x4B, /* CB */ +/* Keypad 5 5D */ 0x4C, /* CC */ +/* Keypad 6 Right 5E */ 0x4D, /* CD */ +/* Keypad 7 Home 5F */ 0x47, /* C7 */ +/* Keypad 8 Up 60 */ 0x48, /* C8 */ +/* Keypad 9 PageUp 61 */ 0x49, /* C9 */ +/* Keypad 0 Insert 62 */ 0x52, /* D2 */ +/* Keypad . Delete 63 */ 0x53, /* D3 */ +/* Europe 2 64 */ 0x56, /* D6 */ +/* App 65 */ E0PREFIX|0x5D, /* E0 DD */ +/* Keyboard Power 66 */ E0PREFIX|0x5E, /* E0 DE */ +/* Keypad = 67 */ 0x59, /* D9 */ +/* F13 68 */ 0x64, /* E4 */ +/* F14 69 */ 0x65, /* E5 */ +/* F15 6A */ 0x66, /* E6 */ +/* F16 6B */ 0x67, /* E7 */ +/* F17 6C */ 0x68, /* E8 */ +/* F18 6D */ 0x69, /* E9 */ +/* F19 6E */ 0x6A, /* EA */ +/* F20 6F */ 0x6B, /* EB */ +/* F21 70 */ 0x6C, /* EC */ +/* F22 71 */ 0x6D, /* ED */ +/* F23 72 */ 0x6E, /* EE */ +/* F24 73 */ 0x76, /* F6 */ +/* Keyboard Execute 74 */ -1, /* Unassigned */ +/* Keyboard Help 75 */ -1, /* Unassigned */ +/* Keyboard Menu 76 */ -1, /* Unassigned */ +/* Keyboard Select 77 */ -1, /* Unassigned */ +/* Keyboard Stop 78 */ -1, /* Unassigned */ +/* Keyboard Again 79 */ -1, /* Unassigned */ +/* Keyboard Undo 7A */ -1, /* Unassigned */ +/* Keyboard Cut 7B */ -1, /* Unassigned */ +/* Keyboard Copy 7C */ -1, /* Unassigned */ +/* Keyboard Paste 7D */ -1, /* Unassigned */ +/* Keyboard Find 7E */ -1, /* Unassigned */ +/* Keyboard Mute 7F */ -1, /* Unassigned */ +/* Keyboard Volume Up 80 */ -1, /* Unassigned */ +/* Keyboard Volume Dn 81 */ -1, /* Unassigned */ +/* Keyboard Locking Caps Lock 82 */ -1, /* Unassigned */ +/* Keyboard Locking Num Lock 83 */ -1, /* Unassigned */ +/* Keyboard Locking Scroll Lock 84 */ -1, /* Unassigned */ +/* Keypad comma 85 */ 0x7E, /* FE */ +/* Keyboard Equal Sign 86 */ -1, /* Unassigned */ +/* Keyboard Int'l 1 87 */ 0x73, /* F3 */ +/* Keyboard Int'l 2 88 */ 0x70, /* F0 */ +/* Keyboard Int'l 2 89 */ 0x7D, /* FD */ +/* Keyboard Int'l 4 8A */ 0x79, /* F9 */ +/* Keyboard Int'l 5 8B */ 0x7B, /* FB */ +/* Keyboard Int'l 6 8C */ 0x5C, /* DC */ +/* Keyboard Int'l 7 8D */ -1, /* Unassigned */ +/* Keyboard Int'l 8 8E */ -1, /* Unassigned */ +/* Keyboard Int'l 9 8F */ -1, /* Unassigned */ +/* Keyboard Lang 1 90 */ 0x71, /* Kana */ +/* Keyboard Lang 2 91 */ 0x72, /* Eisu */ +/* Keyboard Lang 3 92 */ 0x78, /* F8 */ +/* Keyboard Lang 4 93 */ 0x77, /* F7 */ +/* Keyboard Lang 5 94 */ 0x76, /* F6 */ +/* Keyboard Lang 6 95 */ -1, /* Unassigned */ +/* Keyboard Lang 7 96 */ -1, /* Unassigned */ +/* Keyboard Lang 8 97 */ -1, /* Unassigned */ +/* Keyboard Lang 9 98 */ -1, /* Unassigned */ +/* Keyboard Alternate Erase 99 */ -1, /* Unassigned */ +/* Keyboard SysReq/Attention 9A */ -1, /* Unassigned */ +/* Keyboard Cancel 9B */ -1, /* Unassigned */ +/* Keyboard Clear 9C */ -1, /* Unassigned */ +/* Keyboard Prior 9D */ -1, /* Unassigned */ +/* Keyboard Return 9E */ -1, /* Unassigned */ +/* Keyboard Separator 9F */ -1, /* Unassigned */ +/* Keyboard Out A0 */ -1, /* Unassigned */ +/* Keyboard Oper A1 */ -1, /* Unassigned */ +/* Keyboard Clear/Again A2 */ -1, /* Unassigned */ +/* Keyboard CrSel/Props A3 */ -1, /* Unassigned */ +/* Keyboard ExSel A4 */ -1, /* Unassigned */ +/* Reserved A5 */ -1, /* Reserved */ +/* Reserved A6 */ -1, /* Reserved */ +/* Reserved A7 */ -1, /* Reserved */ +/* Reserved A8 */ -1, /* Reserved */ +/* Reserved A9 */ -1, /* Reserved */ +/* Reserved AA */ -1, /* Reserved */ +/* Reserved AB */ -1, /* Reserved */ +/* Reserved AC */ -1, /* Reserved */ +/* Reserved AD */ -1, /* Reserved */ +/* Reserved AE */ -1, /* Reserved */ +/* Reserved AF */ -1, /* Reserved */ +/* Reserved B0 */ -1, /* Reserved */ +/* Reserved B1 */ -1, /* Reserved */ +/* Reserved B2 */ -1, /* Reserved */ +/* Reserved B3 */ -1, /* Reserved */ +/* Reserved B4 */ -1, /* Reserved */ +/* Reserved B5 */ -1, /* Reserved */ +/* Reserved B6 */ -1, /* Reserved */ +/* Reserved B7 */ -1, /* Reserved */ +/* Reserved B8 */ -1, /* Reserved */ +/* Reserved B9 */ -1, /* Reserved */ +/* Reserved BA */ -1, /* Reserved */ +/* Reserved BB */ -1, /* Reserved */ +/* Reserved BC */ -1, /* Reserved */ +/* Reserved BD */ -1, /* Reserved */ +/* Reserved BE */ -1, /* Reserved */ +/* Reserved BF */ -1, /* Reserved */ +/* Reserved C0 */ -1, /* Reserved */ +/* Reserved C1 */ -1, /* Reserved */ +/* Reserved C2 */ -1, /* Reserved */ +/* Reserved C3 */ -1, /* Reserved */ +/* Reserved C4 */ -1, /* Reserved */ +/* Reserved C5 */ -1, /* Reserved */ +/* Reserved C6 */ -1, /* Reserved */ +/* Reserved C7 */ -1, /* Reserved */ +/* Reserved C8 */ -1, /* Reserved */ +/* Reserved C9 */ -1, /* Reserved */ +/* Reserved CA */ -1, /* Reserved */ +/* Reserved CB */ -1, /* Reserved */ +/* Reserved CC */ -1, /* Reserved */ +/* Reserved CD */ -1, /* Reserved */ +/* Reserved CE */ -1, /* Reserved */ +/* Reserved CF */ -1, /* Reserved */ +/* Reserved D0 */ -1, /* Reserved */ +/* Reserved D1 */ -1, /* Reserved */ +/* Reserved D2 */ -1, /* Reserved */ +/* Reserved D3 */ -1, /* Reserved */ +/* Reserved D4 */ -1, /* Reserved */ +/* Reserved D5 */ -1, /* Reserved */ +/* Reserved D6 */ -1, /* Reserved */ +/* Reserved D7 */ -1, /* Reserved */ +/* Reserved D8 */ -1, /* Reserved */ +/* Reserved D9 */ -1, /* Reserved */ +/* Reserved DA */ -1, /* Reserved */ +/* Reserved DB */ -1, /* Reserved */ +/* Reserved DC */ -1, /* Reserved */ +/* Reserved DD */ -1, /* Reserved */ +/* Reserved DE */ -1, /* Reserved */ +/* Reserved DF */ -1, /* Reserved */ +/* Left Control E0 */ 0x1D, /* 9D */ +/* Left Shift E1 */ 0x2A, /* AA */ +/* Left Alt E2 */ 0x38, /* B8 */ +/* Left GUI E3 */ E0PREFIX|0x5B, /* E0 DB */ +/* Right Control E4 */ E0PREFIX|0x1D, /* E0 9D */ +/* Right Shift E5 */ 0x36, /* B6 */ +/* Right Alt E6 */ E0PREFIX|0x38, /* E0 B8 */ +/* Right GUI E7 */ E0PREFIX|0x5C /* E0 DC */ +}; + +#define xsize ((int32_t)(sizeof(x)/sizeof(x[0]))) + +/* + * Get a max HID keycode (aligned) + */ + +int32_t +kbd_maxkey(void) +{ + return (xsize); +} + +/* + * Process keys + */ + +int32_t +kbd_process_keys(bthid_session_p s) +{ + bitstr_t diff[bitstr_size(xsize)]; + int32_t f1, f2, i; + + assert(s != NULL); + assert(s->srv != NULL); + + /* Check if the new keys have been pressed */ + bit_ffs(s->keys1, xsize, &f1); + + /* Check if old keys still pressed */ + bit_ffs(s->keys2, xsize, &f2); + + if (f1 == -1) { + /* no new key pressed */ + if (f2 != -1) { + /* release old keys */ + kbd_write(s->keys2, f2, 0, s->vkbd); + memset(s->keys2, 0, bitstr_size(xsize)); + } + + return (0); + } + + if (f2 == -1) { + /* no old keys, but new keys pressed */ + assert(f1 != -1); + + memcpy(s->keys2, s->keys1, bitstr_size(xsize)); + kbd_write(s->keys1, f1, 1, s->vkbd); + memset(s->keys1, 0, bitstr_size(xsize)); + + return (0); + } + + /* new keys got pressed, old keys got released */ + memset(diff, 0, bitstr_size(xsize)); + + for (i = f2; i < xsize; i ++) { + if (bit_test(s->keys2, i)) { + if (!bit_test(s->keys1, i)) { + bit_clear(s->keys2, i); + bit_set(diff, i); + } + } + } + + for (i = f1; i < xsize; i++) { + if (bit_test(s->keys1, i)) { + if (!bit_test(s->keys2, i)) + bit_set(s->keys2, i); + else + bit_clear(s->keys1, i); + } + } + + bit_ffs(diff, xsize, &f2); + if (f2 > 0) + kbd_write(diff, f2, 0, s->vkbd); + + bit_ffs(s->keys1, xsize, &f1); + if (f1 > 0) { + kbd_write(s->keys1, f1, 1, s->vkbd); + memset(s->keys1, 0, bitstr_size(xsize)); + } + + return (0); +} + +/* + * Translate given keymap and write keyscodes + */ + +static void +kbd_write(bitstr_t *m, int32_t fb, int32_t make, int32_t fd) +{ + int32_t i, *b, *eob, n, buf[64]; + + b = buf; + eob = b + sizeof(buf)/sizeof(buf[0]); + i = fb; + + while (i < xsize) { + if (bit_test(m, i)) { + n = kbd_xlate(i, make, b, eob); + if (n == -1) { + write(fd, buf, (b - buf) * sizeof(buf[0])); + b = buf; + continue; + } + + b += n; + } + + i ++; + } + + if (b != buf) + write(fd, buf, (b - buf) * sizeof(buf[0])); +} + +/* + * Translate HID code into PS/2 code and put codes into buffer b. + * Returns the number of codes put in b. Return -1 if buffer has not + * enough space. + */ + +#undef PUT +#define PUT(c, n, b, eob) \ +do { \ + if ((b) >= (eob)) \ + return (-1); \ + *(b) = (c); \ + (b) ++; \ + (n) ++; \ +} while (0) + +static int32_t +kbd_xlate(int32_t code, int32_t make, int32_t *b, int32_t const *eob) +{ + int32_t c, n; + + n = 0; + + if (code >= xsize) + return (0); /* HID code is not in the table */ + + /* Handle special case - Pause/Break */ + if (code == 0x48) { + if (!make) + return (0); /* No break code */ + +#if 0 +XXX FIXME + if (ctrl_is_pressed) { + /* Break (Ctrl-Pause) */ + PUT(0xe0, n, b, eob); + PUT(0x46, n, b, eob); + PUT(0xe0, n, b, eob); + PUT(0xc6, n, b, eob); + } else { + /* Pause */ + PUT(0xe1, n, b, eob); + PUT(0x1d, n, b, eob); + PUT(0x45, n, b, eob); + PUT(0xe1, n, b, eob); + PUT(0x9d, n, b, eob); + PUT(0xc5, n, b, eob); + } +#endif + + return (n); + } + + if ((c = x[code]) == -1) + return (0); /* HID code translation is not defined */ + + if (make) { + if (c & E0PREFIX) + PUT(0xe0, n, b, eob); + + PUT((c & CODEMASK), n, b, eob); + } else if (!(c & NOBREAK)) { + if (c & E0PREFIX) + PUT(0xe0, n, b, eob); + + PUT((0x80|(c & CODEMASK)), n, b, eob); + } + + return (n); +} + +/* + * Process status change from vkbd(4) + */ + +int32_t +kbd_status_changed(bthid_session_p s, uint8_t *data, int32_t len) +{ + vkbd_status_t st; + uint8_t leds, report_id; + hid_device_p hid_device; + hid_data_t d; + hid_item_t h; + + assert(s != NULL); + assert(len == sizeof(vkbd_status_t)); + + memcpy(&st, data, sizeof(st)); + leds = 0; + report_id = NO_REPORT_ID; + + hid_device = get_hid_device(&s->bdaddr); + assert(hid_device != NULL); + + for (d = hid_start_parse(hid_device->desc, 1 << hid_output, -1); + hid_get_item(d, &h) > 0; ) { + if (HID_PAGE(h.usage) == HUP_LEDS) { + if (report_id == NO_REPORT_ID) + report_id = h.report_ID; + else if (h.report_ID != report_id) + syslog(LOG_WARNING, "Output HID report IDs " \ + "for %s do not match: %d vs. %d. " \ + "Please report", + bt_ntoa(&s->bdaddr, NULL), + h.report_ID, report_id); + + switch(HID_USAGE(h.usage)) { + case 0x01: /* Num Lock LED */ + if (st.leds & LED_NUM) + hid_set_data(&leds, &h, 1); + break; + + case 0x02: /* Caps Lock LED */ + if (st.leds & LED_CAP) + hid_set_data(&leds, &h, 1); + break; + + case 0x03: /* Scroll Lock LED */ + if (st.leds & LED_SCR) + hid_set_data(&leds, &h, 1); + break; + + /* XXX add other LEDs ? */ + } + } + } + hid_end_parse(d); + + data[0] = 0xa2; /* DATA output (HID output report) */ + + if (report_id != NO_REPORT_ID) { + data[1] = report_id; + data[2] = leds; + len = 3; + } else { + data[1] = leds; + len = 2; + } + + write(s->intr, data, len); + + return (0); +} + diff --git a/usr.sbin/bluetooth/bthidd/kbd.h b/usr.sbin/bluetooth/bthidd/kbd.h new file mode 100644 index 0000000..552f310 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/kbd.h @@ -0,0 +1,41 @@ +/* + * kbd.h + */ + +/*- + * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: kbd.h,v 1.3 2006/09/07 21:06:53 max Exp $ + * $FreeBSD$ + */ + +#ifndef _KBD_H_ +#define _KBD_H_ + +int32_t kbd_maxkey (void); +int32_t kbd_process_keys (bthid_session_p s); +int32_t kbd_status_changed(bthid_session_p s, uint8_t *data, int32_t len); + +#endif /* ndef _KBD_H_ */ diff --git a/usr.sbin/bluetooth/bthidd/lexer.l b/usr.sbin/bluetooth/bthidd/lexer.l new file mode 100644 index 0000000..6d913ee --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/lexer.l @@ -0,0 +1,106 @@ +%{ +/* + * lexer.l + */ + +/*- + * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: lexer.l,v 1.3 2006/09/07 21:06:53 max Exp $ + * $FreeBSD$ + */ +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <stdlib.h> +#include "parser.h" + + int yylex (void); + +#define YY_DECL int yylex(void) +%} + +%option yylineno noyywrap nounput noinput + +delim [ \t\n] +ws {delim}+ +empty {delim}* +comment \#.* + +hexdigit [0-9a-fA-F] +hexbyte {hexdigit}{hexdigit}? + +device_word device +bdaddr_word bdaddr +control_psm_word control_psm +interrupt_psm_word interrupt_psm +reconnect_initiate_word reconnect_initiate +battery_power_word battery_power +normally_connectable_word normally_connectable +hid_descriptor_word hid_descriptor +true_word true +false_word false + +bdaddrstring {hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte} +hexbytestring 0x{hexbyte} + +%% + +\; return (';'); +\: return (':'); +\{ return ('{'); +\} return ('}'); + +{ws} ; +{empty} ; +{comment} ; + +{device_word} return (T_DEVICE); +{bdaddr_word} return (T_BDADDR); +{control_psm_word} return (T_CONTROL_PSM); +{interrupt_psm_word} return (T_INTERRUPT_PSM); +{reconnect_initiate_word} return (T_RECONNECT_INITIATE); +{battery_power_word} return (T_BATTERY_POWER); +{normally_connectable_word} return (T_NORMALLY_CONNECTABLE); +{hid_descriptor_word} return (T_HID_DESCRIPTOR); +{true_word} return (T_TRUE); +{false_word} return (T_FALSE); + +{bdaddrstring} { + return (bt_aton(yytext, &yylval.bdaddr)? + T_BDADDRSTRING : T_ERROR); + } + +{hexbytestring} { + char *ep; + + yylval.num = strtoul(yytext, &ep, 16); + + return (*ep == '\0'? T_HEXBYTE : T_ERROR); + } + +. return (T_ERROR); + +%% + diff --git a/usr.sbin/bluetooth/bthidd/parser.y b/usr.sbin/bluetooth/bthidd/parser.y new file mode 100644 index 0000000..dbb2763 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/parser.y @@ -0,0 +1,476 @@ +%{ +/* + * parser.y + */ + +/*- + * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: parser.y,v 1.7 2006/09/07 21:06:53 max Exp $ + * $FreeBSD$ + */ + +#include <sys/queue.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <usbhid.h> + +#ifndef BTHIDCONTROL +#include <stdarg.h> +#include <syslog.h> +#define SYSLOG syslog +#define LOGCRIT LOG_CRIT +#define LOGERR LOG_ERR +#define LOGWARNING LOG_WARNING +#define EOL +#else +#define SYSLOG fprintf +#define LOGCRIT stderr +#define LOGERR stderr +#define LOGWARNING stderr +#define EOL "\n" +#endif /* ndef BTHIDCONTROL */ + +#include "bthid_config.h" + + int yylex (void); + void yyerror (char const *); +static int32_t check_hid_device(hid_device_p hid_device); +static void free_hid_device (hid_device_p hid_device); + +extern FILE *yyin; +extern int yylineno; + char const *config_file = BTHIDD_CONFFILE; + char const *hids_file = BTHIDD_HIDSFILE; + +static char buffer[1024]; +static int32_t hid_descriptor_size; +static hid_device_t *hid_device = NULL; +static LIST_HEAD(, hid_device) hid_devices; + +%} + +%union { + bdaddr_t bdaddr; + int32_t num; +} + +%token <bdaddr> T_BDADDRSTRING +%token <num> T_HEXBYTE +%token T_DEVICE T_BDADDR T_CONTROL_PSM T_INTERRUPT_PSM T_RECONNECT_INITIATE +%token T_BATTERY_POWER T_NORMALLY_CONNECTABLE T_HID_DESCRIPTOR +%token T_TRUE T_FALSE T_ERROR + +%% + +config: line + | config line + ; + +line: T_DEVICE + { + hid_device = (hid_device_t *) calloc(1, sizeof(*hid_device)); + if (hid_device == NULL) { + SYSLOG(LOGCRIT, "Could not allocate new " \ + "config entry" EOL); + YYABORT; + } + + hid_device->new_device = 1; + } + '{' options '}' + { + if (check_hid_device(hid_device)) + LIST_INSERT_HEAD(&hid_devices,hid_device,next); + else + free_hid_device(hid_device); + + hid_device = NULL; + } + ; + +options: option ';' + | options option ';' + ; + +option: bdaddr + | control_psm + | interrupt_psm + | reconnect_initiate + | battery_power + | normally_connectable + | hid_descriptor + | parser_error + ; + +bdaddr: T_BDADDR T_BDADDRSTRING + { + memcpy(&hid_device->bdaddr, &$2, sizeof(hid_device->bdaddr)); + } + ; + +control_psm: T_CONTROL_PSM T_HEXBYTE + { + hid_device->control_psm = $2; + } + ; + +interrupt_psm: T_INTERRUPT_PSM T_HEXBYTE + { + hid_device->interrupt_psm = $2; + } + ; + +reconnect_initiate: T_RECONNECT_INITIATE T_TRUE + { + hid_device->reconnect_initiate = 1; + } + | T_RECONNECT_INITIATE T_FALSE + { + hid_device->reconnect_initiate = 0; + } + ; + +battery_power: T_BATTERY_POWER T_TRUE + { + hid_device->battery_power = 1; + } + | T_BATTERY_POWER T_FALSE + { + hid_device->battery_power = 0; + } + ; + +normally_connectable: T_NORMALLY_CONNECTABLE T_TRUE + { + hid_device->normally_connectable = 1; + } + | T_NORMALLY_CONNECTABLE T_FALSE + { + hid_device->normally_connectable = 0; + } + ; + +hid_descriptor: T_HID_DESCRIPTOR + { + hid_descriptor_size = 0; + } + '{' hid_descriptor_bytes '}' + { + if (hid_device->desc != NULL) + hid_dispose_report_desc(hid_device->desc); + + hid_device->desc = hid_use_report_desc((unsigned char *) buffer, hid_descriptor_size); + if (hid_device->desc == NULL) { + SYSLOG(LOGCRIT, "Could not use HID descriptor" EOL); + YYABORT; + } + } + ; + +hid_descriptor_bytes: hid_descriptor_byte + | hid_descriptor_bytes hid_descriptor_byte + ; + +hid_descriptor_byte: T_HEXBYTE + { + if (hid_descriptor_size >= (int32_t) sizeof(buffer)) { + SYSLOG(LOGCRIT, "HID descriptor is too big" EOL); + YYABORT; + } + + buffer[hid_descriptor_size ++] = $1; + } + ; + +parser_error: T_ERROR + { + YYABORT; + } + +%% + +/* Display parser error message */ +void +yyerror(char const *message) +{ + SYSLOG(LOGERR, "%s in line %d" EOL, message, yylineno); +} + +/* Re-read config file */ +int32_t +read_config_file(void) +{ + int32_t e; + + if (config_file == NULL) { + SYSLOG(LOGERR, "Unknown config file name!" EOL); + return (-1); + } + + if ((yyin = fopen(config_file, "r")) == NULL) { + SYSLOG(LOGERR, "Could not open config file '%s'. %s (%d)" EOL, + config_file, strerror(errno), errno); + return (-1); + } + + clean_config(); + if (yyparse() < 0) { + SYSLOG(LOGERR, "Could not parse config file '%s'" EOL, + config_file); + e = -1; + } else + e = 0; + + fclose(yyin); + yyin = NULL; + + return (e); +} + +/* Clean config */ +void +clean_config(void) +{ + while (!LIST_EMPTY(&hid_devices)) { + hid_device_p d = LIST_FIRST(&hid_devices); + + LIST_REMOVE(d, next); + free_hid_device(d); + } +} + +/* Lookup config entry */ +hid_device_p +get_hid_device(bdaddr_p bdaddr) +{ + hid_device_p d; + + LIST_FOREACH(d, &hid_devices, next) + if (memcmp(&d->bdaddr, bdaddr, sizeof(bdaddr_t)) == 0) + break; + + return (d); +} + +/* Get next config entry */ +hid_device_p +get_next_hid_device(hid_device_p d) +{ + return ((d == NULL)? LIST_FIRST(&hid_devices) : LIST_NEXT(d, next)); +} + +/* Print config entry */ +void +print_hid_device(hid_device_p d, FILE *f) +{ + /* XXX FIXME hack! */ + struct report_desc { + unsigned int size; + unsigned char data[1]; + }; + /* XXX FIXME hack! */ + + struct report_desc *desc = (struct report_desc *) d->desc; + uint32_t i; + + fprintf(f, +"device {\n" \ +" bdaddr %s;\n" \ +" control_psm 0x%x;\n" \ +" interrupt_psm 0x%x;\n" \ +" reconnect_initiate %s;\n" \ +" battery_power %s;\n" \ +" normally_connectable %s;\n" \ +" hid_descriptor {", + bt_ntoa(&d->bdaddr, NULL), + d->control_psm, d->interrupt_psm, + d->reconnect_initiate? "true" : "false", + d->battery_power? "true" : "false", + d->normally_connectable? "true" : "false"); + + for (i = 0; i < desc->size; i ++) { + if ((i % 8) == 0) + fprintf(f, "\n "); + + fprintf(f, "0x%2.2x ", desc->data[i]); + } + + fprintf(f, +"\n" \ +" };\n" \ +"}\n"); +} + +/* Check config entry */ +static int32_t +check_hid_device(hid_device_p d) +{ + hid_data_t hd; + hid_item_t hi; + int32_t page; + + if (get_hid_device(&d->bdaddr) != NULL) { + SYSLOG(LOGERR, "Ignoring duplicated entry for bdaddr %s" EOL, + bt_ntoa(&d->bdaddr, NULL)); + return (0); + } + + if (d->control_psm == 0) { + SYSLOG(LOGERR, "Ignoring entry with invalid control PSM" EOL); + return (0); + } + + if (d->interrupt_psm == 0) { + SYSLOG(LOGERR, "Ignoring entry with invalid interrupt PSM" EOL); + return (0); + } + + if (d->desc == NULL) { + SYSLOG(LOGERR, "Ignoring entry without HID descriptor" EOL); + return (0); + } + + /* XXX somehow need to make sure descriptor is valid */ + for (hd = hid_start_parse(d->desc, ~0, -1); hid_get_item(hd, &hi) > 0; ) { + switch (hi.kind) { + case hid_collection: + case hid_endcollection: + case hid_output: + case hid_feature: + break; + + case hid_input: + /* Check if the device may send keystrokes */ + page = HID_PAGE(hi.usage); + if (page == HUP_KEYBOARD) + d->keyboard = 1; + break; + } + } + hid_end_parse(hd); + + return (1); +} + +/* Free config entry */ +static void +free_hid_device(hid_device_p d) +{ + if (d->desc != NULL) + hid_dispose_report_desc(d->desc); + + memset(d, 0, sizeof(*d)); + free(d); +} + +/* Re-read hids file */ +int32_t +read_hids_file(void) +{ + FILE *f; + hid_device_t *d; + char *line; + bdaddr_t bdaddr; + int32_t lineno; + + if (hids_file == NULL) { + SYSLOG(LOGERR, "Unknown HIDs file name!" EOL); + return (-1); + } + + if ((f = fopen(hids_file, "r")) == NULL) { + if (errno == ENOENT) + return (0); + + SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL, + hids_file, strerror(errno), errno); + return (-1); + } + + for (lineno = 1; fgets(buffer, sizeof(buffer), f) != NULL; lineno ++) { + if ((line = strtok(buffer, "\r\n\t ")) == NULL) + continue; /* ignore empty lines */ + + if (!bt_aton(line, &bdaddr)) { + SYSLOG(LOGWARNING, "Ignoring unparseable BD_ADDR in " \ + "%s:%d" EOL, hids_file, lineno); + continue; + } + + if ((d = get_hid_device(&bdaddr)) != NULL) + d->new_device = 0; + } + + fclose(f); + + return (0); +} + +/* Write hids file */ +int32_t +write_hids_file(void) +{ + char path[PATH_MAX]; + FILE *f; + hid_device_t *d; + + if (hids_file == NULL) { + SYSLOG(LOGERR, "Unknown HIDs file name!" EOL); + return (-1); + } + + snprintf(path, sizeof(path), "%s.new", hids_file); + + if ((f = fopen(path, "w")) == NULL) { + SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL, + path, strerror(errno), errno); + return (-1); + } + + LIST_FOREACH(d, &hid_devices, next) + if (!d->new_device) + fprintf(f, "%s\n", bt_ntoa(&d->bdaddr, NULL)); + + fclose(f); + + if (rename(path, hids_file) < 0) { + SYSLOG(LOGERR, "Could not rename new HIDs file '%s' to '%s'. " \ + "%s (%d)" EOL, path, hids_file, strerror(errno), errno); + unlink(path); + return (-1); + } + + return (0); +} + diff --git a/usr.sbin/bluetooth/bthidd/server.c b/usr.sbin/bluetooth/bthidd/server.c new file mode 100644 index 0000000..26aeb4a --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/server.c @@ -0,0 +1,352 @@ +/* + * server.c + */ + +/*- + * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: server.c,v 1.9 2006/09/07 21:06:53 max Exp $ + * $FreeBSD$ + */ + +#include <sys/queue.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <dev/vkbd/vkbd_var.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <usbhid.h> +#include "bthid_config.h" +#include "bthidd.h" +#include "kbd.h" + +#undef max +#define max(x, y) (((x) > (y))? (x) : (y)) + +static int32_t server_accept (bthid_server_p srv, int32_t fd); +static int32_t server_process(bthid_server_p srv, int32_t fd); + +/* + * Initialize server + */ + +int32_t +server_init(bthid_server_p srv) +{ + struct sockaddr_l2cap l2addr; + + assert(srv != NULL); + + srv->ctrl = srv->intr = -1; + FD_ZERO(&srv->rfdset); + FD_ZERO(&srv->wfdset); + LIST_INIT(&srv->sessions); + + /* Open /dev/consolectl */ + srv->cons = open("/dev/consolectl", O_RDWR); + if (srv->cons < 0) { + syslog(LOG_ERR, "Could not open /dev/consolectl. %s (%d)", + strerror(errno), errno); + return (-1); + } + + /* Create control socket */ + srv->ctrl = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP); + if (srv->ctrl < 0) { + syslog(LOG_ERR, "Could not create control L2CAP socket. " \ + "%s (%d)", strerror(errno), errno); + close(srv->cons); + return (-1); + } + + l2addr.l2cap_len = sizeof(l2addr); + l2addr.l2cap_family = AF_BLUETOOTH; + memcpy(&l2addr.l2cap_bdaddr, &srv->bdaddr, sizeof(l2addr.l2cap_bdaddr)); + l2addr.l2cap_psm = htole16(0x11); + l2addr.l2cap_bdaddr_type = BDADDR_BREDR; + l2addr.l2cap_cid = 0; + + if (bind(srv->ctrl, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) { + syslog(LOG_ERR, "Could not bind control L2CAP socket. " \ + "%s (%d)", strerror(errno), errno); + close(srv->ctrl); + close(srv->cons); + return (-1); + } + + if (listen(srv->ctrl, 10) < 0) { + syslog(LOG_ERR, "Could not listen on control L2CAP socket. " \ + "%s (%d)", strerror(errno), errno); + close(srv->ctrl); + close(srv->cons); + return (-1); + } + + /* Create intrrupt socket */ + srv->intr = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP); + if (srv->intr < 0) { + syslog(LOG_ERR, "Could not create interrupt L2CAP socket. " \ + "%s (%d)", strerror(errno), errno); + close(srv->ctrl); + close(srv->cons); + return (-1); + } + + l2addr.l2cap_psm = htole16(0x13); + + if (bind(srv->intr, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) { + syslog(LOG_ERR, "Could not bind interrupt L2CAP socket. " \ + "%s (%d)", strerror(errno), errno); + close(srv->intr); + close(srv->ctrl); + close(srv->cons); + return (-1); + } + + if (listen(srv->intr, 10) < 0) { + syslog(LOG_ERR, "Could not listen on interrupt L2CAP socket. "\ + "%s (%d)", strerror(errno), errno); + close(srv->intr); + close(srv->ctrl); + close(srv->cons); + return (-1); + } + + FD_SET(srv->ctrl, &srv->rfdset); + FD_SET(srv->intr, &srv->rfdset); + srv->maxfd = max(srv->ctrl, srv->intr); + + return (0); +} + +/* + * Shutdown server + */ + +void +server_shutdown(bthid_server_p srv) +{ + assert(srv != NULL); + + close(srv->cons); + close(srv->ctrl); + close(srv->intr); + + while (!LIST_EMPTY(&srv->sessions)) + session_close(LIST_FIRST(&srv->sessions)); + + memset(srv, 0, sizeof(*srv)); +} + +/* + * Do one server iteration + */ + +int32_t +server_do(bthid_server_p srv) +{ + struct timeval tv; + fd_set rfdset, wfdset; + int32_t n, fd; + + assert(srv != NULL); + + tv.tv_sec = 1; + tv.tv_usec = 0; + + /* Copy cached version of the fd sets and call select */ + memcpy(&rfdset, &srv->rfdset, sizeof(rfdset)); + memcpy(&wfdset, &srv->wfdset, sizeof(wfdset)); + + n = select(srv->maxfd + 1, &rfdset, &wfdset, NULL, &tv); + if (n < 0) { + if (errno == EINTR) + return (0); + + syslog(LOG_ERR, "Could not select(%d, %p, %p). %s (%d)", + srv->maxfd + 1, &rfdset, &wfdset, strerror(errno), errno); + + return (-1); + } + + /* Process descriptors (if any) */ + for (fd = 0; fd < srv->maxfd + 1 && n > 0; fd ++) { + if (FD_ISSET(fd, &rfdset)) { + n --; + + if (fd == srv->ctrl || fd == srv->intr) + server_accept(srv, fd); + else + server_process(srv, fd); + } else if (FD_ISSET(fd, &wfdset)) { + n --; + + client_connect(srv, fd); + } + } + + return (0); +} + +/* + * Accept new connection + */ + +static int32_t +server_accept(bthid_server_p srv, int32_t fd) +{ + bthid_session_p s; + hid_device_p d; + struct sockaddr_l2cap l2addr; + int32_t new_fd; + socklen_t len; + + len = sizeof(l2addr); + if ((new_fd = accept(fd, (struct sockaddr *) &l2addr, &len)) < 0) { + syslog(LOG_ERR, "Could not accept %s connection. %s (%d)", + (fd == srv->ctrl)? "control" : "interrupt", + strerror(errno), errno); + return (-1); + } + + /* Is device configured? */ + if ((d = get_hid_device(&l2addr.l2cap_bdaddr)) == NULL) { + syslog(LOG_ERR, "Rejecting %s connection from %s. " \ + "Device not configured", + (fd == srv->ctrl)? "control" : "interrupt", + bt_ntoa(&l2addr.l2cap_bdaddr, NULL)); + close(new_fd); + return (-1); + } + + /* Check if we have session for the device */ + if ((s = session_by_bdaddr(srv, &l2addr.l2cap_bdaddr)) == NULL) { + d->new_device = 0; /* reset new device flag */ + write_hids_file(); + + /* Create new inbound session */ + if ((s = session_open(srv, d)) == NULL) { + syslog(LOG_CRIT, "Could not open inbound session " + "for %s", bt_ntoa(&l2addr.l2cap_bdaddr, NULL)); + close(new_fd); + return (-1); + } + } + + /* Update descriptors */ + if (fd == srv->ctrl) { + assert(s->ctrl == -1); + s->ctrl = new_fd; + s->state = (s->intr == -1)? W4INTR : OPEN; + } else { + assert(s->intr == -1); + s->intr = new_fd; + s->state = (s->ctrl == -1)? W4CTRL : OPEN; + } + + FD_SET(new_fd, &srv->rfdset); + if (new_fd > srv->maxfd) + srv->maxfd = new_fd; + + syslog(LOG_NOTICE, "Accepted %s connection from %s", + (fd == srv->ctrl)? "control" : "interrupt", + bt_ntoa(&l2addr.l2cap_bdaddr, NULL)); + + /* Register session's vkbd descriptor (if needed) for read */ + if (s->state == OPEN && d->keyboard) { + assert(s->vkbd != -1); + + FD_SET(s->vkbd, &srv->rfdset); + if (s->vkbd > srv->maxfd) + srv->maxfd = s->vkbd; + } + + return (0); +} + +/* + * Process data on the connection + */ + +static int32_t +server_process(bthid_server_p srv, int32_t fd) +{ + bthid_session_p s = session_by_fd(srv, fd); + int32_t len, to_read; + int32_t (*cb)(bthid_session_p, uint8_t *, int32_t); + union { + uint8_t b[1024]; + vkbd_status_t s; + } data; + + if (s == NULL) + return (0); /* can happen on device disconnect */ + + + if (fd == s->ctrl) { + cb = hid_control; + to_read = sizeof(data.b); + } else if (fd == s->intr) { + cb = hid_interrupt; + to_read = sizeof(data.b); + } else { + assert(fd == s->vkbd); + + cb = kbd_status_changed; + to_read = sizeof(data.s); + } + + do { + len = read(fd, &data, to_read); + } while (len < 0 && errno == EINTR); + + if (len < 0) { + syslog(LOG_ERR, "Could not read data from %s (%s). %s (%d)", + bt_ntoa(&s->bdaddr, NULL), + (fd == s->ctrl)? "control" : "interrupt", + strerror(errno), errno); + session_close(s); + return (0); + } + + if (len == 0) { + syslog(LOG_NOTICE, "Remote device %s has closed %s connection", + bt_ntoa(&s->bdaddr, NULL), + (fd == s->ctrl)? "control" : "interrupt"); + session_close(s); + return (0); + } + + (*cb)(s, (uint8_t *) &data, len); + + return (0); +} + diff --git a/usr.sbin/bluetooth/bthidd/session.c b/usr.sbin/bluetooth/bthidd/session.c new file mode 100644 index 0000000..260cb86 --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/session.c @@ -0,0 +1,185 @@ +/* + * session.c + */ + +/*- + * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: session.c,v 1.3 2006/09/07 21:06:53 max Exp $ + * $FreeBSD$ + */ + +#include <sys/queue.h> +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <usbhid.h> +#include "bthid_config.h" +#include "bthidd.h" +#include "kbd.h" + +/* + * Create new session + */ + +bthid_session_p +session_open(bthid_server_p srv, hid_device_p const d) +{ + bthid_session_p s; + + assert(srv != NULL); + assert(d != NULL); + + if ((s = (bthid_session_p) malloc(sizeof(*s))) == NULL) + return (NULL); + + s->srv = srv; + memcpy(&s->bdaddr, &d->bdaddr, sizeof(s->bdaddr)); + s->ctrl = -1; + s->intr = -1; + + if (d->keyboard) { + /* Open /dev/vkbdctl */ + s->vkbd = open("/dev/vkbdctl", O_RDWR); + if (s->vkbd < 0) { + syslog(LOG_ERR, "Could not open /dev/vkbdctl " \ + "for %s. %s (%d)", bt_ntoa(&s->bdaddr, NULL), + strerror(errno), errno); + free(s); + return (NULL); + } + } else + s->vkbd = -1; + + s->state = CLOSED; + + s->keys1 = bit_alloc(kbd_maxkey()); + if (s->keys1 == NULL) { + free(s); + return (NULL); + } + + s->keys2 = bit_alloc(kbd_maxkey()); + if (s->keys2 == NULL) { + free(s->keys1); + free(s); + return (NULL); + } + + LIST_INSERT_HEAD(&srv->sessions, s, next); + + return (s); +} + +/* + * Lookup session by bdaddr + */ + +bthid_session_p +session_by_bdaddr(bthid_server_p srv, bdaddr_p bdaddr) +{ + bthid_session_p s; + + assert(srv != NULL); + assert(bdaddr != NULL); + + LIST_FOREACH(s, &srv->sessions, next) + if (memcmp(&s->bdaddr, bdaddr, sizeof(s->bdaddr)) == 0) + break; + + return (s); +} + +/* + * Lookup session by fd + */ + +bthid_session_p +session_by_fd(bthid_server_p srv, int32_t fd) +{ + bthid_session_p s; + + assert(srv != NULL); + assert(fd >= 0); + + LIST_FOREACH(s, &srv->sessions, next) + if (s->ctrl == fd || s->intr == fd || s->vkbd == fd) + break; + + return (s); +} + +/* + * Close session + */ + +void +session_close(bthid_session_p s) +{ + assert(s != NULL); + assert(s->srv != NULL); + + LIST_REMOVE(s, next); + + if (s->intr != -1) { + FD_CLR(s->intr, &s->srv->rfdset); + FD_CLR(s->intr, &s->srv->wfdset); + close(s->intr); + + if (s->srv->maxfd == s->intr) + s->srv->maxfd --; + } + + if (s->ctrl != -1) { + FD_CLR(s->ctrl, &s->srv->rfdset); + FD_CLR(s->ctrl, &s->srv->wfdset); + close(s->ctrl); + + if (s->srv->maxfd == s->ctrl) + s->srv->maxfd --; + } + + if (s->vkbd != -1) { + FD_CLR(s->vkbd, &s->srv->rfdset); + close(s->vkbd); + + if (s->srv->maxfd == s->vkbd) + s->srv->maxfd --; + } + + free(s->keys1); + free(s->keys2); + + memset(s, 0, sizeof(*s)); + free(s); +} + |