| /* |
| * |
| * Connection Manager |
| * |
| * Copyright (C) 2012 Intel Corporation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * 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 |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <stdint.h> |
| #include <sys/ioctl.h> |
| #include <termios.h> |
| #include <unistd.h> |
| |
| #include <gdbus.h> |
| |
| #define CONNMAN_API_SUBJECT_TO_CHANGE |
| #include <connman/plugin.h> |
| #include <connman/log.h> |
| |
| #define TIST_SYSFS_INSTALL "/sys/devices/platform/kim/install" |
| #define TIST_SYSFS_UART "/sys/devices/platform/kim/dev_name" |
| #define TIST_SYSFS_BAUD "/sys/devices/platform/kim/baud_rate" |
| |
| /* Shared transport line discipline */ |
| #define N_TI_WL 22 |
| |
| static GIOChannel *install_channel = NULL; |
| static GIOChannel *uart_channel = NULL; |
| static char uart_dev_name[32]; |
| static unsigned long baud_rate = 0; |
| |
| static guint install_watch = 0; |
| static guint uart_watch = 0; |
| |
| static int install_count = 0; |
| |
| #define NCCS2 19 |
| struct termios2 { |
| tcflag_t c_iflag; /* input mode flags */ |
| tcflag_t c_oflag; /* output mode flags */ |
| tcflag_t c_cflag; /* control mode flags */ |
| tcflag_t c_lflag; /* local mode flags */ |
| cc_t c_line; /* line discipline */ |
| cc_t c_cc[NCCS2]; /* control characters */ |
| speed_t c_ispeed; /* input speed */ |
| speed_t c_ospeed; /* output speed */ |
| }; |
| |
| #define BOTHER 0x00001000 |
| |
| /* HCI definitions */ |
| #define HCI_HDR_OPCODE 0xff36 |
| #define HCI_COMMAND_PKT 0x01 |
| #define HCI_EVENT_PKT 0x04 |
| #define EVT_CMD_COMPLETE 0x0E |
| |
| /* HCI Command structure to set the target baud rate */ |
| struct speed_change_cmd { |
| uint8_t uart_prefix; |
| uint16_t opcode; |
| uint8_t plen; |
| uint32_t speed; |
| } __attribute__ ((packed)); |
| |
| /* HCI Event structure to set the cusrom baud rate*/ |
| struct cmd_complete { |
| uint8_t uart_prefix; |
| uint8_t evt; |
| uint8_t plen; |
| uint8_t ncmd; |
| uint16_t opcode; |
| uint8_t status; |
| uint8_t data[16]; |
| } __attribute__ ((packed)); |
| |
| static int read_baud_rate(unsigned long *baud) |
| { |
| int err; |
| FILE *f; |
| |
| DBG(""); |
| |
| f = fopen(TIST_SYSFS_BAUD, "r"); |
| if (!f) |
| return -EIO; |
| |
| err = fscanf(f, "%lu", baud); |
| fclose(f); |
| |
| DBG("baud rate %lu", *baud); |
| |
| return err; |
| } |
| |
| static int read_uart_name(char uart_name[], size_t uart_name_len) |
| { |
| int err; |
| FILE *f; |
| |
| DBG(""); |
| |
| memset(uart_name, 0, uart_name_len); |
| |
| f = fopen(TIST_SYSFS_UART, "r"); |
| if (!f) |
| return -EIO; |
| |
| err = fscanf(f, "%s", uart_name); |
| fclose(f); |
| |
| DBG("UART name %s", uart_name); |
| |
| return err; |
| } |
| |
| static int read_hci_event(int fd, unsigned char *buf, int size) |
| { |
| int prefix_len, param_len; |
| |
| if (size <= 0) |
| return -EINVAL; |
| |
| /* First 3 bytes are prefix, event and param length */ |
| prefix_len = read(fd, buf, 3); |
| if (prefix_len < 0) |
| return prefix_len; |
| |
| if (prefix_len < 3) { |
| connman_error("Truncated HCI prefix %d bytes 0x%x", |
| prefix_len, buf[0]); |
| return -EIO; |
| } |
| |
| DBG("type 0x%x event 0x%x param len %d", buf[0], buf[1], buf[2]); |
| |
| param_len = buf[2]; |
| if (param_len > size - 3) { |
| connman_error("Buffer is too small %d", size); |
| return -EINVAL; |
| } |
| |
| return read(fd, buf + 3, param_len); |
| } |
| |
| static int read_command_complete(int fd, unsigned short opcode) |
| { |
| struct cmd_complete resp; |
| int err; |
| |
| DBG(""); |
| |
| err = read_hci_event(fd, (unsigned char *)&resp, sizeof(resp)); |
| if (err < 0) |
| return err; |
| |
| DBG("HCI event %d bytes", err); |
| |
| if (resp.uart_prefix != HCI_EVENT_PKT) { |
| connman_error("Not an event packet"); |
| return -EIO; |
| } |
| |
| if (resp.evt != EVT_CMD_COMPLETE) { |
| connman_error("Not a cmd complete event"); |
| return -EIO; |
| } |
| |
| if (resp.plen < 4) { |
| connman_error("HCI header length %d", resp.plen); |
| return -EIO; |
| } |
| |
| if (resp.opcode != (unsigned short) opcode) { |
| connman_error("opcode 0x%04x 0x%04x", resp.opcode, opcode); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| /* The default baud rate is 115200 */ |
| static int set_default_baud_rate(int fd) |
| { |
| struct termios ti; |
| int err; |
| |
| DBG(""); |
| |
| err = tcflush(fd, TCIOFLUSH); |
| if (err < 0) |
| goto err; |
| |
| err = tcgetattr(fd, &ti); |
| if (err < 0) |
| goto err; |
| |
| cfmakeraw(&ti); |
| |
| ti.c_cflag |= 1; |
| ti.c_cflag |= CRTSCTS; |
| |
| err = tcsetattr(fd, TCSANOW, &ti); |
| if (err < 0) |
| goto err; |
| |
| cfsetospeed(&ti, B115200); |
| cfsetispeed(&ti, B115200); |
| |
| err = tcsetattr(fd, TCSANOW, &ti); |
| if (err < 0) |
| goto err; |
| |
| err = tcflush(fd, TCIOFLUSH); |
| if (err < 0) |
| goto err; |
| |
| return 0; |
| |
| err: |
| connman_error("%s", strerror(errno)); |
| |
| return err; |
| } |
| |
| static int set_custom_baud_rate(int fd, unsigned long cus_baud_rate, int flow_ctrl) |
| { |
| struct termios ti; |
| struct termios2 ti2; |
| int err; |
| |
| DBG("baud rate %lu flow_ctrl %d", cus_baud_rate, flow_ctrl); |
| |
| err = tcflush(fd, TCIOFLUSH); |
| if (err < 0) |
| goto err; |
| |
| err = tcgetattr(fd, &ti); |
| if (err < 0) |
| goto err; |
| |
| if (flow_ctrl) |
| ti.c_cflag |= CRTSCTS; |
| else |
| ti.c_cflag &= ~CRTSCTS; |
| |
| /* |
| * Set the parameters associated with the UART |
| * The change will occur immediately by using TCSANOW. |
| */ |
| err = tcsetattr(fd, TCSANOW, &ti); |
| if (err < 0) |
| goto err; |
| |
| err = tcflush(fd, TCIOFLUSH); |
| if (err < 0) |
| goto err; |
| |
| /* Set the actual baud rate */ |
| err = ioctl(fd, TCGETS2, &ti2); |
| if (err < 0) |
| goto err; |
| |
| ti2.c_cflag &= ~CBAUD; |
| ti2.c_cflag |= BOTHER; |
| ti2.c_ospeed = cus_baud_rate; |
| |
| err = ioctl(fd, TCSETS2, &ti2); |
| if (err < 0) |
| goto err; |
| |
| return 0; |
| |
| err: |
| DBG("%s", strerror(errno)); |
| |
| return err; |
| } |
| |
| static gboolean uart_event(GIOChannel *channel, |
| GIOCondition cond, gpointer data) |
| { |
| int uart_fd, ldisc; |
| |
| DBG(""); |
| |
| uart_fd = g_io_channel_unix_get_fd(channel); |
| |
| if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR)) { |
| connman_error("UART event 0x%x", cond); |
| if (uart_watch > 0) |
| g_source_remove(uart_watch); |
| |
| goto err; |
| } |
| |
| if (read_command_complete(uart_fd, HCI_HDR_OPCODE) < 0) |
| goto err; |
| |
| if (set_custom_baud_rate(uart_fd, baud_rate, 1) < 0) |
| goto err; |
| |
| ldisc = N_TI_WL; |
| if (ioctl(uart_fd, TIOCSETD, &ldisc) < 0) |
| goto err; |
| |
| install_count = 1; |
| __sync_synchronize(); |
| |
| return FALSE; |
| |
| err: |
| install_count = 0; |
| __sync_synchronize(); |
| |
| g_io_channel_shutdown(channel, TRUE, NULL); |
| g_io_channel_unref(channel); |
| |
| return FALSE; |
| } |
| |
| static int install_ldisc(GIOChannel *channel, bool install) |
| { |
| int uart_fd, err; |
| struct speed_change_cmd cmd; |
| GIOFlags flags; |
| |
| DBG("%d %p", install, uart_channel); |
| |
| if (!install) { |
| install_count = 0; |
| __sync_synchronize(); |
| |
| if (!uart_channel) { |
| DBG("UART channel is NULL"); |
| return 0; |
| } |
| |
| g_io_channel_shutdown(uart_channel, TRUE, NULL); |
| g_io_channel_unref(uart_channel); |
| |
| uart_channel = NULL; |
| |
| return 0; |
| } |
| |
| if (uart_channel) { |
| g_io_channel_shutdown(uart_channel, TRUE, NULL); |
| g_io_channel_unref(uart_channel); |
| uart_channel = NULL; |
| } |
| |
| DBG("opening %s custom baud %lu", uart_dev_name, baud_rate); |
| |
| uart_fd = open(uart_dev_name, O_RDWR | O_CLOEXEC); |
| if (uart_fd < 0) |
| return -EIO; |
| |
| uart_channel = g_io_channel_unix_new(uart_fd); |
| g_io_channel_set_close_on_unref(uart_channel, TRUE); |
| |
| g_io_channel_set_encoding(uart_channel, NULL, NULL); |
| g_io_channel_set_buffered(uart_channel, FALSE); |
| |
| flags = g_io_channel_get_flags(uart_channel); |
| flags |= G_IO_FLAG_NONBLOCK; |
| g_io_channel_set_flags(uart_channel, flags, NULL); |
| |
| err = set_default_baud_rate(uart_fd); |
| if (err < 0) { |
| g_io_channel_shutdown(uart_channel, TRUE, NULL); |
| g_io_channel_unref(uart_channel); |
| uart_channel = NULL; |
| |
| return err; |
| } |
| |
| if (baud_rate == 115200) { |
| int ldisc; |
| |
| ldisc = N_TI_WL; |
| if (ioctl(uart_fd, TIOCSETD, &ldisc) < 0) { |
| g_io_channel_shutdown(uart_channel, TRUE, NULL); |
| g_io_channel_unref(uart_channel); |
| uart_channel = NULL; |
| } |
| |
| install_count = 0; |
| __sync_synchronize(); |
| |
| return 0; |
| } |
| |
| cmd.uart_prefix = HCI_COMMAND_PKT; |
| cmd.opcode = HCI_HDR_OPCODE; |
| cmd.plen = sizeof(unsigned long); |
| cmd.speed = baud_rate; |
| |
| uart_watch = g_io_add_watch(uart_channel, |
| G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR, |
| uart_event, NULL); |
| |
| err = write(uart_fd, &cmd, sizeof(cmd)); |
| if (err < 0) { |
| connman_error("Write failed %d", err); |
| |
| g_io_channel_shutdown(uart_channel, TRUE, NULL); |
| g_io_channel_unref(uart_channel); |
| uart_channel = NULL; |
| } |
| |
| return err; |
| } |
| |
| |
| static gboolean install_event(GIOChannel *channel, |
| GIOCondition cond, gpointer data) |
| { |
| GIOStatus status = G_IO_STATUS_NORMAL; |
| unsigned int install_state; |
| bool install; |
| char buf[8]; |
| gsize len; |
| |
| DBG(""); |
| |
| if (cond & (G_IO_HUP | G_IO_NVAL)) { |
| connman_error("install event 0x%x", cond); |
| return FALSE; |
| } |
| |
| __sync_synchronize(); |
| if (install_count != 0) { |
| status = g_io_channel_seek_position(channel, 0, G_SEEK_SET, NULL); |
| if (status != G_IO_STATUS_NORMAL) { |
| g_io_channel_shutdown(channel, TRUE, NULL); |
| g_io_channel_unref(channel); |
| return FALSE; |
| } |
| |
| /* Read the install value */ |
| status = g_io_channel_read_chars(channel, (gchar *) buf, |
| 8, &len, NULL); |
| if (status != G_IO_STATUS_NORMAL) { |
| g_io_channel_shutdown(channel, TRUE, NULL); |
| g_io_channel_unref(channel); |
| return FALSE; |
| } |
| |
| install_state = atoi(buf); |
| DBG("install event while installing %d %c", install_state, buf[0]); |
| |
| return TRUE; |
| } else { |
| install_count = 1; |
| __sync_synchronize(); |
| } |
| |
| status = g_io_channel_seek_position(channel, 0, G_SEEK_SET, NULL); |
| if (status != G_IO_STATUS_NORMAL) { |
| g_io_channel_shutdown(channel, TRUE, NULL); |
| g_io_channel_unref(channel); |
| return FALSE; |
| } |
| |
| /* Read the install value */ |
| status = g_io_channel_read_chars(channel, (gchar *) buf, 8, &len, NULL); |
| if (status != G_IO_STATUS_NORMAL) { |
| g_io_channel_shutdown(channel, TRUE, NULL); |
| g_io_channel_unref(channel); |
| return FALSE; |
| } |
| |
| install_state = atoi(buf); |
| |
| DBG("install state %d", install_state); |
| |
| install = !!install_state; |
| |
| if (install_ldisc(channel, install) < 0) { |
| connman_error("ldisc installation failed"); |
| install_count = 0; |
| __sync_synchronize(); |
| return TRUE; |
| } |
| |
| return TRUE; |
| } |
| |
| |
| static int tist_init(void) |
| { |
| GIOStatus status = G_IO_STATUS_NORMAL; |
| GIOFlags flags; |
| unsigned int install_state; |
| char buf[8]; |
| int fd, err; |
| gsize len; |
| |
| err = read_uart_name(uart_dev_name, sizeof(uart_dev_name)); |
| if (err < 0) { |
| connman_error("Could not read the UART name"); |
| return err; |
| } |
| |
| err = read_baud_rate(&baud_rate); |
| if (err < 0) { |
| connman_error("Could not read the baud rate"); |
| return err; |
| } |
| |
| fd = open(TIST_SYSFS_INSTALL, O_RDONLY | O_CLOEXEC); |
| if (fd < 0) { |
| connman_error("Failed to open TI ST sysfs install file"); |
| return -EIO; |
| } |
| |
| install_channel = g_io_channel_unix_new(fd); |
| g_io_channel_set_close_on_unref(install_channel, TRUE); |
| |
| g_io_channel_set_encoding(install_channel, NULL, NULL); |
| g_io_channel_set_buffered(install_channel, FALSE); |
| |
| flags = g_io_channel_get_flags(install_channel); |
| flags |= G_IO_FLAG_NONBLOCK; |
| g_io_channel_set_flags(install_channel, flags, NULL); |
| |
| status = g_io_channel_read_chars(install_channel, (gchar *) buf, 8, |
| &len, NULL); |
| if (status != G_IO_STATUS_NORMAL) { |
| g_io_channel_shutdown(install_channel, TRUE, NULL); |
| g_io_channel_unref(install_channel); |
| return status; |
| } |
| |
| status = g_io_channel_seek_position(install_channel, 0, G_SEEK_SET, NULL); |
| if (status != G_IO_STATUS_NORMAL) { |
| connman_error("Initial seek failed"); |
| g_io_channel_shutdown(install_channel, TRUE, NULL); |
| g_io_channel_unref(install_channel); |
| return -EIO; |
| } |
| |
| install_state = atoi(buf); |
| |
| DBG("Initial state %d", install_state); |
| |
| install_watch = g_io_add_watch_full(install_channel, G_PRIORITY_HIGH, |
| G_IO_PRI | G_IO_ERR, |
| install_event, NULL, NULL); |
| |
| if (install_state) { |
| install_count = 1; |
| __sync_synchronize(); |
| |
| err = install_ldisc(install_channel, true); |
| if (err < 0) { |
| connman_error("ldisc installtion failed"); |
| return err; |
| } |
| } |
| |
| return 0; |
| } |
| |
| |
| static void tist_exit(void) |
| { |
| |
| if (install_watch > 0) |
| g_source_remove(install_watch); |
| |
| DBG("uart_channel %p", uart_channel); |
| |
| g_io_channel_shutdown(install_channel, TRUE, NULL); |
| g_io_channel_unref(install_channel); |
| |
| if (uart_channel) { |
| g_io_channel_shutdown(uart_channel, TRUE, NULL); |
| g_io_channel_unref(uart_channel); |
| uart_channel = NULL; |
| } |
| |
| } |
| |
| CONNMAN_PLUGIN_DEFINE(tist, "TI shared transport support", VERSION, |
| CONNMAN_PLUGIN_PRIORITY_DEFAULT, tist_init, tist_exit) |