diff options
-rw-r--r-- | libs/usb/tests/accessorytest/Android.mk | 25 | ||||
-rw-r--r-- | libs/usb/tests/accessorytest/accessory.c | 79 | ||||
-rw-r--r-- | libs/usb/tests/accessorytest/accessory.h | 26 | ||||
-rw-r--r-- | libs/usb/tests/accessorytest/audio.c | 221 | ||||
-rw-r--r-- | libs/usb/tests/accessorytest/f_accessory.h | 148 | ||||
-rw-r--r-- | libs/usb/tests/accessorytest/hid.c | 202 | ||||
-rw-r--r-- | libs/usb/tests/accessorytest/usb.c | 227 |
7 files changed, 928 insertions, 0 deletions
diff --git a/libs/usb/tests/accessorytest/Android.mk b/libs/usb/tests/accessorytest/Android.mk new file mode 100644 index 000000000000..6d9a9460c675 --- /dev/null +++ b/libs/usb/tests/accessorytest/Android.mk @@ -0,0 +1,25 @@ +LOCAL_PATH:= $(call my-dir) + +# Build for Linux host only +ifeq ($(HOST_OS),linux) + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := accessory.c \ + audio.c \ + hid.c \ + usb.c + +LOCAL_C_INCLUDES += external/tinyalsa/include + +LOCAL_MODULE := accessorytest + +LOCAL_STATIC_LIBRARIES := libusbhost libcutils libtinyalsa +LOCAL_LDLIBS += -lpthread +LOCAL_CFLAGS := -g -O0 + +include $(BUILD_HOST_EXECUTABLE) + +endif diff --git a/libs/usb/tests/accessorytest/accessory.c b/libs/usb/tests/accessorytest/accessory.c new file mode 100644 index 000000000000..a3c47af7d14e --- /dev/null +++ b/libs/usb/tests/accessorytest/accessory.c @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> + +#include "accessory.h" + +static void usage(char* name) { + fprintf(stderr, "Usage: %s [-a] [-h] [-ic input card] [-id input device] " + "[-oc output card] [-d output device] [-a] [-h] [-i]\n\n" + "\t-ic, -id, -oc and -od specify ALSA card and device numbers\n" + "\t-a : enables AccessoryChat mode\n" + "\t-i : enables HID pass through (requires running as root\n" + "\t-h : prints this usage message\n", name); +} + +int main(int argc, char* argv[]) +{ + unsigned int input_card = 2; + unsigned int input_device = 0; + unsigned int output_card = 0; + unsigned int output_device = 0; + unsigned int enable_accessory = 0; + unsigned int enable_hid = 0; + + /* parse command line arguments */ + argv += 1; + while (*argv) { + if (strcmp(*argv, "-ic") == 0) { + argv++; + if (*argv) + input_card = atoi(*argv); + } else if (strcmp(*argv, "-id") == 0) { + argv++; + if (*argv) + input_device = atoi(*argv); + } else if (strcmp(*argv, "-oc") == 0) { + argv++; + if (*argv) + output_card = atoi(*argv); + } else if (strcmp(*argv, "-od") == 0) { + argv++; + if (*argv) + output_device = atoi(*argv); + } else if (strcmp(*argv, "-a") == 0) { + enable_accessory = 1; + } else if (strcmp(*argv, "-h") == 0) { + usage(argv[0]); + return 1; + } else if (strcmp(*argv, "-i") == 0) { + enable_hid = 1; + } + if (*argv) + argv++; + } + + init_audio(input_card, input_device, output_card, output_device); + if (enable_hid) + init_hid(); + usb_run(enable_accessory); + + return 0; +} diff --git a/libs/usb/tests/accessorytest/accessory.h b/libs/usb/tests/accessorytest/accessory.h new file mode 100644 index 000000000000..55c4550f79e0 --- /dev/null +++ b/libs/usb/tests/accessorytest/accessory.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __ACCESSORY_H__ +#define __ACCESSORY_H__ + +int init_audio(unsigned int ic, unsigned int id, unsigned int oc, unsigned int od); +void init_hid(); +void usb_run(int enable_accessory); + +struct usb_device* usb_wait_for_device(); + +#endif /* __ACCESSORY_H__ */ diff --git a/libs/usb/tests/accessorytest/audio.c b/libs/usb/tests/accessorytest/audio.c new file mode 100644 index 000000000000..d23d9b3fa5d3 --- /dev/null +++ b/libs/usb/tests/accessorytest/audio.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <signal.h> +#include <unistd.h> +#include <pthread.h> +#include <errno.h> +#include <string.h> + +#include <tinyalsa/asoundlib.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/ioctl.h> + +#include "accessory.h" + +#define BUFFER_COUNT 2 +#define BUFFER_SIZE 16384 + +#define BUFFER_EMPTY 0 +#define BUFFER_BUSY 1 +#define BUFFER_FULL 2 + +static char* buffers[BUFFER_COUNT]; +static int buffer_states[BUFFER_COUNT]; +static int empty_index = 0; +static int full_index = -1; + +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t empty_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t full_cond = PTHREAD_COND_INITIALIZER; + +static unsigned int input_card; +static unsigned int input_device; + +static int get_empty() +{ + int index, other; + + pthread_mutex_lock(&mutex); + + while (empty_index == -1) + pthread_cond_wait(&empty_cond, &mutex); + + index = empty_index; + other = (index == 0 ? 1 : 0); + buffer_states[index] = BUFFER_BUSY; + if (buffer_states[other] == BUFFER_EMPTY) + empty_index = other; + else + empty_index = -1; + + pthread_mutex_unlock(&mutex); + return index; +} + +static void put_empty(int index) +{ + pthread_mutex_lock(&mutex); + + buffer_states[index] = BUFFER_EMPTY; + if (empty_index == -1) { + empty_index = index; + pthread_cond_signal(&empty_cond); + } + + pthread_mutex_unlock(&mutex); +} + +static int get_full() +{ + int index, other; + + pthread_mutex_lock(&mutex); + + while (full_index == -1) + pthread_cond_wait(&full_cond, &mutex); + + index = full_index; + other = (index == 0 ? 1 : 0); + buffer_states[index] = BUFFER_BUSY; + if (buffer_states[other] == BUFFER_FULL) + full_index = other; + else + full_index = -1; + + pthread_mutex_unlock(&mutex); + return index; +} + +static void put_full(int index) +{ + pthread_mutex_lock(&mutex); + + buffer_states[index] = BUFFER_FULL; + if (full_index == -1) { + full_index = index; + pthread_cond_signal(&full_cond); + } + + pthread_mutex_unlock(&mutex); +} + +static void* capture_thread(void* arg) +{ + struct pcm_config config; + struct pcm *pcm = NULL; + + fprintf(stderr, "capture_thread start\n"); + + memset(&config, 0, sizeof(config)); + + config.channels = 2; + config.rate = 44100; + config.period_size = 1024; + config.period_count = 4; + config.format = PCM_FORMAT_S16_LE; + + while (1) { + while (!pcm) { + pcm = pcm_open(input_card, input_device, PCM_IN, &config); + if (pcm && !pcm_is_ready(pcm)) { + pcm_close(pcm); + pcm = NULL; + } + if (!pcm) + sleep(1); + } + + while (pcm) { + int index = get_empty(); + if (pcm_read(pcm, buffers[index], BUFFER_SIZE)) { + put_empty(index); + pcm_close(pcm); + pcm = NULL; + } else { + put_full(index); + } + } + } + + fprintf(stderr, "capture_thread done\n"); + return NULL; +} + +static void* play_thread(void* arg) +{ + struct pcm *pcm = arg; + char *buffer; + int index, err; + + fprintf(stderr, "play_thread start\n"); + + while (1) { + index = get_full(); + + err = pcm_write(pcm, buffers[index], BUFFER_SIZE); + if (err) + fprintf(stderr, "pcm_write err: %d\n", err); + + put_empty(index); + } + + fprintf(stderr, "play_thread done\n"); + pcm_close(pcm); + free(buffer); + + return NULL; +} + +int init_audio(unsigned int ic, unsigned int id, unsigned int oc, unsigned int od) +{ + pthread_t tid; + struct pcm_config config; + struct pcm *pcm; + int i; + + input_card = ic; + input_device = id; + + for (i = 0; i < BUFFER_COUNT; i++) { + buffers[i] = malloc(BUFFER_SIZE); + buffer_states[i] = BUFFER_EMPTY; + } + + memset(&config, 0, sizeof(config)); + config.channels = 2; + config.rate = 44100; + config.period_size = 1024; + config.period_count = 4; + config.format = PCM_FORMAT_S16_LE; + + pcm = pcm_open(oc, od, PCM_OUT, &config); + if (!pcm || !pcm_is_ready(pcm)) { + fprintf(stderr, "Unable to open PCM device %d/%d for output (%s)\n", + oc, od, pcm_get_error(pcm)); + return -1; + } + + pthread_create(&tid, NULL, capture_thread, NULL); + pthread_create(&tid, NULL, play_thread, pcm); + return 0; +} diff --git a/libs/usb/tests/accessorytest/f_accessory.h b/libs/usb/tests/accessorytest/f_accessory.h new file mode 100644 index 000000000000..312f4ba6eed3 --- /dev/null +++ b/libs/usb/tests/accessorytest/f_accessory.h @@ -0,0 +1,148 @@ +/* + * Gadget Function Driver for Android USB accessories + * + * Copyright (C) 2011 Google, Inc. + * Author: Mike Lockwood <lockwood@android.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef __LINUX_USB_F_ACCESSORY_H +#define __LINUX_USB_F_ACCESSORY_H + +/* Use Google Vendor ID when in accessory mode */ +#define USB_ACCESSORY_VENDOR_ID 0x18D1 + + +/* Product ID to use when in accessory mode */ +#define USB_ACCESSORY_PRODUCT_ID 0x2D00 + +/* Product ID to use when in accessory mode and adb is enabled */ +#define USB_ACCESSORY_ADB_PRODUCT_ID 0x2D01 + +/* Indexes for strings sent by the host via ACCESSORY_SEND_STRING */ +#define ACCESSORY_STRING_MANUFACTURER 0 +#define ACCESSORY_STRING_MODEL 1 +#define ACCESSORY_STRING_DESCRIPTION 2 +#define ACCESSORY_STRING_VERSION 3 +#define ACCESSORY_STRING_URI 4 +#define ACCESSORY_STRING_SERIAL 5 + +/* Control request for retrieving device's protocol version + * + * requestType: USB_DIR_IN | USB_TYPE_VENDOR + * request: ACCESSORY_GET_PROTOCOL + * value: 0 + * index: 0 + * data version number (16 bits little endian) + * 1 for original accessory support + * 2 adds audio and HID support + */ +#define ACCESSORY_GET_PROTOCOL 51 + +/* Control request for host to send a string to the device + * + * requestType: USB_DIR_OUT | USB_TYPE_VENDOR + * request: ACCESSORY_SEND_STRING + * value: 0 + * index: string ID + * data zero terminated UTF8 string + * + * The device can later retrieve these strings via the + * ACCESSORY_GET_STRING_* ioctls + */ +#define ACCESSORY_SEND_STRING 52 + +/* Control request for starting device in accessory mode. + * The host sends this after setting all its strings to the device. + * + * requestType: USB_DIR_OUT | USB_TYPE_VENDOR + * request: ACCESSORY_START + * value: 0 + * index: 0 + * data none + */ +#define ACCESSORY_START 53 + +/* Control request for registering a HID device. + * Upon registering, a unique ID is sent by the accessory in the + * value parameter. This ID will be used for future commands for + * the device + * + * requestType: USB_DIR_OUT | USB_TYPE_VENDOR + * request: ACCESSORY_REGISTER_HID_DEVICE + * value: Accessory assigned ID for the HID device + * index: total length of the HID report descriptor + * data none + */ +#define ACCESSORY_REGISTER_HID 54 + +/* Control request for unregistering a HID device. + * + * requestType: USB_DIR_OUT | USB_TYPE_VENDOR + * request: ACCESSORY_REGISTER_HID + * value: Accessory assigned ID for the HID device + * index: 0 + * data none + */ +#define ACCESSORY_UNREGISTER_HID 55 + +/* Control request for sending the HID report descriptor. + * If the HID descriptor is longer than the endpoint zero max packet size, + * the descriptor will be sent in multiple ACCESSORY_SET_HID_REPORT_DESC + * commands. The data for the descriptor must be sent sequentially + * if multiple packets are needed. + * + * requestType: USB_DIR_OUT | USB_TYPE_VENDOR + * request: ACCESSORY_SET_HID_REPORT_DESC + * value: Accessory assigned ID for the HID device + * index: offset of data in descriptor + * (needed when HID descriptor is too big for one packet) + * data the HID report descriptor + */ +#define ACCESSORY_SET_HID_REPORT_DESC 56 + +/* Control request for sending HID events. + * + * requestType: USB_DIR_OUT | USB_TYPE_VENDOR + * request: ACCESSORY_SEND_HID_EVENT + * value: Accessory assigned ID for the HID device + * index: 0 + * data the HID report for the event + */ +#define ACCESSORY_SEND_HID_EVENT 57 + +/* Control request for setting the audio mode. + * + * requestType: USB_DIR_OUT | USB_TYPE_VENDOR + * request: ACCESSORY_SET_AUDIO_MODE + * value: 0 - no audio + * 1 - device to host, 44100 16-bit stereo PCM + * index: 0 + * data the HID report for the event + */ +#define ACCESSORY_SET_AUDIO_MODE 58 + + + +/* ioctls for retrieving strings set by the host */ +#define ACCESSORY_GET_STRING_MANUFACTURER _IOW('M', 1, char[256]) +#define ACCESSORY_GET_STRING_MODEL _IOW('M', 2, char[256]) +#define ACCESSORY_GET_STRING_DESCRIPTION _IOW('M', 3, char[256]) +#define ACCESSORY_GET_STRING_VERSION _IOW('M', 4, char[256]) +#define ACCESSORY_GET_STRING_URI _IOW('M', 5, char[256]) +#define ACCESSORY_GET_STRING_SERIAL _IOW('M', 6, char[256]) +/* returns 1 if there is a start request pending */ +#define ACCESSORY_IS_START_REQUESTED _IO('M', 7) +/* returns audio mode (set via the ACCESSORY_SET_AUDIO_MODE control request) */ +#define ACCESSORY_GET_AUDIO_MODE _IO('M', 8) + +#endif /* __LINUX_USB_F_ACCESSORY_H */ diff --git a/libs/usb/tests/accessorytest/hid.c b/libs/usb/tests/accessorytest/hid.c new file mode 100644 index 000000000000..b70d678543ca --- /dev/null +++ b/libs/usb/tests/accessorytest/hid.c @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <errno.h> +#include <pthread.h> +#include <time.h> + +#include <sys/inotify.h> +#include <linux/hidraw.h> +#include <usbhost/usbhost.h> + +#include "f_accessory.h" +#include "accessory.h" + +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +static int next_id = 1; + +static void milli_sleep(int millis) { + struct timespec tm; + + tm.tv_sec = 0; + tm.tv_nsec = millis * 1000000; + nanosleep(&tm, NULL); +} + +static void* hid_thread(void* arg) { + int fd = (int)arg; + char buffer[4096]; + int id, ret, offset; + struct usb_device *device; + struct usb_device_descriptor *device_desc; + int max_packet; + struct hidraw_report_descriptor desc; + int desc_length; + + fprintf(stderr, "hid_thread start fd: %d\n", fd); + + if (ioctl(fd, HIDIOCGRDESCSIZE, &desc_length)) { + fprintf(stderr, "HIDIOCGRDESCSIZE failed\n"); + close(fd); + goto err; + } + + desc.size = HID_MAX_DESCRIPTOR_SIZE - 1; + if (ioctl(fd, HIDIOCGRDESC, &desc)) { + fprintf(stderr, "HIDIOCGRDESC failed\n"); + close(fd); + goto err; + } + +wait_for_device: + fprintf(stderr, "waiting for device fd: %d\n", fd); + device = usb_wait_for_device(); + max_packet = usb_device_get_device_descriptor(device)->bMaxPacketSize0; + // FIXME + max_packet--; + + // FIXME + milli_sleep(500); + + pthread_mutex_lock(&mutex); + id = next_id++; + + ret = usb_device_control_transfer(device, USB_DIR_OUT | USB_TYPE_VENDOR, + ACCESSORY_REGISTER_HID, id, desc_length, NULL, 0, 1000); + fprintf(stderr, "ACCESSORY_REGISTER_HID returned %d\n", ret); + + // FIXME + milli_sleep(500); + + for (offset = 0; offset < desc_length; ) { + int count = desc_length - offset; + if (count > max_packet) count = max_packet; + + fprintf(stderr, "sending ACCESSORY_SET_HID_REPORT_DESC offset: %d count: %d desc_length: %d\n", + offset, count, desc_length); + ret = usb_device_control_transfer(device, USB_DIR_OUT | USB_TYPE_VENDOR, + ACCESSORY_SET_HID_REPORT_DESC, id, offset, &desc.value[offset], count, 1000); + fprintf(stderr, "ACCESSORY_SET_HID_REPORT_DESC returned %d errno %d\n", ret, errno); + offset += count; + } + + pthread_mutex_unlock(&mutex); + + while (1) { + ret = read(fd, buffer, sizeof(buffer)); + if (ret < 0) { +fprintf(stderr, "read failed, errno: %d, fd: %d\n", errno, fd); + break; + } + + ret = usb_device_control_transfer(device, USB_DIR_OUT | USB_TYPE_VENDOR, + ACCESSORY_SEND_HID_EVENT, id, 0, buffer, ret, 1000); + if (ret < 0 && errno != EPIPE) { +fprintf(stderr, "ACCESSORY_SEND_HID_EVENT returned %d errno: %d\n", ret, errno); + goto wait_for_device; + } + } + +fprintf(stderr, "ACCESSORY_UNREGISTER_HID\n"); + ret = usb_device_control_transfer(device, USB_DIR_OUT | USB_TYPE_VENDOR, + ACCESSORY_UNREGISTER_HID, id, 0, NULL, 0, 1000); + +fprintf(stderr, "hid thread exiting\n"); +err: + return NULL; +} + +static void open_hid(const char* name) +{ + char path[100]; + + snprintf(path, sizeof(path), "/dev/%s", name); + int fd = open(path, O_RDWR); + if (fd < 0) return; + + fprintf(stderr, "opened /dev/%s\n", name); + pthread_t th; + pthread_create(&th, NULL, hid_thread, (void *)fd); +} + +static void* inotify_thread(void* arg) +{ + open_hid("hidraw0"); + open_hid("hidraw1"); + open_hid("hidraw2"); + open_hid("hidraw3"); + open_hid("hidraw4"); + open_hid("hidraw5"); + open_hid("hidraw6"); + open_hid("hidraw7"); + open_hid("hidraw8"); + open_hid("hidraw9"); + + int inotify_fd = inotify_init(); + inotify_add_watch(inotify_fd, "/dev", IN_DELETE | IN_CREATE); + + while (1) { + char event_buf[512]; + struct inotify_event *event; + int event_pos = 0; + int event_size; + + int count = read(inotify_fd, event_buf, sizeof(event_buf)); + if (count < (int)sizeof(*event)) { + if(errno == EINTR) + continue; + fprintf(stderr, "could not get event, %s\n", strerror(errno)); + break; + } + while (count >= (int)sizeof(*event)) { + event = (struct inotify_event *)(event_buf + event_pos); + //fprintf(stderr, "%d: %08x \"%s\"\n", event->wd, event->mask, + // event->len ? event->name : ""); + if (event->len) { + if(event->mask & IN_CREATE) { + fprintf(stderr, "created %s\n", event->name); + // FIXME + milli_sleep(50); + open_hid(event->name); + } else { + fprintf(stderr, "lost %s\n", event->name); + } + } + event_size = sizeof(*event) + event->len; + count -= event_size; + event_pos += event_size; + } + } + + close(inotify_fd); + return NULL; +} + +void init_hid() +{ + pthread_t th; + pthread_create(&th, NULL, inotify_thread, NULL); +} diff --git a/libs/usb/tests/accessorytest/usb.c b/libs/usb/tests/accessorytest/usb.c new file mode 100644 index 000000000000..ac72b35b908e --- /dev/null +++ b/libs/usb/tests/accessorytest/usb.c @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <signal.h> +#include <pthread.h> +#include <errno.h> +#include <string.h> + +#include <usbhost/usbhost.h> +#include "f_accessory.h" + +#include "accessory.h" + +static struct usb_device *current_device = NULL; +static uint8_t read_ep; +static uint8_t write_ep; + +static pthread_mutex_t device_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t device_cond = PTHREAD_COND_INITIALIZER; + +static void milli_sleep(int millis) { + struct timespec tm; + + tm.tv_sec = 0; + tm.tv_nsec = millis * 1000000; + nanosleep(&tm, NULL); +} + +static void* read_thread(void* arg) { + int ret = 0; + + while (current_device && ret >= 0) { + char buffer[16384]; + + ret = usb_device_bulk_transfer(current_device, read_ep, buffer, sizeof(buffer), 1000); + if (ret < 0 && errno == ETIMEDOUT) + ret = 0; + if (ret > 0) { + fwrite(buffer, 1, ret, stdout); + fprintf(stderr, "\n"); + fflush(stdout); + } + } + + return NULL; +} + +static void* write_thread(void* arg) { + int ret = 0; + + while (ret >= 0) { + char buffer[16384]; + char *line = fgets(buffer, sizeof(buffer), stdin); + if (!line || !current_device) + break; + ret = usb_device_bulk_transfer(current_device, write_ep, line, strlen(line), 1000); + } + + return NULL; +} + +static void send_string(struct usb_device *device, int index, const char* string) { + usb_device_control_transfer(device, USB_DIR_OUT | USB_TYPE_VENDOR, + ACCESSORY_SEND_STRING, 0, index, (void *)string, strlen(string) + 1, 0); + + // some devices can't handle back-to-back requests, so delay a bit + milli_sleep(10); +} + +static int usb_device_added(const char *devname, void* client_data) { + uint16_t vendorId, productId; + int ret; + int enable_accessory = (int)client_data; + + struct usb_device *device = usb_device_open(devname); + if (!device) { + fprintf(stderr, "usb_device_open failed\n"); + return 0; + } + + vendorId = usb_device_get_vendor_id(device); + productId = usb_device_get_product_id(device); + + if (!current_device && vendorId == 0x18D1 && productId >= 0x2D00 && productId <= 0x2D05) { + + pthread_mutex_lock(&device_mutex); + fprintf(stderr, "Found android device in accessory mode\n"); + current_device = device; + pthread_cond_broadcast(&device_cond); + pthread_mutex_unlock(&device_mutex); + + if (enable_accessory) { + struct usb_descriptor_header* desc; + struct usb_descriptor_iter iter; + struct usb_interface_descriptor *intf = NULL; + struct usb_endpoint_descriptor *ep1 = NULL; + struct usb_endpoint_descriptor *ep2 = NULL; + pthread_t th; + + usb_descriptor_iter_init(device, &iter); + while ((desc = usb_descriptor_iter_next(&iter)) != NULL && (!intf || !ep1 || !ep2)) { + if (desc->bDescriptorType == USB_DT_INTERFACE) { + intf = (struct usb_interface_descriptor *)desc; + } else if (desc->bDescriptorType == USB_DT_ENDPOINT) { + if (ep1) + ep2 = (struct usb_endpoint_descriptor *)desc; + else + ep1 = (struct usb_endpoint_descriptor *)desc; + } + } + + if (!intf) { + fprintf(stderr, "interface not found\n"); + exit(1); + } + if (!ep1 || !ep2) { + fprintf(stderr, "endpoints not found\n"); + exit(1); + } + + if (usb_device_claim_interface(device, intf->bInterfaceNumber)) { + fprintf(stderr, "usb_device_claim_interface failed errno: %d\n", errno); + exit(1); + } + + if ((ep1->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { + read_ep = ep1->bEndpointAddress; + write_ep = ep2->bEndpointAddress; + } else { + read_ep = ep2->bEndpointAddress; + write_ep = ep1->bEndpointAddress; + } + + pthread_create(&th, NULL, read_thread, NULL); + pthread_create(&th, NULL, write_thread, NULL); + } + } else { +// fprintf(stderr, "Found new device - attempting to switch to accessory mode\n"); + + uint16_t protocol = -1; + ret = usb_device_control_transfer(device, USB_DIR_IN | USB_TYPE_VENDOR, + ACCESSORY_GET_PROTOCOL, 0, 0, &protocol, sizeof(protocol), 1000); + if (ret < 0) { + // fprintf(stderr, "ACCESSORY_GET_PROTOCOL returned %d errno: %d\n", ret, errno); + } else { + fprintf(stderr, "device supports protocol version %d\n", protocol); + if (protocol >= 2) { + if (enable_accessory) { + send_string(device, ACCESSORY_STRING_MANUFACTURER, "Google, Inc."); + send_string(device, ACCESSORY_STRING_MODEL, "AccessoryChat"); + send_string(device, ACCESSORY_STRING_DESCRIPTION, "Accessory Chat"); + send_string(device, ACCESSORY_STRING_VERSION, "1.0"); + send_string(device, ACCESSORY_STRING_URI, "http://www.android.com"); + send_string(device, ACCESSORY_STRING_SERIAL, "1234567890"); + } + + fprintf(stderr, "sending ACCESSORY_SET_AUDIO_MODE\n"); + ret = usb_device_control_transfer(device, USB_DIR_OUT | USB_TYPE_VENDOR, + ACCESSORY_SET_AUDIO_MODE, 1, 0, NULL, 0, 1000); + if (ret < 0) + fprintf(stderr, "ACCESSORY_SET_AUDIO_MODE returned %d errno: %d\n", ret, errno); + + fprintf(stderr, "sending ACCESSORY_START\n"); + ret = usb_device_control_transfer(device, USB_DIR_OUT | USB_TYPE_VENDOR, + ACCESSORY_START, 0, 0, NULL, 0, 1000); + fprintf(stderr, "did ACCESSORY_START\n"); + if (ret < 0) + fprintf(stderr, "ACCESSORY_START returned %d errno: %d\n", ret, errno); + } + } + + return 0; + } + + if (device != current_device) + usb_device_close(device); + + return 0; +} + +static int usb_device_removed(const char *devname, void* client_data) { + pthread_mutex_lock(&device_mutex); + + if (current_device && !strcmp(usb_device_get_name(current_device), devname)) { + fprintf(stderr, "current device disconnected\n"); + usb_device_close(current_device); + current_device = NULL; + } + + pthread_mutex_unlock(&device_mutex); + return 0; +} + +struct usb_device* usb_wait_for_device() { + struct usb_device* device = NULL; + + pthread_mutex_lock(&device_mutex); + while (!current_device) + pthread_cond_wait(&device_cond, &device_mutex); + device = current_device; + pthread_mutex_unlock(&device_mutex); + + return device; +} + +void usb_run(int enable_accessory) { + struct usb_host_context* context = usb_host_init(); + + usb_host_run(context, usb_device_added, usb_device_removed, NULL, (void *)enable_accessory); +} + |