/*
* 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;
}