| #include "includes.h" |
| #include <sys/ioctl.h> |
| #include <net/if_arp.h> |
| #include <net/if.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <netlink/genl/genl.h> |
| #include <netlink/genl/family.h> |
| #include <netlink/genl/ctrl.h> |
| #include <netlink/msg.h> |
| #include <netlink/attr.h> |
| |
| #include "wireless_copy.h" |
| #include "common.h" |
| #include "driver.h" |
| #include "eloop.h" |
| #include "driver_wext.h" |
| #include "ieee802_11_defs.h" |
| #include "wpa_common.h" |
| #include "wpa_ctrl.h" |
| #include "wpa_supplicant_i.h" |
| #include "config_ssid.h" |
| #include "wpa_debug.h" |
| #include "linux_ioctl.h" |
| #include "driver_nl80211.h" |
| |
| #define WPA_EVENT_DRIVER_STATE "CTRL-EVENT-DRIVER-STATE " |
| #define DRV_NUMBER_SEQUENTIAL_ERRORS 4 |
| |
| #define WPA_PS_ENABLED 0 |
| #define WPA_PS_DISABLED 1 |
| |
| #define BLUETOOTH_COEXISTENCE_MODE_ENABLED 0 |
| #define BLUETOOTH_COEXISTENCE_MODE_DISABLED 1 |
| #define BLUETOOTH_COEXISTENCE_MODE_SENSE 2 |
| |
| |
| static int g_drv_errors = 0; |
| static int g_power_mode = 0; |
| |
| int send_and_recv_msgs(struct wpa_driver_nl80211_data *drv, struct nl_msg *msg, |
| int (*valid_handler)(struct nl_msg *, void *), |
| void *valid_data); |
| |
| static void wpa_driver_send_hang_msg(struct wpa_driver_nl80211_data *drv) |
| { |
| g_drv_errors++; |
| if (g_drv_errors > DRV_NUMBER_SEQUENTIAL_ERRORS) { |
| g_drv_errors = 0; |
| wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "HANGED"); |
| } |
| } |
| |
| static int get_link_signal(struct nl_msg *msg, void *arg) |
| { |
| struct nlattr *tb[NL80211_ATTR_MAX + 1]; |
| struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); |
| struct nlattr *sinfo[NL80211_STA_INFO_MAX + 1]; |
| static struct nla_policy policy[NL80211_STA_INFO_MAX + 1] = { |
| [NL80211_STA_INFO_SIGNAL] = { .type = NLA_U8 }, |
| }; |
| struct nlattr *rinfo[NL80211_RATE_INFO_MAX + 1]; |
| static struct nla_policy rate_policy[NL80211_RATE_INFO_MAX + 1] = { |
| [NL80211_RATE_INFO_BITRATE] = { .type = NLA_U16 }, |
| [NL80211_RATE_INFO_MCS] = { .type = NLA_U8 }, |
| [NL80211_RATE_INFO_40_MHZ_WIDTH] = { .type = NLA_FLAG }, |
| [NL80211_RATE_INFO_SHORT_GI] = { .type = NLA_FLAG }, |
| }; |
| struct wpa_signal_info *sig_change = arg; |
| |
| nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), |
| genlmsg_attrlen(gnlh, 0), NULL); |
| if (!tb[NL80211_ATTR_STA_INFO] || |
| nla_parse_nested(sinfo, NL80211_STA_INFO_MAX, |
| tb[NL80211_ATTR_STA_INFO], policy)) |
| return NL_SKIP; |
| if (!sinfo[NL80211_STA_INFO_SIGNAL]) |
| return NL_SKIP; |
| |
| sig_change->current_signal = |
| (s8) nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL]); |
| |
| if (sinfo[NL80211_STA_INFO_TX_BITRATE]) { |
| if (nla_parse_nested(rinfo, NL80211_RATE_INFO_MAX, |
| sinfo[NL80211_STA_INFO_TX_BITRATE], |
| rate_policy)) { |
| sig_change->current_txrate = 0; |
| } else { |
| if (rinfo[NL80211_RATE_INFO_BITRATE]) { |
| sig_change->current_txrate = |
| nla_get_u16(rinfo[ |
| NL80211_RATE_INFO_BITRATE]) * 100; |
| } |
| } |
| } |
| |
| return NL_SKIP; |
| } |
| |
| static int wpa_driver_get_link_signal(void *priv, struct wpa_signal_info *sig) |
| { |
| struct i802_bss *bss = priv; |
| struct wpa_driver_nl80211_data *drv = bss->drv; |
| struct nl_msg *msg; |
| int ret = -1; |
| |
| sig->current_signal = -9999; |
| sig->current_txrate = 0; |
| |
| msg = nlmsg_alloc(); |
| if (!msg) |
| return -1; |
| |
| genlmsg_put(msg, 0, 0, genl_family_get_id(drv->nl80211), 0, |
| 0, NL80211_CMD_GET_STATION, 0); |
| |
| NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex); |
| NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, drv->bssid); |
| |
| ret = send_and_recv_msgs(drv, msg, get_link_signal, sig); |
| msg = NULL; |
| if (ret < 0) |
| wpa_printf(MSG_ERROR, "nl80211: get link signal fail: %d", ret); |
| nla_put_failure: |
| nlmsg_free(msg); |
| return ret; |
| } |
| |
| static int wpa_driver_toggle_btcoex_state(char state) |
| { |
| int ret; |
| int fd = open("/sys/devices/platform/wl1271/bt_coex_state", O_RDWR, 0); |
| if (fd == -1) |
| return -1; |
| |
| ret = write(fd, &state, sizeof(state)); |
| close(fd); |
| |
| wpa_printf(MSG_DEBUG, "%s: set btcoex state to '%c' result = %d", __func__, |
| state, ret); |
| return ret; |
| } |
| |
| static int wpa_driver_set_power_save(void *priv, int state) |
| { |
| struct i802_bss *bss = priv; |
| struct wpa_driver_nl80211_data *drv = bss->drv; |
| struct nl_msg *msg; |
| int ret = -1; |
| enum nl80211_ps_state ps_state; |
| |
| msg = nlmsg_alloc(); |
| if (!msg) |
| return -1; |
| |
| genlmsg_put(msg, 0, 0, genl_family_get_id(drv->nl80211), 0, 0, |
| NL80211_CMD_SET_POWER_SAVE, 0); |
| |
| if (state == WPA_PS_ENABLED) |
| ps_state = NL80211_PS_ENABLED; |
| else |
| ps_state = NL80211_PS_DISABLED; |
| |
| NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex); |
| NLA_PUT_U32(msg, NL80211_ATTR_PS_STATE, ps_state); |
| |
| ret = send_and_recv_msgs(drv, msg, NULL, NULL); |
| msg = NULL; |
| if (ret < 0) |
| wpa_printf(MSG_ERROR, "nl80211: Set power mode fail: %d", ret); |
| nla_put_failure: |
| nlmsg_free(msg); |
| return ret; |
| } |
| |
| int wpa_driver_nl80211_driver_cmd(void *priv, char *cmd, char *buf, |
| size_t buf_len ) |
| { |
| struct i802_bss *bss = priv; |
| struct wpa_driver_nl80211_data *drv = bss->drv; |
| struct ifreq ifr; |
| int ret = 0; |
| |
| if (os_strcasecmp(cmd, "STOP") == 0) { |
| linux_set_iface_flags(drv->ioctl_sock, bss->ifname, 0); |
| wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "STOPPED"); |
| } else if (os_strcasecmp(cmd, "START") == 0) { |
| linux_set_iface_flags(drv->ioctl_sock, bss->ifname, 1); |
| wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "STARTED"); |
| } else if (os_strcasecmp(cmd, "RELOAD") == 0) { |
| wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "HANGED"); |
| } else if (os_strncasecmp(cmd, "POWERMODE ", 10) == 0) { |
| int mode; |
| mode = atoi(cmd + 10); |
| ret = wpa_driver_set_power_save(priv, mode); |
| if (ret < 0) { |
| wpa_driver_send_hang_msg(drv); |
| } else { |
| g_power_mode = mode; |
| g_drv_errors = 0; |
| } |
| } else if (os_strncasecmp(cmd, "GETPOWER", 8) == 0) { |
| ret = os_snprintf(buf, buf_len, "POWERMODE = %d\n", g_power_mode); |
| } else if (os_strncasecmp(cmd, "BTCOEXMODE ", 11) == 0) { |
| int mode = atoi(cmd + 11); |
| if (mode == BLUETOOTH_COEXISTENCE_MODE_DISABLED) { /* disable BT-coex */ |
| ret = wpa_driver_toggle_btcoex_state('0'); |
| } else if (mode == BLUETOOTH_COEXISTENCE_MODE_SENSE) { /* enable BT-coex */ |
| ret = wpa_driver_toggle_btcoex_state('1'); |
| } else { |
| wpa_printf(MSG_DEBUG, "invalid btcoex mode: %d", mode); |
| ret = -1; |
| } |
| } else if ((os_strcasecmp(cmd, "RSSI") == 0) || (os_strcasecmp(cmd, "RSSI-APPROX") == 0)) { |
| struct wpa_signal_info sig; |
| int rssi; |
| |
| if (!drv->associated) |
| return -1; |
| |
| ret = wpa_driver_get_link_signal(priv, &sig); |
| if (ret < 0) { |
| wpa_driver_send_hang_msg(drv); |
| } else { |
| rssi = sig.current_signal; |
| wpa_printf(MSG_DEBUG, "%s rssi %d\n", drv->ssid, rssi); |
| ret = os_snprintf(buf, buf_len, "%s rssi %d\n", drv->ssid, rssi); |
| } |
| } else if (os_strcasecmp(cmd, "LINKSPEED") == 0) { |
| struct wpa_signal_info sig; |
| int linkspeed; |
| |
| if (!drv->associated) |
| return -1; |
| |
| ret = wpa_driver_get_link_signal(priv, &sig); |
| if (ret < 0) { |
| wpa_driver_send_hang_msg(drv); |
| } else { |
| linkspeed = sig.current_txrate / 1000; |
| wpa_printf(MSG_DEBUG, "LinkSpeed %d\n", linkspeed); |
| ret = os_snprintf(buf, buf_len, "LinkSpeed %d\n", linkspeed); |
| } |
| } else if (os_strcasecmp(cmd, "MACADDR") == 0) { |
| u8 macaddr[ETH_ALEN] = {}; |
| |
| ret = linux_get_ifhwaddr(drv->ioctl_sock, bss->ifname, macaddr); |
| if (!ret) |
| ret = os_snprintf(buf, buf_len, |
| "Macaddr = " MACSTR "\n", MAC2STR(macaddr)); |
| } else { |
| wpa_printf(MSG_INFO, "%s: Unsupported command %s", __func__, cmd); |
| } |
| return ret; |
| } |