| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2007-2008 Texas Instruments, Inc. |
| * Copyright (C) 2005-2010 Marcel Holtmann <marcel@holtmann.org> |
| * |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <termios.h> |
| #include <time.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <sys/param.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| #include <sys/uio.h> |
| |
| #include <bluetooth/bluetooth.h> |
| #include <bluetooth/hci.h> |
| #include <bluetooth/hci_lib.h> |
| |
| #include "hciattach.h" |
| |
| #ifdef HCIATTACH_DEBUG |
| #define DPRINTF(x...) printf(x) |
| #else |
| #define DPRINTF(x...) |
| #endif |
| |
| #define HCIUARTGETDEVICE _IOR('U', 202, int) |
| |
| #define MAKEWORD(a, b) ((uint16_t)(((uint8_t)(a)) | ((uint16_t)((uint8_t)(b))) << 8)) |
| |
| #define TI_MANUFACTURER_ID 13 |
| |
| #define FIRMWARE_DIRECTORY "/lib/firmware/" |
| |
| #define ACTION_SEND_COMMAND 1 |
| #define ACTION_WAIT_EVENT 2 |
| #define ACTION_SERIAL 3 |
| #define ACTION_DELAY 4 |
| #define ACTION_RUN_SCRIPT 5 |
| #define ACTION_REMARKS 6 |
| |
| #define BRF_DEEP_SLEEP_OPCODE_BYTE_1 0x0c |
| #define BRF_DEEP_SLEEP_OPCODE_BYTE_2 0xfd |
| #define BRF_DEEP_SLEEP_OPCODE \ |
| (BRF_DEEP_SLEEP_OPCODE_BYTE_1 | (BRF_DEEP_SLEEP_OPCODE_BYTE_2 << 8)) |
| |
| #define FILE_HEADER_MAGIC 0x42535442 |
| |
| /* |
| * BRF Firmware header |
| */ |
| struct bts_header { |
| uint32_t magic; |
| uint32_t version; |
| uint8_t future[24]; |
| uint8_t actions[0]; |
| }__attribute__ ((packed)); |
| |
| /* |
| * BRF Actions structure |
| */ |
| struct bts_action { |
| uint16_t type; |
| uint16_t size; |
| uint8_t data[0]; |
| } __attribute__ ((packed)); |
| |
| struct bts_action_send { |
| uint8_t data[0]; |
| } __attribute__ ((packed)); |
| |
| struct bts_action_wait { |
| uint32_t msec; |
| uint32_t size; |
| uint8_t data[0]; |
| }__attribute__ ((packed)); |
| |
| struct bts_action_delay { |
| uint32_t msec; |
| }__attribute__ ((packed)); |
| |
| struct bts_action_serial { |
| uint32_t baud; |
| uint32_t flow_control; |
| }__attribute__ ((packed)); |
| |
| static FILE *bts_load_script(const char* file_name, uint32_t* version) |
| { |
| struct bts_header header; |
| FILE* fp; |
| |
| fp = fopen(file_name, "rb"); |
| if (!fp) { |
| perror("can't open firmware file"); |
| goto out; |
| } |
| |
| if (1 != fread(&header, sizeof(struct bts_header), 1, fp)) { |
| perror("can't read firmware file"); |
| goto errclose; |
| } |
| |
| if (header.magic != FILE_HEADER_MAGIC) { |
| fprintf(stderr, "%s not a legal TI firmware file\n", file_name); |
| goto errclose; |
| } |
| |
| if (NULL != version) |
| *version = header.version; |
| |
| goto out; |
| |
| errclose: |
| fclose(fp); |
| fp = NULL; |
| out: |
| return fp; |
| } |
| |
| static unsigned long bts_fetch_action(FILE* fp, unsigned char* action_buf, |
| unsigned long buf_size, uint16_t* action_type) |
| { |
| struct bts_action action_hdr; |
| unsigned long nread; |
| |
| if (!fp) |
| return 0; |
| |
| if (1 != fread(&action_hdr, sizeof(struct bts_action), 1, fp)) |
| return 0; |
| |
| if (action_hdr.size > buf_size) { |
| fprintf(stderr, "bts_next_action: not enough space to read next action\n"); |
| return 0; |
| } |
| |
| nread = fread(action_buf, sizeof(uint8_t), action_hdr.size, fp); |
| if (nread != (action_hdr.size)) { |
| fprintf(stderr, "bts_next_action: fread failed to read next action\n"); |
| return 0; |
| } |
| |
| *action_type = action_hdr.type; |
| |
| return nread * sizeof(uint8_t); |
| } |
| |
| static void bts_unload_script(FILE* fp) |
| { |
| if (fp) |
| fclose(fp); |
| } |
| |
| static int is_it_texas(const uint8_t *respond) |
| { |
| uint16_t manufacturer_id; |
| |
| manufacturer_id = MAKEWORD(respond[11], respond[12]); |
| |
| return TI_MANUFACTURER_ID == manufacturer_id ? 1 : 0; |
| } |
| |
| static const char *get_firmware_name(const uint8_t *respond) |
| { |
| static char firmware_file_name[PATH_MAX] = {0}; |
| uint16_t version = 0, chip = 0, min_ver = 0, maj_ver = 0; |
| |
| version = MAKEWORD(respond[13], respond[14]); |
| chip = (version & 0x7C00) >> 10; |
| min_ver = (version & 0x007F); |
| maj_ver = (version & 0x0380) >> 7; |
| |
| if (version & 0x8000) |
| maj_ver |= 0x0008; |
| |
| sprintf(firmware_file_name, FIRMWARE_DIRECTORY "TIInit_%d.%d.%d.bts", chip, maj_ver, min_ver); |
| |
| return firmware_file_name; |
| } |
| |
| static void brf_delay(struct bts_action_delay *delay) |
| { |
| usleep(1000 * delay->msec); |
| } |
| |
| static int brf_set_serial_params(struct bts_action_serial *serial_action, |
| int fd, struct termios *ti) |
| { |
| fprintf(stderr, "texas: changing baud rate to %u, flow control to %u\n", |
| serial_action->baud, serial_action->flow_control ); |
| tcflush(fd, TCIOFLUSH); |
| |
| if (serial_action->flow_control) |
| ti->c_cflag |= CRTSCTS; |
| else |
| ti->c_cflag &= ~CRTSCTS; |
| |
| if (tcsetattr(fd, TCSANOW, ti) < 0) { |
| perror("Can't set port settings"); |
| return -1; |
| } |
| |
| tcflush(fd, TCIOFLUSH); |
| |
| if (set_speed(fd, ti, serial_action->baud) < 0) { |
| perror("Can't set baud rate"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int brf_send_command_socket(int fd, struct bts_action_send* send_action) |
| { |
| char response[1024] = {0}; |
| hci_command_hdr *cmd = (hci_command_hdr *) send_action->data; |
| uint16_t opcode = cmd->opcode; |
| |
| struct hci_request rq; |
| memset(&rq, 0, sizeof(rq)); |
| rq.ogf = cmd_opcode_ogf(opcode); |
| rq.ocf = cmd_opcode_ocf(opcode); |
| rq.event = EVT_CMD_COMPLETE; |
| rq.cparam = &send_action->data[3]; |
| rq.clen = send_action->data[2]; |
| rq.rparam = response; |
| rq.rlen = sizeof(response); |
| |
| if (hci_send_req(fd, &rq, 15) < 0) { |
| perror("Cannot send hci command to socket"); |
| return -1; |
| } |
| |
| /* verify success */ |
| if (response[0]) { |
| errno = EIO; |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int brf_send_command_file(int fd, struct bts_action_send* send_action, long size) |
| { |
| unsigned char response[1024] = {0}; |
| long ret = 0; |
| |
| /* send command */ |
| if (size != write(fd, send_action, size)) { |
| perror("Texas: Failed to write action command"); |
| return -1; |
| } |
| |
| /* read response */ |
| ret = read_hci_event(fd, response, sizeof(response)); |
| if (ret < 0) { |
| perror("texas: failed to read command response"); |
| return -1; |
| } |
| |
| /* verify success */ |
| if (ret < 7 || 0 != response[6]) { |
| fprintf( stderr, "TI init command failed.\n" ); |
| errno = EIO; |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| |
| static int brf_send_command(int fd, struct bts_action_send* send_action, long size, int hcill_installed) |
| { |
| int ret = 0; |
| char *fixed_action; |
| |
| /* remove packet type when giving to socket API */ |
| if (hcill_installed) { |
| fixed_action = ((char *) send_action) + 1; |
| ret = brf_send_command_socket(fd, (struct bts_action_send *) fixed_action); |
| } else { |
| ret = brf_send_command_file(fd, send_action, size); |
| } |
| |
| return ret; |
| } |
| |
| static int brf_do_action(uint16_t brf_type, uint8_t *brf_action, long brf_size, |
| int fd, struct termios *ti, int hcill_installed) |
| { |
| int ret = 0; |
| |
| switch (brf_type) { |
| case ACTION_SEND_COMMAND: |
| DPRINTF("W"); |
| ret = brf_send_command(fd, (struct bts_action_send*) brf_action, brf_size, hcill_installed); |
| break; |
| case ACTION_WAIT_EVENT: |
| DPRINTF("R"); |
| break; |
| case ACTION_SERIAL: |
| DPRINTF("S"); |
| ret = brf_set_serial_params((struct bts_action_serial *) brf_action, fd, ti); |
| break; |
| case ACTION_DELAY: |
| DPRINTF("D"); |
| brf_delay((struct bts_action_delay *) brf_action); |
| break; |
| case ACTION_REMARKS: |
| DPRINTF("C"); |
| break; |
| default: |
| fprintf(stderr, "brf_init: unknown firmware action type (%d)\n", brf_type); |
| break; |
| } |
| |
| return ret; |
| } |
| |
| /* |
| * tests whether a given brf action is a HCI_VS_Sleep_Mode_Configurations cmd |
| */ |
| static int brf_action_is_deep_sleep(uint8_t *brf_action, long brf_size, |
| uint16_t brf_type) |
| { |
| uint16_t opcode; |
| |
| if (brf_type != ACTION_SEND_COMMAND) |
| return 0; |
| |
| if (brf_size < 3) |
| return 0; |
| |
| if (brf_action[0] != HCI_COMMAND_PKT) |
| return 0; |
| |
| /* HCI data is little endian */ |
| opcode = brf_action[1] | (brf_action[2] << 8); |
| |
| if (opcode != BRF_DEEP_SLEEP_OPCODE) |
| return 0; |
| |
| /* action is deep sleep configuration command ! */ |
| return 1; |
| } |
| |
| /* |
| * This function is called twice. |
| * The first time it is called, it loads the brf script, and executes its |
| * commands until it reaches a deep sleep command (or its end). |
| * The second time it is called, it assumes HCILL protocol is set up, |
| * and sends rest of brf script via the supplied socket. |
| */ |
| static int brf_do_script(int fd, struct termios *ti, const char *bts_file) |
| { |
| int ret = 0, hcill_installed = bts_file ? 0 : 1; |
| uint32_t vers; |
| static FILE *brf_script_file = NULL; |
| static uint8_t brf_action[512]; |
| static long brf_size; |
| static uint16_t brf_type; |
| |
| /* is it the first time we are called ? */ |
| if (0 == hcill_installed) { |
| DPRINTF("Sending script to serial device\n"); |
| brf_script_file = bts_load_script(bts_file, &vers ); |
| if (!brf_script_file) { |
| fprintf(stderr, "Warning: cannot find BTS file: %s\n", |
| bts_file); |
| return 0; |
| } |
| |
| fprintf( stderr, "Loaded BTS script version %u\n", vers ); |
| |
| brf_size = bts_fetch_action(brf_script_file, brf_action, |
| sizeof(brf_action), &brf_type); |
| if (brf_size == 0) { |
| fprintf(stderr, "Warning: BTS file is empty !"); |
| return 0; |
| } |
| } |
| else { |
| DPRINTF("Sending script to bluetooth socket\n"); |
| } |
| |
| /* execute current action and continue to parse brf script file */ |
| while (brf_size != 0) { |
| ret = brf_do_action(brf_type, brf_action, brf_size, |
| fd, ti, hcill_installed); |
| if (ret == -1) |
| break; |
| |
| brf_size = bts_fetch_action(brf_script_file, brf_action, |
| sizeof(brf_action), &brf_type); |
| |
| /* if this is the first time we run (no HCILL yet) */ |
| /* and a deep sleep command is encountered */ |
| /* we exit */ |
| if (!hcill_installed && |
| brf_action_is_deep_sleep(brf_action, |
| brf_size, brf_type)) |
| return 0; |
| } |
| |
| bts_unload_script(brf_script_file); |
| brf_script_file = NULL; |
| DPRINTF("\n"); |
| |
| return ret; |
| } |
| |
| int texas_init(int fd, struct termios *ti) |
| { |
| struct timespec tm = {0, 50000}; |
| char cmd[4]; |
| unsigned char resp[100]; /* Response */ |
| const char *bts_file; |
| int n; |
| |
| memset(resp,'\0', 100); |
| |
| /* It is possible to get software version with manufacturer specific |
| HCI command HCI_VS_TI_Version_Number. But the only thing you get more |
| is if this is point-to-point or point-to-multipoint module */ |
| |
| /* Get Manufacturer and LMP version */ |
| cmd[0] = HCI_COMMAND_PKT; |
| cmd[1] = 0x01; |
| cmd[2] = 0x10; |
| cmd[3] = 0x00; |
| |
| do { |
| n = write(fd, cmd, 4); |
| if (n < 0) { |
| perror("Failed to write init command (READ_LOCAL_VERSION_INFORMATION)"); |
| return -1; |
| } |
| if (n < 4) { |
| fprintf(stderr, "Wanted to write 4 bytes, could only write %d. Stop\n", n); |
| return -1; |
| } |
| |
| /* Read reply. */ |
| if (read_hci_event(fd, resp, 100) < 0) { |
| perror("Failed to read init response (READ_LOCAL_VERSION_INFORMATION)"); |
| return -1; |
| } |
| |
| /* Wait for command complete event for our Opcode */ |
| } while (resp[4] != cmd[1] && resp[5] != cmd[2]); |
| |
| /* Verify manufacturer */ |
| if (! is_it_texas(resp)) { |
| fprintf(stderr,"ERROR: module's manufacturer is not Texas Instruments\n"); |
| return -1; |
| } |
| |
| fprintf(stderr, "Found a Texas Instruments' chip!\n"); |
| |
| bts_file = get_firmware_name(resp); |
| fprintf(stderr, "Firmware file : %s\n", bts_file); |
| |
| n = brf_do_script(fd, ti, bts_file); |
| |
| nanosleep(&tm, NULL); |
| |
| return n; |
| } |
| |
| int texas_post(int fd, struct termios *ti) |
| { |
| int dev_id, dd, ret = 0; |
| |
| sleep(1); |
| |
| dev_id = ioctl(fd, HCIUARTGETDEVICE, 0); |
| if (dev_id < 0) { |
| perror("cannot get device id"); |
| return -1; |
| } |
| |
| DPRINTF("\nAdded device hci%d\n", dev_id); |
| |
| dd = hci_open_dev(dev_id); |
| if (dd < 0) { |
| perror("HCI device open failed"); |
| return -1; |
| } |
| |
| if (ioctl(dd, HCIDEVUP, dev_id) < 0 && errno != EALREADY) { |
| fprintf(stderr, "Can't init device hci%d: %s (%d)", dev_id, |
| strerror(errno), errno); |
| hci_close_dev(dd); |
| return -1; |
| } |
| |
| ret = brf_do_script(dd, ti, NULL); |
| |
| hci_close_dev(dd); |
| |
| return ret; |
| } |