| /* |
| * Name: uamp_linux.c |
| * |
| * Description: Universal AMP API |
| * |
| * Copyright (C) 1999-2011, Broadcom Corporation |
| * |
| * Unless you and Broadcom execute a separate written software license |
| * agreement governing use of this software, this software is licensed to you |
| * under the terms of the GNU General Public License version 2 (the "GPL"), |
| * available at http://www.broadcom.com/licenses/GPLv2.php, with the |
| * following added to such license: |
| * |
| * As a special exception, the copyright holders of this software give you |
| * permission to link this software with independent modules, and to copy and |
| * distribute the resulting executable under terms of your choice, provided that |
| * you also meet, for each linked independent module, the terms and conditions of |
| * the license of that module. An independent module is a module which is not |
| * derived from this software. The special exception does not apply to any |
| * modifications of the software. |
| * |
| * Notwithstanding the above, under no circumstances may you combine this |
| * software in any way with any other Broadcom software provided under a license |
| * other than the GPL, without Broadcom's express prior written consent. |
| * |
| * $Id: uamp_linux.c,v 1.2.2.1 2011-02-05 00:16:14 Exp $ |
| * |
| */ |
| |
| /* ---- Include Files ---------------------------------------------------- */ |
| |
| #include "typedefs.h" |
| #include "bcmutils.h" |
| #include "bcmendian.h" |
| #include "uamp_api.h" |
| #include "wlioctl.h" |
| #include "dhdioctl.h" |
| #include "proto/bt_amp_hci.h" |
| #include "proto/bcmevent.h" |
| #include "proto/802.11_bta.h" |
| |
| #include <ctype.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <assert.h> |
| |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <arpa/inet.h> |
| #include <sys/ioctl.h> |
| #include <net/if.h> |
| #include <linux/if_packet.h> |
| #include <pthread.h> |
| |
| #include <linux/if_ether.h> |
| #include <mqueue.h> |
| |
| #include <linux/sockios.h> |
| #include <linux/ethtool.h> |
| |
| |
| /* ---- Public Variables ------------------------------------------------- */ |
| /* ---- Private Constants and Types -------------------------------------- */ |
| |
| #define UAMP_DEBUG 1 |
| |
| #define DEV_TYPE_LEN 3 /* length for devtype 'wl'/'et' */ |
| |
| #define UAMP_EVT_Q_STR "/uamp_evt_q" |
| #define UAMP_PKT_RX_Q_STR "/uamp_pkt_rx_q" |
| |
| #if UAMP_DEBUG |
| #define UAMP_PRINT(a) printf a |
| #define UAMP_TRACE(a) printf a |
| #define UAMP_ERROR(a) printf a |
| #else |
| #define UAMP_PRINT(a) printf a |
| #define UAMP_TRACE(a) |
| #define UAMP_ERROR(a) printf a |
| #endif |
| |
| #if ((BRCM_BLUETOOTH_HOST == 1) && (UAMP_IS_GKI_AWARE == 1)) |
| #include "gki.h" |
| #define UAMP_ALLOC(a) GKI_getbuf(a+sizeof(BT_HDR)) |
| #define UAMP_FREE(a) GKI_freebuf(a) |
| #else |
| #define UAMP_ALLOC(a) malloc(a) |
| #define UAMP_FREE(a) free(a) |
| #endif /* BRCM_BLUETOOTH_HOST && UAMP_IS_GKI_AWARE */ |
| |
| #define GET_UAMP_FROM_ID(id) (((id) == 0) ? &g_uamp_mgr.uamp : NULL) |
| |
| #define MAX_IOVAR_LEN 2096 |
| |
| |
| /* State associated with a single universal AMP. */ |
| typedef struct UAMP_STATE |
| { |
| /* Unique universal AMP identifier. */ |
| tUAMP_ID id; |
| |
| /* Event/data queues. */ |
| mqd_t evt_q; |
| mqd_t pkt_rx_q; |
| |
| /* Event file descriptors. */ |
| int evt_fd; |
| int evt_fd_pipe[2]; |
| |
| |
| /* Packet rx descriptors. */ |
| int pkt_rx_fd; |
| int pkt_rx_fd_pipe[2]; |
| |
| /* Storage buffers for recieved events and packets. */ |
| uint32 event_data[WLC_IOCTL_SMLEN/4]; |
| uint32 pkt_data[MAX_IOVAR_LEN/4]; |
| |
| } UAMP_STATE; |
| |
| |
| /* State associated with collection of univerisal AMPs. */ |
| typedef struct UAMP_MGR |
| { |
| /* Event/data callback. */ |
| tUAMP_CBACK callback; |
| |
| /* WLAN interface. */ |
| struct ifreq ifr; |
| |
| /* UAMP state. Only support a single AMP currently. */ |
| UAMP_STATE uamp; |
| |
| } UAMP_MGR; |
| |
| |
| /* ---- Private Variables ------------------------------------------------ */ |
| |
| static UAMP_MGR g_uamp_mgr; |
| |
| |
| /* ---- Private Function Prototypes -------------------------------------- */ |
| |
| static void usage(void); |
| static int uamp_accept_test(void); |
| static int uamp_create_test(void); |
| static UINT16 uamp_write_cmd(uint16 opcode, uint8 *params, uint8 len, |
| amp_hci_cmd_t *cmd, unsigned int max_len); |
| static UINT16 uamp_write_data(uint16 handle, uint8 *data, uint8 len, |
| amp_hci_ACL_data_t *pkt, unsigned int max_len); |
| |
| static int ioctl_get(int cmd, void *buf, int len); |
| static int ioctl_set(int cmd, void *buf, int len); |
| static int iovar_set(const char *iovar, void *param, int paramlen); |
| static int iovar_setbuf(const char *iovar, void *param, int paramlen, void *bufptr, |
| int buflen); |
| static int iovar_mkbuf(const char *name, char *data, uint datalen, char *iovar_buf, |
| uint buflen, int *perr); |
| static int wl_ioctl(int cmd, void *buf, int len, bool set); |
| static void wl_get_interface_name(struct ifreq *ifr); |
| static int wl_get_dev_type(char *name, void *buf, int len); |
| static void syserr(char *s); |
| |
| static int init_event_rx(UAMP_STATE *uamp); |
| static void deinit_event_rx(UAMP_STATE *uamp); |
| static void* event_thread(void *param); |
| static void handle_event(UAMP_STATE *uamp); |
| |
| static int init_pkt_rx(UAMP_STATE *uamp); |
| static void deinit_pkt_rx(UAMP_STATE *uamp); |
| static void* packet_rx_thread(void *param); |
| static void handle_rx_pkt(UAMP_STATE *uamp); |
| |
| |
| #if (BRCM_BLUETOOTH_HOST == 1) |
| #if (UAMP_IS_GKI_AWARE == 1) |
| void wl_event_gki_callback(wl_event_msg_t* event, void* event_data); |
| int wl_btamp_rx_gki_pkt_callback(wl_drv_netif_pkt pkt, unsigned int len); |
| #endif /* UAMP_IS_GKI_AWARE */ |
| static void *uamp_get_acl_buf(unsigned int len); |
| void *hcisu_amp_get_acl_buf(int len); /* Get GKI buffer from ACL pool */ |
| void hcisu_handle_amp_data_buf(void *pkt, unsigned int len); /* Send GKI buffer to BTU task */ |
| void hcisu_handle_amp_evt_buf(void* evt, unsigned int len); |
| int wl_is_drv_init_done(void); |
| #endif /* BRCM_BLUETOOTH_HOST */ |
| |
| /* ---- Functions -------------------------------------------------------- */ |
| |
| /* ------------------------------------------------------------------------- */ |
| BT_API BOOLEAN UAMP_Init(tUAMP_CBACK p_cback) |
| { |
| memset(&g_uamp_mgr, 0, sizeof(g_uamp_mgr)); |
| g_uamp_mgr.callback = p_cback; |
| |
| wl_get_interface_name(&g_uamp_mgr.ifr); |
| |
| return (TRUE); |
| } |
| |
| |
| /* ------------------------------------------------------------------------- */ |
| BT_API BOOLEAN UAMP_Open(tUAMP_ID amp_id) |
| { |
| UAMP_STATE *uamp = GET_UAMP_FROM_ID(amp_id); |
| |
| #if (BRCM_BLUETOOTH_HOST == 1) |
| if (!wl_is_drv_init_done()) { |
| UAMP_ERROR(("%s: WLAN driver is not initialized! \n", __FUNCTION__)); |
| return FALSE; |
| } |
| #endif /* BRCM_BLUETOOTH_HOST */ |
| |
| /* Setup event receive. */ |
| if ((init_event_rx(uamp)) < 0) { |
| return (FALSE); |
| } |
| |
| /* Setup packet receive. */ |
| if ((init_pkt_rx(uamp)) < 0) { |
| return (FALSE); |
| } |
| |
| return (TRUE); |
| } |
| |
| |
| /* ------------------------------------------------------------------------- */ |
| BT_API void UAMP_Close(tUAMP_ID amp_id) |
| { |
| UAMP_STATE *uamp = GET_UAMP_FROM_ID(amp_id); |
| |
| #if (BRCM_BLUETOOTH_HOST == 1) |
| if (!wl_is_drv_init_done()) { |
| UAMP_ERROR(("%s: WLAN driver is not initialized! \n", __FUNCTION__)); |
| return; |
| } |
| #endif /* BRCM_BLUETOOTH_HOST */ |
| |
| /* Cleanup packet and event receive. */ |
| deinit_pkt_rx(uamp); |
| deinit_event_rx(uamp); |
| } |
| |
| /* ------------------------------------------------------------------------- */ |
| BT_API UINT16 UAMP_Write(tUAMP_ID amp_id, UINT8 *p_buf, UINT16 num_bytes, tUAMP_CH channel) |
| { |
| int ret = -1; |
| UINT16 num_bytes_written = num_bytes; |
| |
| UNUSED_PARAMETER(amp_id); |
| |
| #if (BRCM_BLUETOOTH_HOST == 1) |
| if (!wl_is_drv_init_done()) { |
| UAMP_ERROR(("%s: WLAN driver is not initialized! \n", __FUNCTION__)); |
| return (0); |
| } |
| #endif /* BRCM_BLUETOOTH_HOST */ |
| |
| if (channel == UAMP_CH_HCI_CMD) { |
| ret = iovar_set("HCI_cmd", p_buf, num_bytes); |
| } |
| else if (channel == UAMP_CH_HCI_DATA) { |
| ret = iovar_set("HCI_ACL_data", p_buf, num_bytes); |
| } |
| |
| if (ret != 0) { |
| num_bytes_written = 0; |
| UAMP_ERROR(("UAMP_Write error: %i ( 0=success )\n", ret)); |
| } |
| |
| return (num_bytes_written); |
| } |
| |
| |
| /* ------------------------------------------------------------------------- */ |
| BT_API UINT16 UAMP_Read(tUAMP_ID amp_id, UINT8 *p_buf, UINT16 buf_size, tUAMP_CH channel) |
| { |
| UAMP_STATE *uamp = GET_UAMP_FROM_ID(amp_id); |
| mqd_t num_bytes; |
| unsigned int msg_prio; |
| |
| #if (BRCM_BLUETOOTH_HOST == 1) |
| if (!wl_is_drv_init_done()) { |
| UAMP_ERROR(("%s: WLAN driver is not initialized! \n", __FUNCTION__)); |
| return (0); |
| } |
| #endif /* BRCM_BLUETOOTH_HOST */ |
| |
| |
| if (channel == UAMP_CH_HCI_EVT) { |
| /* Dequeue event. */ |
| num_bytes = mq_receive(uamp->evt_q, (char *)p_buf, buf_size, &msg_prio); |
| if (num_bytes == -1) { |
| UAMP_ERROR(("%s: Event queue receive error!\n", __FUNCTION__)); |
| return (0); |
| } |
| |
| return (num_bytes); |
| } |
| else if (channel == UAMP_CH_HCI_DATA) { |
| /* Dequeue rx packet. */ |
| num_bytes = mq_receive(uamp->pkt_rx_q, (char *)p_buf, buf_size, &msg_prio); |
| if (num_bytes == -1) { |
| UAMP_ERROR(("%s: Pkt queue receive error!\n", __FUNCTION__)); |
| return (0); |
| } |
| |
| return (num_bytes); |
| } |
| |
| return (0); |
| } |
| |
| |
| /* |
| * Get IOCTL given the parameter buffer. |
| */ |
| static int |
| ioctl_get(int cmd, void *buf, int len) |
| { |
| return wl_ioctl(cmd, buf, len, FALSE); |
| } |
| |
| |
| /* |
| * Set IOCTL given the parameter buffer. |
| */ |
| static int |
| ioctl_set(int cmd, void *buf, int len) |
| { |
| return wl_ioctl(cmd, buf, len, TRUE); |
| } |
| |
| |
| /* |
| * Set named iovar given the parameter buffer. |
| */ |
| static int |
| iovar_set(const char *iovar, void *param, int paramlen) |
| { |
| static char smbuf[MAX_IOVAR_LEN]; |
| |
| memset(smbuf, 0, sizeof(smbuf)); |
| |
| return iovar_setbuf(iovar, param, paramlen, smbuf, sizeof(smbuf)); |
| } |
| |
| /* |
| * Set named iovar providing both parameter and i/o buffers. |
| */ |
| static int |
| iovar_setbuf(const char *iovar, |
| void *param, int paramlen, void *bufptr, int buflen) |
| { |
| int err; |
| int iolen; |
| |
| iolen = iovar_mkbuf(iovar, param, paramlen, bufptr, buflen, &err); |
| if (err) |
| return err; |
| |
| return ioctl_set(DHD_SET_VAR, bufptr, iolen); |
| } |
| |
| |
| /* |
| * Format an iovar buffer. |
| */ |
| static int |
| iovar_mkbuf(const char *name, char *data, uint datalen, char *iovar_buf, uint buflen, int *perr) |
| { |
| int iovar_len; |
| |
| iovar_len = strlen(name) + 1; |
| |
| /* check for overflow */ |
| if ((iovar_len + datalen) > buflen) { |
| *perr = -1; |
| return 0; |
| } |
| |
| /* copy data to the buffer past the end of the iovar name string */ |
| if (datalen > 0) |
| memmove(&iovar_buf[iovar_len], data, datalen); |
| |
| /* copy the name to the beginning of the buffer */ |
| strcpy(iovar_buf, name); |
| |
| *perr = 0; |
| return (iovar_len + datalen); |
| } |
| |
| |
| /* |
| * Send IOCTL to WLAN driver. |
| */ |
| static int |
| wl_ioctl(int cmd, void *buf, int len, bool set) |
| { |
| struct ifreq ifr; |
| dhd_ioctl_t ioc; |
| int ret = 0; |
| int s; |
| |
| memset(&ifr, 0, sizeof(ifr)); |
| strncpy(ifr.ifr_name, g_uamp_mgr.ifr.ifr_name, sizeof(ifr.ifr_name)); |
| |
| /* open socket to kernel */ |
| if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { |
| ret = -1; |
| return ret; |
| } |
| |
| /* do it */ |
| ioc.cmd = cmd; |
| ioc.buf = buf; |
| ioc.len = len; |
| ioc.set = set; |
| ioc.driver = DHD_IOCTL_MAGIC; |
| ifr.ifr_data = (caddr_t) &ioc; |
| if ((ret = ioctl(s, SIOCDEVPRIVATE, &ifr)) < 0) { |
| ret = -1; |
| } |
| |
| /* cleanup */ |
| close(s); |
| return ret; |
| } |
| |
| |
| static int |
| wl_get_dev_type(char *name, void *buf, int len) |
| { |
| int s; |
| int ret; |
| struct ifreq ifr; |
| struct ethtool_drvinfo info; |
| |
| /* open socket to kernel */ |
| if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) |
| syserr("socket"); |
| |
| /* get device type */ |
| memset(&info, 0, sizeof(info)); |
| info.cmd = ETHTOOL_GDRVINFO; |
| ifr.ifr_data = (caddr_t)&info; |
| strncpy(ifr.ifr_name, name, IFNAMSIZ); |
| if ((ret = ioctl(s, SIOCETHTOOL, &ifr)) < 0) { |
| |
| /* print a good diagnostic if not superuser */ |
| if (errno == EPERM) |
| syserr("wl_get_dev_type"); |
| |
| *(char *)buf = '\0'; |
| } else { |
| strncpy(buf, info.driver, len); |
| } |
| |
| close(s); |
| return ret; |
| } |
| |
| |
| static void |
| wl_get_interface_name(struct ifreq *ifr) |
| { |
| char proc_net_dev[] = "/proc/net/dev"; |
| FILE *fp; |
| char buf[1000], *c, *name; |
| char dev_type[DEV_TYPE_LEN]; |
| int ret = -1; |
| |
| ifr->ifr_name[0] = '\0'; |
| |
| if (!(fp = fopen(proc_net_dev, "r"))) |
| return; |
| |
| /* eat first two lines */ |
| if (!fgets(buf, sizeof(buf), fp) || |
| !fgets(buf, sizeof(buf), fp)) { |
| fclose(fp); |
| return; |
| } |
| |
| while (fgets(buf, sizeof(buf), fp)) { |
| c = buf; |
| while (isspace(*c)) |
| c++; |
| if (!(name = strsep(&c, ":"))) |
| continue; |
| strncpy(ifr->ifr_name, name, IFNAMSIZ); |
| if (wl_get_dev_type(name, dev_type, DEV_TYPE_LEN) >= 0 && |
| (!strncmp(dev_type, "wl", 2) || !strncmp(dev_type, "dhd", 3))) |
| { |
| ret = 0; |
| break; |
| } |
| ifr->ifr_name[0] = '\0'; |
| } |
| |
| fclose(fp); |
| } |
| |
| |
| static void |
| syserr(char *s) |
| { |
| fprintf(stderr, "uamp_linux:"); |
| perror(s); |
| exit(errno); |
| } |
| |
| |
| #if (BRCM_BLUETOOTH_HOST == 1) |
| static void *uamp_get_acl_buf(unsigned int len) |
| { |
| return (hcisu_amp_get_acl_buf(len)); |
| } |
| #endif /* BRCM_BLUETOOTH_HOST */ |
| |
| |
| /* |
| * Setup packet receive. |
| */ |
| static int |
| init_pkt_rx(UAMP_STATE *uamp) |
| { |
| struct ifreq ifr; |
| int fd = -1; |
| struct sockaddr_ll local; |
| int err; |
| int fd_pipe[2] = {-1, -1}; |
| pthread_t h; |
| |
| |
| memset(&ifr, 0, sizeof(ifr)); |
| wl_get_interface_name(&ifr); |
| |
| /* Create and bind socket to receive packets. */ |
| fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_802_2)); |
| if (fd < 0) { |
| UAMP_ERROR(("%s: Cannot open socket", __FUNCTION__)); |
| return (-1); |
| } |
| |
| err = ioctl(fd, SIOCGIFINDEX, &ifr); |
| if (err < 0) { |
| UAMP_ERROR(("%s: Cannot get index %d\n", __FUNCTION__, err)); |
| close(fd); |
| return (-1); |
| } |
| |
| memset(&local, 0, sizeof(local)); |
| local.sll_family = PF_PACKET; |
| local.sll_protocol = htons(ETH_P_802_2); |
| local.sll_ifindex = ifr.ifr_ifindex; |
| |
| if (bind(fd, (struct sockaddr*)&local, sizeof(local)) < 0) { |
| UAMP_ERROR(("%s: Cannot bind socket", __FUNCTION__)); |
| close(fd); |
| return (-1); |
| } |
| |
| |
| /* Create pipe used to terminate receive packet thread. */ |
| if (pipe(fd_pipe) != 0) { |
| UAMP_ERROR(("%s: pipe failed\n", __FUNCTION__)); |
| goto cleanup; |
| } |
| |
| /* Save in instance memory. */ |
| uamp->pkt_rx_fd = fd; |
| uamp->pkt_rx_fd_pipe[0] = fd_pipe[0]; |
| uamp->pkt_rx_fd_pipe[1] = fd_pipe[1]; |
| |
| |
| /* Create message queue for received packets. */ |
| |
| uamp->pkt_rx_q = mq_open(UAMP_PKT_RX_Q_STR, O_RDWR | O_CREAT, 0666, NULL); |
| |
| |
| |
| /* Spawn packet handling thread. */ |
| pthread_create(&h, NULL, packet_rx_thread, uamp); |
| |
| return (fd); |
| |
| cleanup: |
| if (-1 != fd) close(fd); |
| if (-1 != fd_pipe[0]) close(fd_pipe[0]); |
| if (-1 != fd_pipe[1]) close(fd_pipe[1]); |
| return (-1); |
| } |
| |
| |
| /* |
| * Cleanup packet receive. |
| */ |
| static void |
| deinit_pkt_rx(UAMP_STATE *uamp) |
| { |
| /* Cleanup the message queue. */ |
| mq_close(uamp->pkt_rx_q); |
| mq_unlink(UAMP_PKT_RX_Q_STR); |
| |
| /* Kill the receive thread. */ |
| write(uamp->pkt_rx_fd_pipe[1], NULL, 0); |
| close(uamp->pkt_rx_fd_pipe[1]); |
| } |
| |
| |
| /* |
| * Packet receive thread. |
| */ |
| static void* |
| packet_rx_thread(void *param) |
| { |
| UAMP_STATE *uamp = (UAMP_STATE *) param; |
| |
| UAMP_PRINT(("Start packet rx wait loop\n")); |
| |
| while (1) { |
| fd_set rfds; /* fds for select */ |
| int last_fd; |
| int ret; |
| |
| FD_ZERO(&rfds); |
| FD_SET(uamp->pkt_rx_fd_pipe[0], &rfds); |
| FD_SET(uamp->pkt_rx_fd, &rfds); |
| last_fd = MAX(uamp->pkt_rx_fd_pipe[0], uamp->pkt_rx_fd); |
| |
| /* Wait on stop pipe or rx packet socket */ |
| ret = select(last_fd+1, &rfds, NULL, NULL, NULL); |
| |
| /* Error processing */ |
| if (0 > ret) { |
| UAMP_ERROR(("%s: Unhandled signal on pkt rx socket\n", __FUNCTION__)); |
| break; |
| } |
| |
| /* Stop processing */ |
| if (FD_ISSET(uamp->pkt_rx_fd_pipe[0], &rfds)) { |
| UAMP_PRINT(("%s: stop rcvd on dispatcher pipe\n", __FUNCTION__)); |
| break; |
| } |
| |
| /* Packet processing */ |
| if (FD_ISSET(uamp->pkt_rx_fd, &rfds)) { |
| handle_rx_pkt(uamp); |
| } |
| |
| } /* end-while(1) */ |
| |
| UAMP_PRINT(("%s: End packet rx wait loop\n", __FUNCTION__)); |
| |
| close(uamp->pkt_rx_fd); |
| close(uamp->pkt_rx_fd_pipe[0]); |
| |
| UAMP_TRACE(("Exit %s\n", __FUNCTION__)); |
| return (NULL); |
| } |
| |
| /* |
| * Process received packet. |
| */ |
| static void |
| handle_rx_pkt(UAMP_STATE *uamp) |
| { |
| int bytes; |
| struct dot11_llc_snap_header *lsh; |
| amp_hci_ACL_data_t *acl_data; |
| |
| /* Read packet. */ |
| bytes = recv(uamp->pkt_rx_fd, uamp->pkt_data, sizeof(uamp->pkt_data), MSG_DONTWAIT); |
| |
| /* Error handling. */ |
| if (bytes < 0) { |
| if (errno != EINTR && errno != EAGAIN) { |
| UAMP_ERROR(("%s: Error reading packet rx socket: %s\n", |
| __FUNCTION__, strerror(errno))); |
| return; |
| } |
| } |
| |
| if (bytes == 0) { |
| UAMP_ERROR(("%s: EOF on packet rx socket", __FUNCTION__)); |
| return; |
| } |
| |
| |
| /* Verify that this is an HCI data packet. */ |
| lsh = (struct dot11_llc_snap_header *)uamp->pkt_data; |
| if (bcmp(lsh, BT_SIG_SNAP_MPROT, DOT11_LLC_SNAP_HDR_LEN - 2) != 0 || |
| ntoh16(lsh->type) != BTA_PROT_L2CAP) { |
| /* Not HCI data. */ |
| return; |
| } |
| |
| |
| UAMP_TRACE(("%s: received packet!\n", __FUNCTION__)); |
| |
| acl_data = (amp_hci_ACL_data_t *) &lsh[1]; |
| bytes -= DOT11_LLC_SNAP_HDR_LEN; |
| |
| #if (BRCM_BLUETOOTH_HOST == 1) |
| hcisu_handle_amp_data_buf(acl_data, bytes); |
| #else |
| { |
| tUAMP_EVT_DATA uamp_evt_data; |
| #if (UAMP_DEBUG == 1) |
| /* Debug - dump rx packet data. */ |
| { |
| int i; |
| uint8 *data = acl_data->data; |
| UAMP_TRACE(("data(%d): ", bytes)); |
| for (i = 0; i < bytes; i++) { |
| UAMP_TRACE(("0x%x ", data[i])); |
| } |
| UAMP_TRACE(("\n")); |
| } |
| #endif /* UAMP_DEBUG */ |
| |
| /* Post packet to queue. Stack will de-queue it with call to UAMP_Read(). */ |
| if (mq_send(uamp->pkt_rx_q, (const char *)acl_data, bytes, 0) != 0) { |
| /* Unable to queue packet */ |
| UAMP_ERROR(("%s: Unable to queue rx packet data!\n", __FUNCTION__)); |
| return; |
| } |
| |
| |
| /* Inform application stack of received packet. */ |
| memset(&uamp_evt_data, 0, sizeof(uamp_evt_data)); |
| uamp_evt_data.channel = UAMP_CH_HCI_DATA; |
| g_uamp_mgr.callback(0, UAMP_EVT_RX_READY, &uamp_evt_data); |
| } |
| #endif /* BRCM_BLUETOOTH_HOST */ |
| } |
| |
| |
| /* |
| * Setup event receive. |
| */ |
| static int |
| init_event_rx(UAMP_STATE *uamp) |
| { |
| struct ifreq ifr; |
| int fd = -1; |
| struct sockaddr_ll local; |
| int err; |
| int fd_pipe[2] = {-1, -1}; |
| pthread_t h; |
| |
| memset(&ifr, 0, sizeof(ifr)); |
| wl_get_interface_name(&ifr); |
| UAMP_PRINT(("ifr_name (%s)\n", ifr.ifr_name)); |
| |
| /* Create and bind socket to receive packets. */ |
| fd = socket(PF_PACKET, SOCK_RAW, htons(ETHER_TYPE_BRCM)); |
| if (fd < 0) { |
| UAMP_ERROR(("%s: Cannot open socket", __FUNCTION__)); |
| return (-1); |
| } |
| |
| err = ioctl(fd, SIOCGIFINDEX, &ifr); |
| if (err < 0) { |
| UAMP_ERROR(("%s: Cannot get index %d\n", __FUNCTION__, err)); |
| close(fd); |
| return (-1); |
| } |
| |
| memset(&local, 0, sizeof(local)); |
| local.sll_family = AF_PACKET; |
| local.sll_protocol = htons(ETHER_TYPE_BRCM); |
| local.sll_ifindex = ifr.ifr_ifindex; |
| |
| if (bind(fd, (struct sockaddr*)&local, sizeof(local)) < 0) { |
| UAMP_ERROR(("%s: Cannot bind event socket", __FUNCTION__)); |
| close(fd); |
| return (-1); |
| } |
| |
| |
| /* Create pipe used to terminate receive packet thread. */ |
| if (pipe(fd_pipe) != 0) { |
| UAMP_ERROR(("%s: pipe failed\n", __FUNCTION__)); |
| goto cleanup; |
| } |
| |
| /* Save in instance memory. */ |
| uamp->evt_fd = fd; |
| uamp->evt_fd_pipe[0] = fd_pipe[0]; |
| uamp->evt_fd_pipe[1] = fd_pipe[1]; |
| |
| |
| /* Create message queue for received events. */ |
| |
| uamp->evt_q = mq_open(UAMP_EVT_Q_STR, O_RDWR | O_CREAT, 0666, NULL); |
| UAMP_PRINT(("evt_q(0x%x)\n", (int)uamp->evt_q)); |
| |
| |
| |
| /* Spawn event handling thread. */ |
| pthread_create(&h, NULL, event_thread, uamp); |
| |
| return (fd); |
| |
| cleanup: |
| if (-1 != fd) close(fd); |
| if (-1 != fd_pipe[0]) close(fd_pipe[0]); |
| if (-1 != fd_pipe[1]) close(fd_pipe[1]); |
| return (-1); |
| } |
| |
| |
| /* |
| * Cleanup event receive. |
| */ |
| static void |
| deinit_event_rx(UAMP_STATE *uamp) |
| { |
| /* Cleanup the message queue. */ |
| mq_close(uamp->evt_q); |
| mq_unlink(UAMP_EVT_Q_STR); |
| |
| /* Kill the receive thread. */ |
| write(uamp->evt_fd_pipe[1], NULL, 0); |
| close(uamp->evt_fd_pipe[1]); |
| } |
| |
| /* |
| * Event receive thread. |
| */ |
| static void* |
| event_thread(void *param) |
| { |
| UAMP_STATE *uamp = (UAMP_STATE *) param; |
| |
| UAMP_PRINT(("Start event wait loop\n")); |
| |
| while (1) { |
| fd_set rfds; /* fds for select */ |
| int last_fd; |
| int ret; |
| |
| FD_ZERO(&rfds); |
| FD_SET(uamp->evt_fd_pipe[0], &rfds); |
| FD_SET(uamp->evt_fd, &rfds); |
| last_fd = MAX(uamp->evt_fd_pipe[0], uamp->evt_fd); |
| |
| /* Wait on stop pipe or brcm event socket. */ |
| ret = select(last_fd+1, &rfds, NULL, NULL, NULL); |
| |
| /* Error processing */ |
| if (0 > ret) { |
| UAMP_ERROR(("%s: Unhandled signal on brcm event socket\n", __FUNCTION__)); |
| break; |
| } |
| |
| /* Stop processing. */ |
| if (FD_ISSET(uamp->evt_fd_pipe[0], &rfds)) { |
| UAMP_PRINT(("%s: stop rcvd on dispatcher pipe\n", __FUNCTION__)); |
| break; |
| } |
| |
| /* Event processing. */ |
| if (FD_ISSET(uamp->evt_fd, &rfds)) { |
| handle_event(uamp); |
| } |
| |
| } /* end-while(1) */ |
| |
| UAMP_PRINT(("%s: End event wait loop\n", __FUNCTION__)); |
| |
| close(uamp->evt_fd); |
| close(uamp->evt_fd_pipe[0]); |
| |
| UAMP_TRACE(("Exit %s\n", __FUNCTION__)); |
| return (NULL); |
| } |
| |
| |
| /* |
| * Process received event. |
| */ |
| static void |
| handle_event(UAMP_STATE *uamp) |
| { |
| int bytes; |
| bcm_event_t *bcm_event; |
| wl_event_msg_t *wl_event; |
| uint8 *wl_evt_data; |
| uint32 datalen; |
| |
| /* Read event. */ |
| bytes = recv(uamp->evt_fd, uamp->event_data, sizeof(uamp->event_data), MSG_DONTWAIT); |
| |
| /* Error handling. */ |
| if (bytes < 0) { |
| if (errno != EINTR && errno != EAGAIN) { |
| UAMP_ERROR(("%s: Error reading event socket: %s\n", |
| __FUNCTION__, strerror(errno))); |
| return; |
| } |
| } |
| |
| if (bytes == 0) { |
| UAMP_ERROR(("%s: EOF on event socket", __FUNCTION__)); |
| return; |
| } |
| |
| |
| /* We're only interested in HCI events. */ |
| bcm_event = (bcm_event_t *)uamp->event_data; |
| if (ntoh32(bcm_event->event.event_type) != WLC_E_BTA_HCI_EVENT) { |
| return; |
| } |
| |
| UAMP_TRACE(("%s: received event!\n", __FUNCTION__)); |
| |
| |
| wl_event = &bcm_event->event; |
| wl_evt_data = (uint8 *)&wl_event[1]; |
| datalen = ntoh32(wl_event->datalen); |
| |
| #if (BRCM_BLUETOOTH_HOST == 1) |
| hcisu_handle_amp_evt_buf(wl_evt_data, datalen); |
| #else |
| { |
| tUAMP_EVT_DATA uamp_evt_data; |
| |
| #if (UAMP_DEBUG == 1) |
| /* Debug - dump event data. */ |
| { |
| unsigned int i; |
| UAMP_TRACE(("data(%d): ", datalen)); |
| for (i = 0; i < datalen; i++) |
| { |
| UAMP_TRACE(("0x%x ", wl_evt_data[i])); |
| } |
| UAMP_TRACE(("\n")); |
| } |
| #endif /* UAMP_DEBUG */ |
| |
| /* Post event to queue. Stack will de-queue it with call to UAMP_Read(). */ |
| if (mq_send(uamp->evt_q, (const char *)wl_evt_data, datalen, 0) != 0) { |
| /* Unable to queue packet */ |
| UAMP_ERROR(("%s: Unable to queue event packet!\n", __FUNCTION__)); |
| return; |
| } |
| |
| |
| /* Inform application stack of received event. */ |
| memset(&uamp_evt_data, 0, sizeof(uamp_evt_data)); |
| uamp_evt_data.channel = UAMP_CH_HCI_EVT; |
| g_uamp_mgr.callback(0, UAMP_EVT_RX_READY, &uamp_evt_data); |
| } |
| #endif /* BRCM_BLUETOOTH_HOST */ |
| |
| } |
| |
| |
| #define UAMP_TEST 1 |
| #if UAMP_TEST |
| int |
| main(int argc, char **argv) |
| { |
| int ret; |
| |
| printf("Hello, world!\n"); |
| |
| if (argc != 2) { |
| usage(); |
| return (-1); |
| } |
| |
| if (strcmp(argv[1], "-a") == 0) { |
| ret = uamp_accept_test(); |
| } |
| else if (strcmp(argv[1], "-c") == 0) { |
| ret = uamp_create_test(); |
| } |
| else { |
| usage(); |
| return (-1); |
| } |
| |
| |
| return (ret); |
| } |
| |
| /* |
| * Usage display. |
| */ |
| static void usage(void) |
| { |
| UAMP_PRINT(("Usage:\n")); |
| UAMP_PRINT(("\t uamp [-a | -c]\n")); |
| UAMP_PRINT(("\t\t -a: acceptor\n")); |
| UAMP_PRINT(("\t\t -c: creator\n")); |
| } |
| |
| #define WAIT_FOR_KEY(delay) \ |
| do { \ |
| usleep(1000*delay); \ |
| UAMP_PRINT(("Press key to continue\n")); \ |
| getchar(); \ |
| } \ |
| while (0); |
| |
| |
| /* |
| * Application callback for received events and packets. |
| */ |
| static void uamp_callback(tUAMP_ID amp_id, tUAMP_EVT amp_evt, tUAMP_EVT_DATA *p_amp_evt_data) |
| { |
| UINT8 buf[8192]; |
| amp_hci_ACL_data_t *data; |
| amp_hci_event_t *evt; |
| unsigned int i; |
| UINT16 num_bytes; |
| |
| UNUSED_PARAMETER(amp_evt); |
| |
| num_bytes = UAMP_Read(amp_id, buf, sizeof(buf), p_amp_evt_data->channel); |
| if (num_bytes != 0) { |
| if (p_amp_evt_data->channel == UAMP_CH_HCI_EVT) { |
| evt = (amp_hci_event_t *) buf; |
| UAMP_PRINT(("%s: evt - ecode(%d) plen(%d)\n", |
| __FUNCTION__, evt->ecode, evt->plen)); |
| |
| for (i = 0; i < evt->plen; i++) { |
| UAMP_PRINT(("0x%x ", evt->parms[i])); |
| } |
| UAMP_PRINT(("\n")); |
| } |
| else if (p_amp_evt_data->channel == UAMP_CH_HCI_DATA) { |
| data = (amp_hci_ACL_data_t *) buf; |
| UAMP_PRINT(("%s: data - dlen(%d)\n", __FUNCTION__, data->dlen)); |
| |
| for (i = 0; i < data->dlen; i++) { |
| UAMP_PRINT(("0x%x ", data->data[i])); |
| } |
| UAMP_PRINT(("\n")); |
| } |
| } |
| else { |
| UAMP_PRINT(("%s: UAMP_Read error\n", __FUNCTION__)); |
| } |
| } |
| |
| |
| /* This client is 00:90:4c:c6:02:5b. Remote is 00:90:4c:c5:06:79. */ |
| static int uamp_accept_test(void) |
| { |
| uint8 set_event_mask_page_2_data[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; |
| uint8 read_local_amp_assoc_data[] = {0, 0, 0}; |
| uint8 accept_physical_link_request_data[] = {0x11, 32, 3, 0x00, 0x01, 0x02, |
| 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, |
| 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, |
| 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}; |
| uint8 write_remote_amp_assoc_data[] = {0x11, 0x0, 0x0, 0x24, 0x00, 0x04, |
| 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x05, 0x00, 0x01, 0x0f, |
| 0x00, 0x10, 0x09, 0x01, 0x06, 0x00, 0x00, 0x90, 0x4c, 0xc5, 0x06, |
| 0x79, 0x02, 0x09, 0x00, 0x55, 0x53, 0x20, 0xc9, 0x0c, 0x00, 0x01, |
| 0x01, 0x14}; |
| uint8 accept_logical_link_data[] = {0x11, 0x01, 0x01, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0x01, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0xff}; |
| uint8 tx_data[] = {7, 6, 5, 4, 3, 2, 1, 0}; |
| uint8 disconnect_logical_link_data[] = {0}; |
| uint8 disconnect_physical_link_data[] = {0x11, 0}; |
| |
| uint32 buf[64]; |
| amp_hci_cmd_t *cmd = (amp_hci_cmd_t *)buf; |
| amp_hci_ACL_data_t *pkt = (amp_hci_ACL_data_t *)buf; |
| |
| |
| UAMP_PRINT(("UAMP acceptor test\n")); |
| |
| |
| UAMP_Init(uamp_callback); |
| UAMP_Open(0); |
| |
| |
| /* HCI_Set_Event_Mask_Page_2 */ |
| uamp_write_cmd(HCI_Set_Event_Mask_Page_2, set_event_mask_page_2_data, |
| sizeof(set_event_mask_page_2_data), cmd, sizeof(buf)); |
| |
| /* Read_Local_AMP_ASSOC */ |
| uamp_write_cmd(HCI_Read_Local_AMP_ASSOC, read_local_amp_assoc_data, |
| sizeof(read_local_amp_assoc_data), cmd, sizeof(buf)); |
| WAIT_FOR_KEY(1000); |
| |
| /* Accept_Physical_Link_Request */ |
| uamp_write_cmd(HCI_Accept_Physical_Link_Request, accept_physical_link_request_data, |
| sizeof(accept_physical_link_request_data), cmd, sizeof(buf)); |
| |
| |
| /* This is specific to info obtained from the remote client. */ |
| /* Write_Remote_AMP_ASSOC */ |
| uamp_write_cmd(HCI_Write_Remote_AMP_ASSOC, write_remote_amp_assoc_data, |
| sizeof(write_remote_amp_assoc_data), cmd, sizeof(buf)); |
| WAIT_FOR_KEY(1000); |
| |
| /* Accept_Logical_Link */ |
| uamp_write_cmd(HCI_Accept_Logical_Link, accept_logical_link_data, |
| sizeof(accept_logical_link_data), cmd, sizeof(buf)); |
| WAIT_FOR_KEY(1000); |
| |
| /* HCI_ACL_data */ |
| uamp_write_data(0 | HCI_ACL_DATA_BC_FLAGS | HCI_ACL_DATA_PB_FLAGS, tx_data, |
| sizeof(tx_data), pkt, sizeof(buf)); |
| WAIT_FOR_KEY(1000); |
| |
| /* Disconnect_Logical_Link */ |
| uamp_write_cmd(HCI_Disconnect_Logical_Link, disconnect_logical_link_data, |
| sizeof(disconnect_logical_link_data), cmd, sizeof(buf)); |
| |
| /* Disconnect_Physical_Link */ |
| uamp_write_cmd(HCI_Disconnect_Physical_Link, disconnect_physical_link_data, |
| sizeof(disconnect_physical_link_data), cmd, sizeof(buf)); |
| |
| usleep(1000*1000); |
| UAMP_Close(0); |
| usleep(1000*1000); |
| UAMP_PRINT(("UAMP acceptor test done!\n")); |
| |
| return (0); |
| } |
| |
| |
| /* This client is 00:90:4c:c5:06:79. Remote is 00:90:4c:c6:02:5b. */ |
| static int uamp_create_test(void) |
| { |
| uint8 set_event_mask_page_2_data[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; |
| uint8 create_physical_link_data[] = {0x10, 32, 3, 0x00, 0x01, 0x02, 0x03, |
| 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, |
| 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, |
| 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}; |
| uint8 write_remote_amp_assoc_data[] = {0x10, 0x0, 0x0, 0x21, 0x00, 0x04, |
| 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x05, 0x00, 0x01, 0x0f, |
| 0x00, 0x10, 0x09, 0x01, 0x06, 0x00, 0x00, 0x90, 0x4c, 0xc6, 0x02, |
| 0x5b, 0x02, 0x06, 0x00, 0x55, 0x53, 0x20, 0xc9, 0x0c, 0x00}; |
| uint8 read_local_amp_assoc_data[] = {0x10, 0, 0, 100, 0}; |
| uint8 create_logical_link_data[] = {0x10, 0x01, 0x01, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0x01, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0xff}; |
| uint8 disconnect_logical_link_data[] = {0}; |
| uint8 disconnect_physical_link_data[] = {0x10, 0}; |
| uint8 tx_data[] = {0, 1, 2, 3, 4, 5, 6, 7}; |
| |
| uint32 buf[64]; |
| amp_hci_cmd_t *cmd = (amp_hci_cmd_t *)buf; |
| amp_hci_ACL_data_t *pkt = (amp_hci_ACL_data_t *)buf; |
| |
| UAMP_PRINT(("UAMP creator test\n")); |
| |
| |
| UAMP_Init(uamp_callback); |
| |
| UAMP_Open(0); |
| |
| /* HCI_Set_Event_Mask_Page_2 */ |
| uamp_write_cmd(HCI_Set_Event_Mask_Page_2, set_event_mask_page_2_data, |
| sizeof(set_event_mask_page_2_data), cmd, sizeof(buf)); |
| |
| /* Read_Local_AMP_Info */ |
| uamp_write_cmd(HCI_Read_Local_AMP_Info, NULL, 0, cmd, sizeof(buf)); |
| WAIT_FOR_KEY(1000); |
| |
| /* Create_Physical_Link */ |
| uamp_write_cmd(HCI_Create_Physical_Link, create_physical_link_data, |
| sizeof(create_physical_link_data), cmd, sizeof(buf)); |
| |
| /* This is specific to info obtained from the remote client. */ |
| /* Write_Remote_AMP_ASSOC */ |
| uamp_write_cmd(HCI_Write_Remote_AMP_ASSOC, write_remote_amp_assoc_data, |
| sizeof(write_remote_amp_assoc_data), cmd, sizeof(buf)); |
| |
| |
| /* Spin for a bit. */ |
| usleep(1000*1000); |
| |
| |
| /* Read_Local_AMP_ASSOC */ |
| uamp_write_cmd(HCI_Read_Local_AMP_ASSOC, read_local_amp_assoc_data, |
| sizeof(read_local_amp_assoc_data), cmd, sizeof(buf)); |
| WAIT_FOR_KEY(1000); |
| |
| /* Create_Logical_Link */ |
| uamp_write_cmd(HCI_Create_Logical_Link, create_logical_link_data, |
| sizeof(create_logical_link_data), cmd, sizeof(buf)); |
| WAIT_FOR_KEY(1000); |
| |
| /* HCI_ACL_data */ |
| uamp_write_data(0 | HCI_ACL_DATA_BC_FLAGS | HCI_ACL_DATA_PB_FLAGS, tx_data, |
| sizeof(tx_data), pkt, sizeof(buf)); |
| WAIT_FOR_KEY(1000); |
| |
| /* Disconnect_Logical_Link */ |
| uamp_write_cmd(HCI_Disconnect_Logical_Link, disconnect_logical_link_data, |
| sizeof(disconnect_logical_link_data), cmd, sizeof(buf)); |
| |
| /* Disconnect_Physical_Link */ |
| uamp_write_cmd(HCI_Disconnect_Physical_Link, disconnect_physical_link_data, |
| sizeof(disconnect_physical_link_data), cmd, sizeof(buf)); |
| |
| usleep(1000*1000); |
| UAMP_Close(0); |
| usleep(1000*1000); |
| UAMP_PRINT(("UAMP creator test done!\n")); |
| |
| return (0); |
| } |
| |
| |
| /* |
| * Send UAMP command. |
| */ |
| static UINT16 uamp_write_cmd(uint16 opcode, uint8 *params, uint8 len, |
| amp_hci_cmd_t *cmd, unsigned int max_len) |
| { |
| memset(cmd, 0, sizeof(amp_hci_cmd_t)); |
| cmd->plen = len; |
| cmd->opcode = opcode; |
| assert(HCI_CMD_PREAMBLE_SIZE + len <= max_len); |
| |
| if (len != 0) { |
| memcpy(cmd->parms, params, len); |
| } |
| |
| return (UAMP_Write(0, (UINT8 *)cmd, HCI_CMD_PREAMBLE_SIZE + len, UAMP_CH_HCI_CMD)); |
| } |
| |
| |
| /* |
| * Send UAMP data. |
| */ |
| static UINT16 uamp_write_data(uint16 handle, uint8 *data, uint8 len, |
| amp_hci_ACL_data_t *pkt, unsigned int max_len) |
| { |
| memset(pkt, 0, sizeof(amp_hci_ACL_data_t)); |
| pkt->handle = handle; |
| pkt->dlen = len; |
| assert(HCI_ACL_DATA_PREAMBLE_SIZE + len <= max_len); |
| |
| if (len != 0) { |
| memcpy(pkt->data, data, len); |
| } |
| |
| return (UAMP_Write(0, (UINT8 *)pkt, HCI_ACL_DATA_PREAMBLE_SIZE + len, UAMP_CH_HCI_DATA)); |
| } |
| #endif /* UAMP_TEST */ |