| /* |
| * This is a stripped down version of the iw tool designed for |
| * programmatically checking driver/hw capabilities. |
| * |
| * Copyright 2007, 2008 Johannes Berg <johannes@sipsolutions.net> |
| */ |
| |
| #include <errno.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <net/if.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <stdbool.h> |
| |
| #include <netlink/netlink.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 "nl80211.h" |
| |
| #ifndef CONFIG_LIBNL20 |
| /* libnl 2.0 compatibility code */ |
| |
| # define nl_sock nl_handle |
| |
| static inline struct nl_handle *nl_socket_alloc(void) |
| { |
| return nl_handle_alloc(); |
| } |
| |
| static inline void nl_socket_free(struct nl_sock *h) |
| { |
| nl_handle_destroy(h); |
| } |
| |
| static inline int __genl_ctrl_alloc_cache(struct nl_sock *h, struct nl_cache **cache) |
| { |
| struct nl_cache *tmp = genl_ctrl_alloc_cache(h); |
| if (!tmp) |
| return -ENOMEM; |
| *cache = tmp; |
| return 0; |
| } |
| #define genl_ctrl_alloc_cache __genl_ctrl_alloc_cache |
| #endif /* CONFIG_LIBNL20 */ |
| |
| struct nl80211_state { |
| struct nl_sock *nl_sock; |
| struct nl_cache *nl_cache; |
| struct genl_family *nl80211; |
| }; |
| |
| static int nl80211_init(struct nl80211_state *state) |
| { |
| int err; |
| |
| state->nl_sock = nl_socket_alloc(); |
| if (!state->nl_sock) { |
| fprintf(stderr, "Failed to allocate netlink socket.\n"); |
| return -ENOMEM; |
| } |
| |
| if (genl_connect(state->nl_sock)) { |
| fprintf(stderr, "Failed to connect to generic netlink.\n"); |
| err = -ENOLINK; |
| goto out_handle_destroy; |
| } |
| |
| if (genl_ctrl_alloc_cache(state->nl_sock, &state->nl_cache)) { |
| fprintf(stderr, "Failed to allocate generic netlink cache.\n"); |
| err = -ENOMEM; |
| goto out_handle_destroy; |
| } |
| |
| state->nl80211 = genl_ctrl_search_by_name(state->nl_cache, "nl80211"); |
| if (!state->nl80211) { |
| fprintf(stderr, "nl80211 not found.\n"); |
| err = -ENOENT; |
| goto out_cache_free; |
| } |
| |
| return 0; |
| |
| out_cache_free: |
| nl_cache_free(state->nl_cache); |
| out_handle_destroy: |
| nl_socket_free(state->nl_sock); |
| return err; |
| } |
| |
| static void nl80211_cleanup(struct nl80211_state *state) |
| { |
| genl_family_put(state->nl80211); |
| nl_cache_free(state->nl_cache); |
| nl_socket_free(state->nl_sock); |
| } |
| |
| static const char *argv0; |
| |
| static int phy_lookup(char *name) |
| { |
| char buf[200]; |
| int fd, pos; |
| |
| snprintf(buf, sizeof(buf), "/sys/class/ieee80211/%s/index", name); |
| |
| fd = open(buf, O_RDONLY); |
| if (fd < 0) |
| return -1; |
| pos = read(fd, buf, sizeof(buf) - 1); |
| if (pos < 0) |
| return -1; |
| buf[pos] = '\0'; |
| return atoi(buf); |
| } |
| |
| enum { |
| CHECK_IS_HT20 = 0x00000001, |
| CHECK_IS_HT40 = 0x00000002, |
| CHECK_IS_PSMP = 0x00000004, |
| CHECK_IS_AMPDU = 0x00000008, |
| CHECK_IS_AMSDU = 0x00000010, |
| CHECK_IS_SMPS = 0x00000020, |
| CHECK_IS_STA = 0x00000040, |
| CHECK_IS_AP = 0x00000080, |
| CHECK_IS_IBSS = 0x00000100, |
| CHECK_IS_MBSS = 0x00000200, |
| CHECK_IS_MONITOR = 0x00000400, |
| CHECK_BANDS = 0x00000800, |
| CHECK_FREQS = 0x00001000, |
| CHECK_RATES = 0x00002000, |
| CHECK_MCS = 0x00004000, |
| CHECK_AMPDU_DENS = 0x00008000, |
| CHECK_AMPDU_FACT = 0x00010000, |
| CHECK_AMSDU_LEN = 0x00020000, |
| CHECK_IS_LPDC = 0x00040000, |
| CHECK_IS_GREENFIELD = 0x00080000, |
| CHECK_IS_SGI20 = 0x00100000, |
| CHECK_IS_SGI40 = 0x00200000, |
| CHECK_IS_TXSTBC = 0x00400000, |
| CHECK_RXSTBC = 0x00800000, |
| CHECK_IS_DELBA = 0x01000000, |
| /* NB: must be in upper 16-bits to avoid HT caps */ |
| CHECK_IS_24GHZ = 0x02000000, |
| CHECK_IS_5GHZ = 0x04000000, |
| CHECK_IS_11B = 0x08000000, |
| CHECK_IS_11G = 0x10000000, |
| CHECK_IS_11A = 0x20000000, |
| CHECK_IS_11N = 0x40000000, |
| }; |
| |
| struct check { |
| const char *name; |
| int namelen; |
| int bits; |
| }; |
| static const struct check checks[] = { |
| { "24ghz", 5, CHECK_IS_24GHZ }, |
| { "5ghz", 4, CHECK_IS_5GHZ }, |
| { "11b", 3, CHECK_IS_11B }, |
| { "11g", 3, CHECK_IS_11G }, |
| { "11a", 3, CHECK_IS_11A }, |
| { "11n", 3, CHECK_IS_11N }, |
| { "ht20", 4, CHECK_IS_HT20 }, |
| { "ht40", 4, CHECK_IS_HT40 }, |
| { "psmp", 5, CHECK_IS_PSMP }, |
| { "ampdu", 5, CHECK_IS_AMPDU }, |
| { "amsdu", 5, CHECK_IS_AMSDU }, |
| { "smps", 4, CHECK_IS_SMPS }, |
| { "sta", 3, CHECK_IS_STA }, |
| { "ap", 2, CHECK_IS_AP }, |
| { "ibss", 4, CHECK_IS_IBSS }, |
| { "mbss", 4, CHECK_IS_MBSS }, |
| { "mon", 3, CHECK_IS_MONITOR }, |
| { "bands", 4, CHECK_BANDS }, |
| { "freqs", 4, CHECK_FREQS }, |
| { "rates", 4, CHECK_RATES }, |
| { "mcs", 3, CHECK_MCS }, |
| { "ampdu_dens", 10, CHECK_AMPDU_DENS }, |
| { "ampdu_fact", 10, CHECK_AMPDU_FACT }, |
| { "amsdu_len", 9, CHECK_AMSDU_LEN }, |
| { "lpdc", 4, CHECK_IS_LPDC }, |
| { "green", 5, CHECK_IS_GREENFIELD }, |
| { "sgi20", 5, CHECK_IS_SGI20 }, |
| { "sgi40", 5, CHECK_IS_SGI40 }, |
| { "txstbc", 6, CHECK_IS_TXSTBC }, |
| { "rxstbc", 6, CHECK_RXSTBC }, |
| { "delba", 5, CHECK_IS_DELBA }, |
| { "all", 3, -1 }, |
| { NULL } |
| }; |
| |
| static const struct check *find_check_byname(const char *name) |
| { |
| const struct check *p; |
| |
| for (p = checks; p->name != NULL; p++) |
| if (strncasecmp(p->name, name, p->namelen) == 0) |
| return p; |
| return NULL; |
| } |
| |
| #if 0 |
| static const struct check *find_check_bybits(int bits) |
| { |
| const struct check *p; |
| |
| for (p = checks; p->name != NULL; p++) |
| if (p->bits == bits) |
| return p; |
| return NULL; |
| } |
| #endif |
| |
| static int check_iftype(struct nlattr *tb_msg[], int nl_type) |
| { |
| struct nlattr *nl_mode; |
| int rem_mode; |
| |
| if (!tb_msg[NL80211_ATTR_SUPPORTED_IFTYPES]) |
| return 0; |
| |
| nla_for_each_nested(nl_mode, tb_msg[NL80211_ATTR_SUPPORTED_IFTYPES], rem_mode) |
| if (nl_mode->nla_type == nl_type) |
| return 1; |
| return 0; |
| } |
| |
| static unsigned int get_max_mcs(unsigned char *mcs) |
| { |
| unsigned int mcs_bit, max; |
| |
| max = 0; |
| for (mcs_bit = 0; mcs_bit <= 76; mcs_bit++) { |
| unsigned int mcs_octet = mcs_bit/8; |
| unsigned int MCS_RATE_BIT = 1 << mcs_bit % 8; |
| bool mcs_rate_idx_set; |
| |
| mcs_rate_idx_set = !!(mcs[mcs_octet] & MCS_RATE_BIT); |
| |
| if (!mcs_rate_idx_set) |
| continue; |
| |
| if (mcs_bit > max) |
| max = mcs_bit; |
| } |
| return max; |
| } |
| |
| static void pbool(const char *tag, int v) |
| { |
| printf("%s: %s\n", tag, v ? "true" : "false"); |
| } |
| |
| static void pint(const char *tag, int v) |
| { |
| printf("%s: %d\n", tag, v); |
| } |
| |
| static void prate(const char *tag, int v) |
| { |
| printf("%s: %2.1f\n", tag, 0.1*v); |
| } |
| |
| static int check_phy_handler(struct nl_msg *msg, void *arg) |
| { |
| struct nlattr *tb_msg[NL80211_ATTR_MAX + 1]; |
| struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); |
| uintptr_t checks = (uintptr_t) arg; |
| |
| struct nlattr *tb_band[NL80211_BAND_ATTR_MAX + 1]; |
| |
| struct nlattr *tb_freq[NL80211_FREQUENCY_ATTR_MAX + 1]; |
| static struct nla_policy freq_policy[NL80211_FREQUENCY_ATTR_MAX + 1] = { |
| [NL80211_FREQUENCY_ATTR_FREQ] = { .type = NLA_U32 }, |
| [NL80211_FREQUENCY_ATTR_DISABLED] = { .type = NLA_FLAG }, |
| [NL80211_FREQUENCY_ATTR_PASSIVE_SCAN] = { .type = NLA_FLAG }, |
| [NL80211_FREQUENCY_ATTR_NO_IBSS] = { .type = NLA_FLAG }, |
| [NL80211_FREQUENCY_ATTR_RADAR] = { .type = NLA_FLAG }, |
| [NL80211_FREQUENCY_ATTR_MAX_TX_POWER] = { .type = NLA_U32 }, |
| }; |
| |
| struct nlattr *tb_rate[NL80211_BITRATE_ATTR_MAX + 1]; |
| static struct nla_policy rate_policy[NL80211_BITRATE_ATTR_MAX + 1] = { |
| [NL80211_BITRATE_ATTR_RATE] = { .type = NLA_U32 }, |
| [NL80211_BITRATE_ATTR_2GHZ_SHORTPREAMBLE] = { .type = NLA_FLAG }, |
| }; |
| |
| struct nlattr *nl_band; |
| struct nlattr *nl_freq; |
| struct nlattr *nl_rate; |
| int rem_band, rem_freq, rem_rate, phy_caps; |
| int amsdu_len, ampdu_fact, ampdu_dens, max_mcs, max_rate; |
| |
| nla_parse(tb_msg, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), |
| genlmsg_attrlen(gnlh, 0), NULL); |
| |
| if (!tb_msg[NL80211_ATTR_WIPHY_BANDS]) |
| return NL_SKIP; |
| |
| phy_caps = 0; |
| amsdu_len = 0; |
| ampdu_fact = 0; |
| ampdu_dens = 0; |
| max_mcs = 0; |
| max_rate = 0; |
| /* NB: merge each band's findings; this stuff is silly */ |
| nla_for_each_nested(nl_band, tb_msg[NL80211_ATTR_WIPHY_BANDS], rem_band) { |
| nla_parse(tb_band, NL80211_BAND_ATTR_MAX, nla_data(nl_band), |
| nla_len(nl_band), NULL); |
| |
| if (tb_band[NL80211_BAND_ATTR_HT_CAPA]) { |
| unsigned short caps = nla_get_u16(tb_band[NL80211_BAND_ATTR_HT_CAPA]); |
| int len; |
| |
| /* XXX not quite right but close enough */ |
| phy_caps |= CHECK_IS_11N | caps; |
| len = 0xeff + ((caps & 0x0800) << 1); |
| if (len > amsdu_len) |
| amsdu_len = len; |
| } |
| if (tb_band[NL80211_BAND_ATTR_HT_AMPDU_FACTOR]) { |
| unsigned char factor = nla_get_u8(tb_band[NL80211_BAND_ATTR_HT_AMPDU_FACTOR]); |
| int fact = (1<<(13+factor))-1; |
| if (fact > ampdu_fact) |
| ampdu_fact = fact; |
| } |
| if (tb_band[NL80211_BAND_ATTR_HT_AMPDU_DENSITY]) { |
| unsigned char dens = nla_get_u8(tb_band[NL80211_BAND_ATTR_HT_AMPDU_DENSITY]); |
| if (dens > ampdu_dens) |
| ampdu_dens = dens; |
| } |
| if (tb_band[NL80211_BAND_ATTR_HT_MCS_SET] && |
| nla_len(tb_band[NL80211_BAND_ATTR_HT_MCS_SET]) == 16) { |
| /* As defined in 7.3.2.57.4 Supported MCS Set field */ |
| unsigned char *mcs = nla_data(tb_band[NL80211_BAND_ATTR_HT_MCS_SET]); |
| int max = get_max_mcs(&mcs[0]); |
| if (max > max_mcs) |
| max_mcs = max; |
| } |
| |
| nla_for_each_nested(nl_freq, tb_band[NL80211_BAND_ATTR_FREQS], rem_freq) { |
| uint32_t freq; |
| |
| nla_parse(tb_freq, NL80211_FREQUENCY_ATTR_MAX, |
| nla_data(nl_freq), nla_len(nl_freq), |
| freq_policy); |
| if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ]) |
| continue; |
| #if 0 |
| /* NB: we care about device caps, not regulatory */ |
| if (tb_freq[NL80211_FREQUENCY_ATTR_DISABLED]) |
| continue; |
| #endif |
| freq = nla_get_u32( |
| tb_freq[NL80211_FREQUENCY_ATTR_FREQ]); |
| if (checks & CHECK_FREQS) |
| pint("freq", freq); |
| |
| /* NB: approximate band boundaries, we get no help */ |
| if (2000 <= freq && freq <= 3000) |
| phy_caps |= CHECK_IS_24GHZ; |
| else if (4000 <= freq && freq <= 6000) |
| phy_caps |= CHECK_IS_5GHZ; |
| } |
| |
| nla_for_each_nested(nl_rate, tb_band[NL80211_BAND_ATTR_RATES], rem_rate) { |
| int rate; |
| |
| nla_parse(tb_rate, NL80211_BITRATE_ATTR_MAX, nla_data(nl_rate), |
| nla_len(nl_rate), rate_policy); |
| if (!tb_rate[NL80211_BITRATE_ATTR_RATE]) |
| continue; |
| rate = nla_get_u32(tb_rate[NL80211_BITRATE_ATTR_RATE]); |
| if (rate > max_rate) |
| max_rate = rate; |
| } |
| } |
| #if 0 |
| /* NB: 11n =>'s legacy support */ |
| if (phy_caps & CHECK_IS_11N) { |
| if (phy_caps & CHECK_IS_24GHZ) |
| phy_caps |= CHECK_IS_11B | CHECK_IS_11G; |
| if (phy_caps & CHECK_IS_5GHZ) |
| phy_caps |= CHECK_IS_11A; |
| } |
| #else |
| /* XXX no way to figure this out; just force 'em */ |
| if (phy_caps & CHECK_IS_24GHZ) |
| phy_caps |= CHECK_IS_11B | CHECK_IS_11G; |
| if (phy_caps & CHECK_IS_5GHZ) |
| phy_caps |= CHECK_IS_11A; |
| #endif |
| |
| #define PBOOL(c, b, name) if (checks & (c)) pbool(name, phy_caps & (b)) |
| PBOOL(CHECK_IS_24GHZ, CHECK_IS_24GHZ, "24ghz"); |
| PBOOL(CHECK_IS_5GHZ, CHECK_IS_5GHZ, "5ghz"); |
| PBOOL(CHECK_IS_11B, CHECK_IS_11B, "11b"); |
| PBOOL(CHECK_IS_11G, CHECK_IS_11G, "11g"); |
| PBOOL(CHECK_IS_11A, CHECK_IS_11A, "11a"); |
| PBOOL(CHECK_IS_11N, CHECK_IS_11N, "11n"); |
| PBOOL(CHECK_IS_LPDC, 0x1, "lpdc"); |
| PBOOL(CHECK_IS_HT20, CHECK_IS_11N, "ht20"); |
| PBOOL(CHECK_IS_HT40, 0x2, "ht40"); |
| if (checks & CHECK_IS_SMPS) |
| pbool("smps", ((phy_caps & 0x000c) >> 2) < 2); |
| PBOOL(CHECK_IS_GREENFIELD, 0x10, "green"); |
| PBOOL(CHECK_IS_SGI20, 0x20, "sgi20"); |
| PBOOL(CHECK_IS_SGI40, 0x40, "sgi40"); |
| PBOOL(CHECK_IS_TXSTBC, 0x40, "txstbc"); |
| PBOOL(CHECK_RXSTBC, 0x300, "rxstbc"); |
| PBOOL(CHECK_IS_DELBA, 0x400, "delba"); |
| #if 0 |
| PBOOL(CHECK_IS_DSSCCK, 0x1000, "dsscck"); |
| #endif |
| PBOOL(CHECK_IS_PSMP, 0x2000, "psmp"); |
| #if 0 |
| PBOOL(CHECK_IS_INTOL, 0x4000, "intol"); |
| #endif |
| #if 0 |
| PBOOL(CHECK_IS_LSIGTXOP, 0x8000, "lsigtxop"); |
| #endif |
| #undef PBOOL |
| if (checks & CHECK_AMSDU_LEN) |
| pint("amsdu_len", amsdu_len); |
| if (checks & CHECK_AMPDU_FACT) |
| pint("ampdu_fact", ampdu_fact); |
| if (checks & CHECK_AMPDU_DENS) |
| pint("ampdu_dens", ampdu_dens); |
| if (checks & CHECK_RATES) |
| prate("rate", max_rate); |
| if (checks & CHECK_MCS) |
| pint("mcs", max_mcs); |
| |
| if (checks & CHECK_IS_STA) |
| pbool("sta", check_iftype(tb_msg, NL80211_IFTYPE_STATION)); |
| if (checks & CHECK_IS_IBSS) |
| pbool("ibss", check_iftype(tb_msg, NL80211_IFTYPE_ADHOC)); |
| if (checks & CHECK_IS_AP) |
| pbool("ap", check_iftype(tb_msg, NL80211_IFTYPE_AP)); |
| if (checks & CHECK_IS_MBSS) |
| pbool("mbss", check_iftype(tb_msg, NL80211_IFTYPE_MESH_POINT)); |
| if (checks & CHECK_IS_MONITOR) |
| pbool("mon", check_iftype(tb_msg, NL80211_IFTYPE_MONITOR)); |
| |
| return NL_SKIP; |
| } |
| |
| static int check_phy_caps(struct nl80211_state *state, |
| struct nl_cb *cb, |
| struct nl_msg *msg, |
| int argc, char **argv) |
| { |
| int checks = 0; |
| for (; argc > 0; argc--, argv++) { |
| const struct check *p = find_check_byname(argv[0]); |
| if (p == NULL) { |
| fprintf(stderr, "invalid check %s\n", argv[0]); |
| return 3; /* XXX whatever? */ |
| } |
| checks |= p->bits; |
| } |
| nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, check_phy_handler, |
| (void *)(uintptr_t) checks); |
| return 0; |
| } |
| |
| static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, |
| void *arg) |
| { |
| int *ret = arg; |
| *ret = err->error; |
| return NL_STOP; |
| } |
| |
| static int finish_handler(struct nl_msg *msg, void *arg) |
| { |
| int *ret = arg; |
| *ret = 0; |
| return NL_SKIP; |
| } |
| |
| static int ack_handler(struct nl_msg *msg, void *arg) |
| { |
| int *ret = arg; |
| *ret = 0; |
| return NL_STOP; |
| } |
| |
| static int __handle_cmd(struct nl80211_state *state, int argc, char **argv) |
| { |
| struct nl_cb *cb; |
| struct nl_msg *msg; |
| int devidx, err; |
| |
| if (argc <= 1) |
| return 1; |
| |
| devidx = phy_lookup(*argv); |
| if (devidx < 0) |
| return -errno; |
| argc--, argv++; |
| |
| msg = nlmsg_alloc(); |
| if (!msg) { |
| fprintf(stderr, "failed to allocate netlink message\n"); |
| return 2; |
| } |
| |
| cb = nl_cb_alloc(NL_CB_DEFAULT); |
| if (!cb) { |
| fprintf(stderr, "failed to allocate netlink callbacks\n"); |
| err = 2; |
| goto out_free_msg; |
| } |
| |
| genlmsg_put(msg, 0, 0, genl_family_get_id(state->nl80211), 0, |
| 0, NL80211_CMD_GET_WIPHY, 0); |
| NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, devidx); |
| |
| err = check_phy_caps(state, cb, msg, argc, argv); |
| if (err) |
| goto out; |
| |
| err = nl_send_auto_complete(state->nl_sock, msg); |
| if (err < 0) |
| goto out; |
| |
| err = 1; |
| |
| nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err); |
| nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err); |
| nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &err); |
| |
| while (err > 0) |
| nl_recvmsgs(state->nl_sock, cb); |
| out: |
| nl_cb_put(cb); |
| out_free_msg: |
| nlmsg_free(msg); |
| return err; |
| nla_put_failure: |
| fprintf(stderr, "building message failed\n"); |
| return 2; |
| } |
| |
| int main(int argc, char **argv) |
| { |
| struct nl80211_state nlstate; |
| int err; |
| |
| argc--; |
| argv0 = *argv++; |
| |
| err = nl80211_init(&nlstate); |
| if (err == 0) { |
| if (argc > 1 && strncmp(*argv, "phy", 3) == 0) { |
| err = __handle_cmd(&nlstate, argc, argv); |
| if (err < 0) |
| fprintf(stderr, "command failed: %s (%d)\n", |
| strerror(-err), err); |
| else if (err) |
| fprintf(stderr, "command failed: err %d\n", err); |
| } else { |
| fprintf(stderr, "usage: %s phyX [args]\n", argv0); |
| err = 1; |
| } |
| nl80211_cleanup(&nlstate); |
| } |
| return err; |
| } |