diff options
Diffstat (limited to 'bt-vendor/hci.c')
-rw-r--r-- | bt-vendor/hci.c | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/bt-vendor/hci.c b/bt-vendor/hci.c new file mode 100644 index 0000000..c83d3e1 --- /dev/null +++ b/bt-vendor/hci.c @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2014 Paul Kocialkowski <contact@paulk.fr> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> +#include <termios.h> + +#define LOG_TAG "gta04_bt_vendor" +#include <cutils/log.h> + +#include <bt_vendor_lib.h> +#include <bt_hci_bdroid.h> + +#include "gta04_bt_vendor.h" + +int gta04_bt_vendor_hci_cmd_prepare(void *buffer, size_t length) +{ + HC_BT_HDR *header; + + if (buffer == NULL || length < BT_HC_HDR_SIZE) + return -EINVAL; + + header = (HC_BT_HDR *) buffer; + header->event = MSG_STACK_TO_HC_HCI_CMD; + header->len = length - BT_HC_HDR_SIZE; + header->offset = 0; + header->layer_specific = 0; + + return 0; +} + +int gta04_bt_vendor_hci_preamble(void *buffer, size_t length, + unsigned short cmd) +{ + unsigned char *p; + + if (buffer == NULL || length < HCI_CMD_PREAMBLE_SIZE) + return -EINVAL; + + p = (unsigned char *) buffer; + + p[0] = cmd & 0xff; + p[1] = cmd >> 8; + p[2] = length - HCI_CMD_PREAMBLE_SIZE; + + return 0; +} + +int gta04_bt_vendor_hci_bccmd(void *buffer, size_t length, + unsigned short pdu, unsigned short varid) +{ + static unsigned bccmd_seq = 0; + size_t bccmd_words_count = HCI_BCCMD_WORDS_COUNT; + size_t bccmd_length = bccmd_words_count * sizeof(unsigned short) + 1; + unsigned char *p; + + if (buffer == NULL || length < bccmd_length) + return -EINVAL; + + memset(buffer, 0, 11); + + bccmd_words_count = (length - 1) / 2; + + p = (unsigned char *) buffer; + + // Descriptor + p[0] = HCI_BCCMD_DESCRIPTOR; + + // Payload + p[1] = pdu & 0xff; + p[2] = pdu >> 8; + p[3] = bccmd_words_count & 0xff; + p[4] = bccmd_words_count >> 8; + p[5] = bccmd_seq & 0xff; + p[6] = bccmd_seq >> 8; + p[7] = varid & 0xff; + p[8] = varid >> 8; + p[9] = HCI_BCCMD_STAT_OK & 0xff; + p[10] = HCI_BCCMD_STAT_OK >> 8; + + bccmd_seq++; + + return 0; +} + +unsigned short gta04_bt_vendor_hci_bccmd_speed(speed_t speed) +{ + switch (speed) { + case B0: + case B50: + case B75: + case B110: + case B134: + case B150: + case B200: + return 0; + case B300: + return 1; + case B600: + return 3; + case B1200: + return 5; + case B1800: + return 7; + case B2400: + return 10; + case B4800: + return 20; + case B9600: + return 39; + case B19200: + return 79; + case B38400: + return 157; + case B57600: + return 236; + case B115200: + return 472; + case B230400: + return 944; + case B460800: + return 1887; + case B500000: + return 2048; + case B576000: + return 2359; + case B921600: + return 3775; + case B1000000: + return 4096; + case B1152000: + return 4719; + case B1500000: + return 6144; + case B2000000: + return 8192; + case B2500000: + return 10240; + case B3000000: + return 12288; + case B3500000: + return 14336; + case B4000000: + return 16384; + default: + return 12288; + } +} + +int gta04_bt_vendor_hci_h4_write(void *buffer, size_t length) +{ + void *data; + size_t size; + unsigned char *p; + int rc; + + if (buffer == NULL || length < BT_HC_HDR_SIZE + HCI_CMD_PREAMBLE_SIZE) + return -EINVAL; + + p = (unsigned char *) buffer + BT_HC_HDR_SIZE - 1; + + data = (void *) p; + size = length - BT_HC_HDR_SIZE + 1; + + p[0] = HCI_H4_TYPE_CMD; + + rc = gta04_bt_vendor_serial_write(data, size); + if (rc < 0) { + ALOGE("%s: Writing to serial failed", __func__); + return -1; + } + + return 0; +} + +int gta04_bt_vendor_hci_h4_read_event(void *buffer, size_t length, + unsigned char event) +{ + struct timeval timeout; + unsigned char cleanup[256]; + unsigned char byte; + unsigned char type = 0; + unsigned char size = 0; + unsigned char *p = NULL; + unsigned int count; + unsigned int limit; + int failures; + int skip = 0; + fd_set fds; + int rc; + + if (gta04_bt_vendor == NULL || gta04_bt_vendor->serial_fd < 0) + return -1; + + failures = 0; + + while (1) { + FD_ZERO(&fds); + FD_SET(gta04_bt_vendor->serial_fd, &fds); + + timeout.tv_sec = 5; + timeout.tv_usec = 0; + + rc = select(gta04_bt_vendor->serial_fd + 1, &fds, NULL, NULL, &timeout); + if (rc < 0) { + ALOGE("%s: Polling failed", __func__); + + if (failures) { + return -1; + } else { + failures++; + continue; + } + } else if (rc == 0) { + ALOGE("%s: Polling timed out", __func__); + return -1; + } + + if (failures > 10) { + ALOGE("%s: Too many failures", __func__); + return -1; + } + + if (p == NULL) { + // Read single bytes first + + rc = gta04_bt_vendor_serial_read(&byte, sizeof(byte)); + if (rc < (int) sizeof(byte)) { + ALOGE("%s: Reading from serial failed", __func__); + return -1; + } + + if (type != HCI_H4_TYPE_EVENT) { + if (byte != HCI_H4_TYPE_EVENT) { + ALOGD("%s: Ignored response with type: %d", __func__, byte); + gta04_bt_vendor_serial_read(&cleanup, sizeof(cleanup)); + continue; + } + + type = byte; + } else { + if (byte != event) { + ALOGD("%s: Ignored response with event: 0x%x", __func__, byte); + gta04_bt_vendor_serial_read(&cleanup, sizeof(cleanup)); + type = 0; + continue; + } + + rc = gta04_bt_vendor_serial_read(&size, sizeof(size)); + if (rc <= 0) { + ALOGE("%s: Reading from serial failed", __func__); + failures++; + continue; + } else if (failures) { + failures = 0; + } + + + if (size == 0) + return 0; + + if (buffer == NULL || length == 0) { + count = 0; + + while (count < size) { + rc = gta04_bt_vendor_serial_read(&cleanup, size - count); + if (rc <= 0) { + ALOGE("%s: Reading from serial failed", __func__); + failures++; + break; + } else if (failures) { + failures = 0; + } + + count += rc; + } + + if (rc <= 0) + continue; + + return 0; + } + + p = (unsigned char *) buffer; + } + } + + if (p != NULL) { + // Make sure to read from the start in case of failure + p = (unsigned char *) buffer; + + if (size > length) { + ALOGE("%s: Provided buffer length is too small for size: %d/%d bytes", __func__, length, size); + + limit = length; + } else { + limit = size; + } + + count = 0; + + while (count < limit) { + rc = gta04_bt_vendor_serial_read(p, limit - count); + if (rc <= 0) { + ALOGE("%s: Reading from serial failed", __func__); + failures++; + break; + } else if (failures) { + failures = 0; + } + + p += rc; + count += rc; + } + + if (rc <= 0) + continue; + + if (limit < size) { + limit = size; + + while (count < limit) { + rc = gta04_bt_vendor_serial_read(&cleanup, limit - count); + if (rc <= 0) { + ALOGE("%s: Reading from serial failed", __func__); + failures++; + break; + } else if (failures) { + failures = 0; + } + + count += rc; + } + } + + if (rc <= 0) + continue; + + return 0; + } + } + + return 0; +} |