]> git.ipfire.org Git - network.git/blobdiff - src/libnetwork/phy.c
libnetwork: Add function to check if a PHY supports a specific channel
[network.git] / src / libnetwork / phy.c
index 6662eae489c50f0115a4d9e3f37309a3de155ed2..0bf9c81b844aab87c77ffadae0c1c834309e49bd 100644 (file)
 #include <netlink/attr.h>
 #include <netlink/genl/genl.h>
 #include <netlink/msg.h>
+#include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <sys/queue.h>
 
 #include <network/libnetwork.h>
 #include <network/logging.h>
 #include <network/phy.h>
 #include "libnetwork-private.h"
 
+struct network_phy_channel {
+       unsigned int number;
+       unsigned int frequency;
+       bool dfs;
+       double max_tx_power; // dBm
+
+       TAILQ_ENTRY(network_phy_channel) channels;
+};
+
 struct network_phy {
        struct network_ctx* ctx;
        int refcount;
@@ -39,6 +50,10 @@ struct network_phy {
        int index;
        char* name;
 
+       TAILQ_HEAD(head, network_phy_channel) channels;
+
+       ssize_t max_mpdu_length;
+       unsigned int vht_caps;
        unsigned int ht_caps;
 };
 
@@ -65,10 +80,95 @@ static int phy_get_index(const char* name) {
        return atoi(index);
 }
 
+static void phy_parse_vht_capabilities(struct network_phy* phy, __u32 caps) {
+       // Max MPDU length
+       switch (caps & 0x3) {
+               case 0:
+                       phy->max_mpdu_length = 3895;
+                       break;
+
+               case 1:
+                       phy->max_mpdu_length = 7991;
+                       break;
+
+               case 2:
+                       phy->max_mpdu_length = 11454;
+                       break;
+
+               case 3:
+                       phy->max_mpdu_length = -1;
+       }
+
+       // Supported channel widths
+       switch ((caps >> 2) & 0x3) {
+               case 0:
+                       break;
+
+               // Supports 160 MHz
+               case 1:
+                       phy->vht_caps |= NETWORK_PHY_VHT_CAP_VHT160;
+                       break;
+
+               // Supports 160 MHz and 80+80 MHz
+               case 2:
+                       phy->vht_caps |= NETWORK_PHY_VHT_CAP_VHT160;
+                       phy->vht_caps |= NETWORK_PHY_VHT_CAP_VHT80PLUS80;
+                       break;
+       }
+
+       // RX LDPC
+       if (caps & BIT(4))
+               phy->vht_caps |= NETWORK_PHY_VHT_CAP_RX_LDPC;
+
+       // RX Short GI 80 MHz
+       if (caps & BIT(5))
+               phy->vht_caps |= NETWORK_PHY_VHT_CAP_RX_SHORT_GI_80;
+
+       // RX Short GI 160 MHz and 80+80 MHz
+       if (caps & BIT(6))
+               phy->vht_caps |= NETWORK_PHY_VHT_CAP_RX_SHORT_GI_160;
+
+       // TX STBC
+       if (caps & BIT(7))
+               phy->vht_caps |= NETWORK_PHY_VHT_CAP_TX_STBC;
+
+       // Single User Beamformer
+       if (caps & BIT(11))
+               phy->vht_caps |= NETWORK_PHY_VHT_CAP_SU_BEAMFORMER;
+
+       // Single User Beamformee
+       if (caps & BIT(12))
+               phy->vht_caps |= NETWORK_PHY_VHT_CAP_SU_BEAMFORMEE;
+
+       // Multi User Beamformer
+       if (caps & BIT(19))
+               phy->vht_caps |= NETWORK_PHY_VHT_CAP_MU_BEAMFORMER;
+
+       // Multi User Beamformee
+       if (caps & BIT(20))
+               phy->vht_caps |= NETWORK_PHY_VHT_CAP_MU_BEAMFORMEE;
+
+       // TX-OP-PS
+       if (caps & BIT(21))
+               phy->vht_caps |= NETWORK_PHY_VHT_CAP_TXOP_PS;
+
+       // HTC-VHT
+       if (caps & BIT(22))
+               phy->vht_caps |= NETWORK_PHY_VHT_CAP_HTC_VHT;
+
+       // RX Antenna Pattern Consistency
+       if (caps & BIT(28))
+               phy->vht_caps |= NETWORK_PHY_VHT_CAP_RX_ANTENNA_PATTERN;
+
+       // TX Antenna Pattern Consistency
+       if (caps & BIT(29))
+               phy->vht_caps |= NETWORK_PHY_VHT_CAP_TX_ANTENNA_PATTERN;
+}
+
 static void phy_parse_ht_capabilities(struct network_phy* phy, __u16 caps) {
-       // RX LDCP
+       // RX LDPC
        if (caps & BIT(0))
-               phy->ht_caps |= NETWORK_PHY_HT_CAP_RX_LDCP;
+               phy->ht_caps |= NETWORK_PHY_HT_CAP_RX_LDPC;
 
        // HT40
        if (caps & BIT(1))
@@ -145,6 +245,77 @@ static void phy_parse_ht_capabilities(struct network_phy* phy, __u16 caps) {
                phy->ht_caps |= NETWORK_PHY_HT_CAP_LSIG_TXOP_PROT;
 }
 
+static struct nla_policy phy_frequency_policy[NL80211_FREQUENCY_ATTR_MAX + 1] = {
+       [NL80211_FREQUENCY_ATTR_FREQ] = { .type = NLA_U32 },
+       [NL80211_FREQUENCY_ATTR_DISABLED] = { .type = NLA_FLAG },
+       [NL80211_FREQUENCY_ATTR_NO_IR] = { .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 },
+};
+
+static unsigned int phy_frequency_to_channel(unsigned int freq) {
+       if (freq == 2484)
+               return 14;
+
+       else if (freq < 2484)
+               return (freq - 2407) / 5;
+
+       else if (freq >= 4910 && freq <= 4980)
+               return (freq - 4000) / 5;
+
+       else if (freq <= 45000)
+               return (freq - 5000) / 5;
+
+       else if (freq >= 58320 && freq <= 64800)
+               return (freq - 56160) / 2160;
+
+       return 0;
+};
+
+static int phy_parse_channels(struct network_phy* phy, struct nlattr* nl_freqs) {
+       struct nlattr* nl_freq;
+       int rem_freq;
+
+       struct nlattr* tb_freq[NL80211_FREQUENCY_ATTR_MAX + 1];
+       nla_for_each_nested(nl_freq, nl_freqs, rem_freq) {
+               // Get data
+               nla_parse(tb_freq, NL80211_FREQUENCY_ATTR_MAX, nla_data(nl_freq),
+                               nla_len(nl_freq), phy_frequency_policy);
+
+               if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ])
+                       continue;
+
+               // Skip any disabled channels
+               if (tb_freq[NL80211_FREQUENCY_ATTR_DISABLED])
+                       continue;
+
+               struct network_phy_channel* channel = calloc(1, sizeof(*channel));
+               if (!channel)
+                       return -1;
+
+               // Append object to list of channels
+               TAILQ_INSERT_TAIL(&phy->channels, channel, channels);
+
+               // Get frequency
+               channel->frequency = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]);
+
+               // Convert frequency to channel
+               channel->number = phy_frequency_to_channel(channel->frequency);
+
+               // Radar detection
+               if (tb_freq[NL80211_FREQUENCY_ATTR_RADAR])
+                       channel->dfs = true;
+
+               // Maximum TX power
+               if (tb_freq[NL80211_FREQUENCY_ATTR_MAX_TX_POWER])
+                       channel->max_tx_power = \
+                               nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_MAX_TX_POWER]) * 0.01;
+       }
+
+       return 0;
+}
+
 static int phy_parse_info(struct nl_msg* msg, void* data) {
        struct network_phy* phy = data;
 
@@ -165,8 +336,20 @@ static int phy_parse_info(struct nl_msg* msg, void* data) {
 
                        // HT Capabilities
                        if (band_attrs[NL80211_BAND_ATTR_HT_CAPA]) {
-                               __u16 caps = nla_get_u16(band_attrs[NL80211_BAND_ATTR_HT_CAPA]);
-                               phy_parse_ht_capabilities(phy, caps);
+                               __u16 ht_caps = nla_get_u16(band_attrs[NL80211_BAND_ATTR_HT_CAPA]);
+                               phy_parse_ht_capabilities(phy, ht_caps);
+                       }
+
+                       // VHT Capabilities
+                       if (band_attrs[NL80211_BAND_ATTR_VHT_CAPA]) {
+                               __u32 vht_caps = nla_get_u32(band_attrs[NL80211_BAND_ATTR_VHT_CAPA]);
+
+                               phy_parse_vht_capabilities(phy, vht_caps);
+                       }
+
+                       // Frequencies
+                       if (band_attrs[NL80211_BAND_ATTR_FREQS]) {
+                               phy_parse_channels(phy, band_attrs[NL80211_BAND_ATTR_FREQS]);
                        }
                }
        }
@@ -195,6 +378,13 @@ static int phy_get_info(struct network_phy* phy) {
 static void network_phy_free(struct network_phy* phy) {
        DEBUG(phy->ctx, "Releasing phy at %p\n", phy);
 
+       // Destroy all channels
+       while (!TAILQ_EMPTY(&phy->channels)) {
+               struct network_phy_channel* channel = TAILQ_FIRST(&phy->channels);
+               TAILQ_REMOVE(&phy->channels, channel, channels);
+               free(channel);
+       }
+
        if (phy->name)
                free(phy->name);
 
@@ -220,6 +410,9 @@ NETWORK_EXPORT int network_phy_new(struct network_ctx* ctx, struct network_phy**
        p->refcount = 1;
 
        p->name = strdup(name);
+       p->index = index;
+
+       TAILQ_INIT(&p->channels);
 
        // Load information from kernel
        int r = phy_get_info(p);
@@ -230,7 +423,7 @@ NETWORK_EXPORT int network_phy_new(struct network_ctx* ctx, struct network_phy**
                return r;
        }
 
-       DEBUG(p->ctx, "Allocated phy at %p\n", p);
+       DEBUG(p->ctx, "Allocated phy at %p (index = %d)\n", p, p->index);
        *phy = p;
        return 0;
 }
@@ -271,13 +464,65 @@ nla_put_failure:
        return NULL;
 }
 
+NETWORK_EXPORT int network_phy_has_vht_capability(struct network_phy* phy, const enum network_phy_vht_caps cap) {
+       return phy->vht_caps & cap;
+}
+
 NETWORK_EXPORT int network_phy_has_ht_capability(struct network_phy* phy, const enum network_phy_ht_caps cap) {
        return phy->ht_caps & cap;
 }
 
+static const char* network_phy_get_vht_capability_string(const enum network_phy_vht_caps cap) {
+       switch (cap) {
+               case NETWORK_PHY_VHT_CAP_VHT160:
+                       return "[VHT-160]";
+
+               case NETWORK_PHY_VHT_CAP_VHT80PLUS80:
+                       return "[VHT-160-80PLUS80]";
+
+               case NETWORK_PHY_VHT_CAP_RX_LDPC:
+                       return "[RXLDPC]";
+
+               case NETWORK_PHY_VHT_CAP_RX_SHORT_GI_80:
+                       return "[SHORT-GI-80]";
+
+               case NETWORK_PHY_VHT_CAP_RX_SHORT_GI_160:
+                       return "[SHORT-GI-160]";
+
+               case NETWORK_PHY_VHT_CAP_TX_STBC:
+                       return "[TX-STBC-2BY1]";
+
+               case NETWORK_PHY_VHT_CAP_SU_BEAMFORMER:
+                       return "[SU-BEAMFORMER]";
+
+               case NETWORK_PHY_VHT_CAP_SU_BEAMFORMEE:
+                       return "[SU-BEAMFORMEE]";
+
+               case NETWORK_PHY_VHT_CAP_MU_BEAMFORMER:
+                       return "[MU-BEAMFORMER]";
+
+               case NETWORK_PHY_VHT_CAP_MU_BEAMFORMEE:
+                       return "[MU-BEAMFORMEE]";
+
+               case NETWORK_PHY_VHT_CAP_TXOP_PS:
+                       return "[VHT-TXOP-PS]";
+
+               case NETWORK_PHY_VHT_CAP_HTC_VHT:
+                       return "[HTC-VHT]";
+
+               case NETWORK_PHY_VHT_CAP_RX_ANTENNA_PATTERN:
+                       return "[RX-ANTENNA-PATTERN]";
+
+               case NETWORK_PHY_VHT_CAP_TX_ANTENNA_PATTERN:
+                       return "[TX-ANTENNA-PATTERN]";
+       }
+
+       return NULL;
+}
+
 static const char* network_phy_get_ht_capability_string(const enum network_phy_ht_caps cap) {
        switch (cap) {
-               case NETWORK_PHY_HT_CAP_RX_LDCP:
+               case NETWORK_PHY_HT_CAP_RX_LDPC:
                        return "[LDPC]";
 
                case NETWORK_PHY_HT_CAP_HT40:
@@ -329,6 +574,32 @@ static const char* network_phy_get_ht_capability_string(const enum network_phy_h
        return NULL;
 }
 
+NETWORK_EXPORT char* network_phy_list_vht_capabilities(struct network_phy* phy) {
+       char* buffer = malloc(1024);
+       *buffer = '\0';
+
+       char* p = buffer;
+
+       switch (phy->max_mpdu_length) {
+               case 7991:
+               case 11454:
+                       snprintf(p, 1024 - 1, "[MAX-MPDU-%zu]", phy->max_mpdu_length);
+                       break;
+
+       }
+
+       foreach_vht_cap(cap) {
+               if (network_phy_has_vht_capability(phy, cap)) {
+                       const char* cap_str = network_phy_get_vht_capability_string(cap);
+
+                       if (cap_str)
+                               p = strncat(p, cap_str, 1024 - 1);
+               }
+       }
+
+       return buffer;
+}
+
 NETWORK_EXPORT char* network_phy_list_ht_capabilities(struct network_phy* phy) {
        char* buffer = malloc(1024);
        *buffer = '\0';
@@ -345,3 +616,20 @@ NETWORK_EXPORT char* network_phy_list_ht_capabilities(struct network_phy* phy) {
 
        return buffer;
 }
+
+NETWORK_EXPORT char* network_phy_list_channels(struct network_phy* phy) {
+       char string[10240] = "CHAN FREQ  DFS TXPWR\n";
+       char* p = string + strlen(string);
+
+       struct network_phy_channel* channel;
+       TAILQ_FOREACH(channel, &phy->channels, channels) {
+               p += sprintf(p, "%-4u %-5u %-3s %-4.1f\n",
+                       channel->number,
+                       channel->frequency,
+                       (channel->dfs) ? "Y" : "N",
+                       channel->max_tx_power
+               );
+       }
+
+       return strdup(string);
+}