From 67adfbc5b4831ecb62a169370c102e0939ae884e Mon Sep 17 00:00:00 2001 From: ian77_chen Date: Mon, 10 Mar 2025 15:21:57 +0800 Subject: [PATCH] patch for Morse Micro iwinfo --- .../utils/iwinfo/patches/MMiwinfo.patch | 1976 +++++++++++++++++ 1 file changed, 1976 insertions(+) create mode 100644 package/network/utils/iwinfo/patches/MMiwinfo.patch diff --git a/package/network/utils/iwinfo/patches/MMiwinfo.patch b/package/network/utils/iwinfo/patches/MMiwinfo.patch new file mode 100644 index 0000000000..c10d4bf881 --- /dev/null +++ b/package/network/utils/iwinfo/patches/MMiwinfo.patch @@ -0,0 +1,1976 @@ +diff --git a/.gitignore b/.gitignore +new file mode 100644 +index 0000000..7f4922f +--- /dev/null ++++ b/.gitignore +@@ -0,0 +1,17 @@ ++iwinfo_cli.o ++iwinfo_lib.o ++iwinfo_lua.o ++iwinfo_nl80211.o ++iwinfo_utils.o ++iwinfo_wext_scan.o ++iwinfo_wext.o ++iwinfo.so ++libiwinfo.so ++libiwinfo.so.0 ++iwinfo ++ ++*.so ++*.so.* ++*.o ++ ++.vscode/ +diff --git a/Makefile b/Makefile +index adb9e73..fac0abf 100644 +--- a/Makefile ++++ b/Makefile +@@ -37,6 +37,7 @@ ifneq ($(filter nl80211,$(IWINFO_BACKENDS)),) + IWINFO_CLI_LDFLAGS += -lnl-tiny + IWINFO_LIB_LDFLAGS += -lnl-tiny + IWINFO_LIB_OBJ += iwinfo_nl80211.o ++ IWINFO_LIB_OBJ += iwinfo_morsecli.o dot11ah_channel.o + endif + + +diff --git a/devices.txt b/devices.txt +index eded184..397a1cf 100644 +--- a/devices.txt ++++ b/devices.txt +@@ -192,6 +192,9 @@ + 0x02d0 0xa9a6 0x0000 0x0000 0 0 "Cypress" "CYW43455" + 0x02d0 0x4345 0x0000 0x0000 0 0 "Cypress" "CYW43455" + 0x1ae9 0x0310 0x1ae9 0x0000 0 0 "Wilocity" "Wil6210" ++0x325B 0x0206 0x0000 0x0000 0 0 "Morse Micro" "HaLow WiFi" ++0x325B 0x0306 0x0000 0x0000 0 0 "Morse Micro" "HaLow WiFi" ++ + + # USB devices + # 0x0000 | 0x0000 | vendor id | product id | ... +@@ -263,3 +266,4 @@ + "ralink,rt3883-wmac" 0 0 "Ralink" "Rt3883" + "ralink,rt5350-wmac" 0 0 "Ralink" "Rt5350" + "ralink,rt7620-wmac" 0 0 "MediaTek" "MT7620" ++"morse,mm610x-spi" 0 0 "Morse Micro" "HaLow WiFi" +diff --git a/dot11ah_channel.c b/dot11ah_channel.c +new file mode 100644 +index 0000000..24dfb0e +--- /dev/null ++++ b/dot11ah_channel.c +@@ -0,0 +1,444 @@ ++/* ++ * Copyright 2022 Morse Micro ++ * ++ * The iwinfo library is free software: you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License version 2 ++ * as published by the Free Software Foundation. ++ * ++ * The iwinfo library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ * See the GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License along ++ * with the iwinfo library. If not, see http://www.gnu.org/licenses/. ++*/ ++ ++#include ++#include ++#include ++ ++#include "dot11ah_channel.h" ++ ++static const channel_to_halow_freq_t kNullAhValue = {0, 0, 0, 0}; ++ ++static const country_channel_map_t us_channel_map = { ++ .country = "US", ++ .num_mapped_channels = 48, ++ .ah_vals = { ++ /* 1Mhz */ ++ {132, 1, 902.5, 0}, ++ {136, 3, 903.5, 0}, ++ {36, 5, 904.5, 0}, ++ {40, 7, 905.5, 0}, ++ {44, 9, 906.5, 0}, ++ {48, 11, 907.5, 0}, ++ {52, 13, 908.5, 0}, ++ {56, 15, 909.5, 0}, ++ {60, 17, 910.5, 0}, ++ {64, 19, 911.5, 0}, ++ {100, 21, 912.5, 0}, ++ {104, 23, 913.5, 0}, ++ {108, 25, 914.5, 0}, ++ {112, 27, 915.5, 0}, ++ {116, 29, 916.5, 0}, ++ {120, 31, 917.5, 0}, ++ {124, 33, 918.5, 0}, ++ {128, 35, 919.5, 0}, ++ {149, 37, 920.5, 0}, ++ {153, 39, 921.5, 0}, ++ {157, 41, 922.5, 0}, ++ {161, 43, 923.5, 0}, ++ {165, 45, 924.5, 0}, ++ {169, 47, 925.5, 0}, ++ {173, 49, 926.5, 0}, ++ {177, 51, 927.5, 0}, ++ /* 2MHz */ ++ {134, 2, 903, 1}, ++ {38, 6, 905, 1}, ++ {46, 10, 907, 1}, ++ {54, 14, 909, 1}, ++ {62, 18, 911, 1}, ++ {102, 22, 913, 1}, ++ {110, 26, 915, 1}, ++ {118, 30, 917, 1}, ++ {126, 34, 919, 1}, ++ {151, 38, 921, 1}, ++ {159, 42, 923, 1}, ++ {167, 46, 925, 1}, ++ {175, 50, 927, 1}, ++ /* 4MHz */ ++ {42, 8, 906, 2}, ++ {58, 16, 910, 2}, ++ {106, 24, 914, 2}, ++ {122, 32, 918, 2}, ++ {155, 40, 922, 2}, ++ {171, 48, 926, 2}, ++ /* 8MHz */ ++ {50, 12, 908, 3}, ++ {114, 28, 916, 3}, ++ {163, 44, 924, 3}, ++ } ++}; ++ ++static const country_channel_map_t au_channel_map = { ++ .country = "AU", ++ .num_mapped_channels = 23, ++ .ah_vals = { ++ /* 1Mhz */ ++ {112, 27, 915.5, 0}, ++ {116, 29, 916.5, 0}, ++ {120, 31, 917.5, 0}, ++ {124, 33, 918.5, 0}, ++ {128, 35, 919.5, 0}, ++ {149, 37, 920.5, 0}, ++ {153, 39, 921.5, 0}, ++ {157, 41, 922.5, 0}, ++ {161, 43, 923.5, 0}, ++ {165, 45, 924.5, 0}, ++ {169, 47, 925.5, 0}, ++ {173, 49, 926.5, 0}, ++ {177, 51, 927.5, 0}, ++ /* 2Mhz */ ++ {118, 30, 917, 1}, ++ {126, 34, 919, 1}, ++ {151, 38, 921, 1}, ++ {159, 42, 923, 1}, ++ {167, 46, 925, 1}, ++ {175, 50, 927, 1}, ++ /* 4 Mhz */ ++ {122, 32, 918, 2}, ++ {155, 40, 922, 2}, ++ {171, 48, 926, 2}, ++ /* 8 Mhz */ ++ {163, 44, 924, 3} ++ } ++}; ++ ++static const country_channel_map_t nz_channel_map = { ++ .country = "NZ", ++ .num_mapped_channels = 23, ++ .ah_vals = { ++ /* 1Mhz */ ++ {112, 27, 915.5, 0}, ++ {116, 29, 916.5, 0}, ++ {120, 31, 917.5, 0}, ++ {124, 33, 918.5, 0}, ++ {128, 35, 919.5, 0}, ++ {149, 37, 920.5, 0}, ++ {153, 39, 921.5, 0}, ++ {157, 41, 922.5, 0}, ++ {161, 43, 923.5, 0}, ++ {165, 45, 924.5, 0}, ++ {169, 47, 925.5, 0}, ++ {173, 49, 926.5, 0}, ++ {177, 51, 927.5, 0}, ++ /* 2Mhz */ ++ {118, 30, 917, 1}, ++ {126, 34, 919, 1}, ++ {151, 38, 921, 1}, ++ {159, 42, 923, 1}, ++ {167, 46, 925, 1}, ++ {175, 50, 927, 1}, ++ /* 4 Mhz */ ++ {122, 32, 918, 2}, ++ {155, 40, 922, 2}, ++ {171, 48, 926, 2}, ++ /* 8 Mhz */ ++ {163, 44, 924, 3} ++ } ++}; ++ ++static const country_channel_map_t eu_channel_map = { ++ .country = "EU", ++ .num_mapped_channels = 8, ++ .ah_vals = { ++ /* 1Mhz */ ++ {132, 1, 863.5, 0}, ++ {136, 3, 864.5, 0}, ++ {36, 5, 865.5, 0}, ++ {40, 7, 866.5, 0}, ++ {44, 9, 867.5, 0}, ++ {120, 31, 916.9, 0}, ++ {124, 33, 917.9, 0}, ++ {128, 35, 918.9, 0}, ++ } ++}; ++ ++static const country_channel_map_t in_channel_map = { ++ .country = "IN", ++ .num_mapped_channels = 3, ++ .ah_vals = { ++ /* 1Mhz */ ++ {36, 5, 865.5, 0}, ++ {40, 7, 866.5, 0}, ++ {44, 9, 867.5, 0}, ++ } ++}; ++ ++static const country_channel_map_t jp_channel_map = { ++ .country = "JP", ++ .num_mapped_channels = 11, ++ .ah_vals = { ++ /* 1 Mhz */ ++ {36, 13, 923, 0}, ++ {40, 15, 924, 0}, ++ {44, 17, 925, 0}, ++ {48, 19, 926, 0}, ++ {64, 21, 927, 0}, ++ /* 2Mhz */ ++ {38, 2, 923.5, 1}, ++ {46, 6, 925.5, 1}, ++ {54, 4, 924.5, 1}, ++ {62, 8, 926.5, 1}, ++ /* 4Mhz */ ++ {42, 36, 924.5, 2}, ++ {58, 38, 925.5, 2}, ++ } ++}; ++ ++static const country_channel_map_t kr_channel_map = { ++ .country = "KR", ++ .num_mapped_channels = 10, ++ .ah_vals = { ++ /* 1 Mhz */ ++ {132, 1, 918, 0}, ++ {136, 3, 919, 0}, ++ {36, 5, 920, 0}, ++ {40, 7, 921, 0}, ++ {44, 9, 922, 0}, ++ {48, 11, 923, 0}, ++ /* 2Mhz */ ++ {134, 2, 918.5, 1}, ++ {38, 6, 920.5, 1}, ++ {46, 10, 922.5, 1}, ++ /* 4Mhz */ ++ {42, 8, 921.5, 2}, ++ } ++}; ++ ++static const country_channel_map_t sg_channel_map = { ++ .country = "SG", ++ .num_mapped_channels = 12, ++ .ah_vals = { ++ /* 1 Mhz */ ++ {40, 7, 866.5, 0}, ++ {44, 9, 867.5, 0}, ++ {48, 11, 868.5, 0}, ++ {149, 37, 920.5, 0}, ++ {153, 39, 921.5, 0}, ++ {157, 41, 922.5, 0}, ++ {161, 43, 923.5, 0}, ++ {165, 45, 924.5, 0}, ++ /* 2Mhz */ ++ {46, 10, 868, 1}, ++ {151, 38, 921, 1}, ++ {159, 42, 923, 1}, ++ /* 4Mhz */ ++ {155, 40, 922, 2} ++ } ++}; ++ ++static const country_channel_map_t channel_map_terminate = { ++ .country = {0,0,0}, ++ .num_mapped_channels = 0, ++ .ah_vals = {} ++}; ++ ++static const country_channel_map_t *mapped_channel[] = { ++ &us_channel_map, ++ &au_channel_map, ++ &nz_channel_map, ++ &eu_channel_map, ++ &in_channel_map, ++ &jp_channel_map, ++ &kr_channel_map, ++ &sg_channel_map, ++ &channel_map_terminate ++}; ++ ++#define CHANNEL_MAP_SIZE (sizeof(mapped_channel) / sizeof(*mapped_channel)) ++ ++static void morse_get_country(country_channel_map_t *halow_vals) ++{ ++ FILE *country_parameter; ++ ++ country_parameter = fopen("/sys/module/morse/parameters/country", "r"); ++ fscanf(country_parameter, "%2s", halow_vals->country); ++ fclose(country_parameter); ++} ++ ++country_channel_map_t *set_s1g_channel_map(void) ++{ ++ country_channel_map_t halow_vals; ++ ++ morse_get_country(&halow_vals); ++ if (strlen(halow_vals.country) != 0) ++ { ++ for (int i = 0; i < CHANNEL_MAP_SIZE; i++) ++ { ++ if (!strncmp(halow_vals.country, mapped_channel[i]->country, strlen(mapped_channel[i]->country))) ++ { ++ return mapped_channel[i]; ++ } ++ } ++ } ++ ++ return NULL; ++} ++ ++ ++channel_to_halow_freq_t *get_s1g(country_channel_map_t *map, int channel) ++{ ++ if(map == NULL) ++ return &kNullAhValue; ++ ++ for(int i=0;inum_mapped_channels;i++) ++ { ++ if(map->ah_vals[i].channel==channel) ++ return &map->ah_vals[i]; ++ } ++ return &kNullAhValue; ++} ++ ++float get_freq(country_channel_map_t *map, int channel) ++{ ++ if(map == NULL) ++ return 0; ++ ++ for(int i=0; i< map->num_mapped_channels; i++) ++ { ++ if(map->ah_vals[i].halow_channel==channel) ++ return map->ah_vals[i].halow_freq; ++ } ++ ++ return 0; ++ ++} ++ ++int s1g_rate(int fiveG_rate, int frq_mhz) ++{ ++ int sc_map_5g[][2] = { ++ {20 , 52}, ++ {40 , 108}, ++ {80 , 234}, ++ {160 , 468}}; ++ int sc_map_s1g[][2] = { ++ {20 , 24}, ++ {40 , 52}, ++ {80 , 108}, ++ {160 , 234}}; ++ ++ int index=-1; ++ for (int i = 0; i < sizeof(sc_map_5g) / sizeof(int[2]); i++) ++ { ++ if(sc_map_5g[i][0] == frq_mhz) ++ { ++ index = i; ++ break; ++ } ++ } ++ int scale = 20; // for s1g we need to scale the reported shim layer values. if not exist approximate. ++ if (index != -1) ++ { ++ scale = 10 * sc_map_5g[index][1] / sc_map_s1g[index][1]; ++ } ++ return fiveG_rate / scale; ++} ++ ++int s1g_freq2channel(country_channel_map_t *map,int freq)//frq in khz ++{ ++ if(map == NULL) ++ return 0; ++ ++ for(int i=0; i< map->num_mapped_channels; i++) ++ { ++ if ((int)(map->ah_vals[i].halow_freq * 1000) == freq) ++ return map->ah_vals[i].halow_channel; ++ } ++ ++ return 0; ++} ++ ++int s1g_chan2bw(country_channel_map_t *map,int channel)//bw in MHz ++{ ++ if(map == NULL) ++ return 0; ++ ++ for(int i=0; i< map->num_mapped_channels; i++) ++ { ++ if ((map->ah_vals[i].halow_channel) == channel) ++ return map->ah_vals[i].bw; ++ } ++ ++ return 0; ++} ++ ++const country_channel_map_t** s1g_mapped_channel() ++{ ++ return mapped_channel; ++} ++ ++void s1g_get_country(char *buf) ++{ ++ country_channel_map_t halow_vals; ++ morse_get_country(&halow_vals); ++ memcpy(buf,halow_vals.country,2); ++} ++ ++ ++// returns true if this raw is the selected rate. ++int mmrc_table_active_raw(const char* line) ++{ ++ //check if it has MHz and MCS and GI keywords. ++ if (strstr(line, "MCS") == NULL) ++ return 0; ++ if (strstr(line, "MHz") == NULL) ++ return 0; ++ if ((strstr(line, "SGI") == NULL) && (strstr(line, "LGI") == NULL)) ++ return 0; ++ //start search from 17th character. ++ if (strstr(line+17,"A")) ++ return 1; ++return 0; ++ ++} ++//returns the avg tp from the selected line. ++int get_mmrc_table_raw_throughput_avg(const char* line) ++{ ++ float tp_avg; ++ sscanf(line + 55, "%f", &tp_avg); ++ return tp_avg*1000; ++} ++ ++int get_mmrc_throughput(const char* phyname) ++{ ++ FILE *file; ++ char * line = NULL; ++ size_t len = 0; ++ ssize_t read; ++ char table_path[64]; ++ int rate_kbps=-1; ++ ++ sprintf (table_path,"/sys/kernel/debug/ieee80211/%s/morse/mmrc_table",phyname); ++ file = fopen(table_path, "r"); ++ if (file == NULL) ++ { ++ return -1; ++ } ++ ++ while ((read = getline(&line, &len, file)) != -1) { ++ if(mmrc_table_active_raw(line)) ++ { ++ rate_kbps = get_mmrc_table_raw_throughput_avg(line); ++ break; ++ } ++ } ++ fclose(file); ++ if (line) ++ free(line); ++ ++ if(rate_kbps == 0) ++ rate_kbps+=1; //to make sure that assoc list doesn't show "unknown" when there's no traffic. ++ return rate_kbps; ++} +\ No newline at end of file +diff --git a/dot11ah_channel.h b/dot11ah_channel.h +new file mode 100644 +index 0000000..676906d +--- /dev/null ++++ b/dot11ah_channel.h +@@ -0,0 +1,105 @@ ++/* ++ * Copyright 2022 Morse Micro ++ * ++ * The iwinfo library is free software: you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License version 2 ++ * as published by the Free Software Foundation. ++ * ++ * The iwinfo library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ * See the GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License along ++ * with the iwinfo library. If not, see http://www.gnu.org/licenses/. ++*/ ++ ++#ifndef __DOT11AH_CHANNELS__ ++#define __DOT11AH_CHANNELS__ ++ ++typedef struct { ++ /*5G channel*/ ++ int channel; ++ /*80211ah channel*/ ++ int halow_channel; ++ /*80211ah freq*/ ++ float halow_freq; ++ /*80211ah bandwidth*/ ++ int bw; ++} channel_to_halow_freq_t; ++ ++typedef struct { ++ char country[3]; ++ int num_mapped_channels; ++ channel_to_halow_freq_t ah_vals[]; ++} country_channel_map_t; ++ ++ ++/** ++ * @brief gets the channel 5g to s1g channel map for a given region. ++ * country setting is read from /sys/module/morse/parameters/country ++ */ ++country_channel_map_t *set_s1g_channel_map(void); ++ ++ ++/** ++ * retrieves the map entry for a given 5g channel ++ * @param map a map of 5g to s1g channels retrieved by set_s1g_channel_map ++ * @param channel a 5g channel value ++ * @return The entry in the map corresponding to the given 5g channel and configured region ++ */ ++channel_to_halow_freq_t *get_s1g(country_channel_map_t *map, int channel); ++ ++/** ++ * retrieves the s1g frequency for a given s1g channel ++ * @param map a map of 5g to s1g channels retrieved by set_s1g_channel_map ++ * @param channel an s1g channel value ++ * @return a floating point value corresponding to the s1g frequency, in mhz, for the given channel. ++ */ ++float get_freq(country_channel_map_t *map, int channel); ++ ++/** ++ * a conversion helper for calculating the correct s1g mcs data rate for a given bandwidth and existing 5g mcs rate ++ * @param fiveG_rate a 5g mcs data rate value ++ * @param frq_mhz the 5g bandwidth corresponding to the 5g rate ++ * @return the s1g mcs data rate ++ */ ++int s1g_rate(int fiveG_rate, int frq_mhz); ++ ++/** ++ * converts s1g frequency to s1g channel ++ * @param map a map of 5g to s1g channels retrieved by set_s1g_channel_map ++ * @param freq s1g frequency in KHz ++ * @return s1g channel ++ */ ++int s1g_freq2channel(country_channel_map_t *map,int freq); ++ ++/** ++ * converts s1g channel to s1g bandwidth ++ * @param map a map of 5g to s1g channels retrieved by set_s1g_channel_map ++ * @param channel s1g channel ++ * @return s1g channel ++ */ ++int s1g_chan2bw(country_channel_map_t *map,int channel); ++ ++/** ++ * gives a pointer to the country separated channels map ++ * @return a pointer to the country separated channels map. ++ */ ++const country_channel_map_t** s1g_mapped_channel(); ++ ++ ++/** ++ * returns the currently set country. ++ * @param buf a pointer to the result buffer. ++ */ ++void s1g_get_country(char *buf); ++ ++/** ++ * gets mmrc average throughput from the mmrc_table. ++ * @param phyname phyname of the halow device. ++ * @return average throughput in kbps ++ */ ++int get_mmrc_throughput(const char* phyname); ++ ++#endif /* __DOT11AH_CHANNELS__ */ +diff --git a/include/iwinfo.h b/include/iwinfo.h +index b50de69..0a9f157 100644 +--- a/include/iwinfo.h ++++ b/include/iwinfo.h +@@ -31,6 +31,7 @@ enum iwinfo_80211 { + IWINFO_80211_AC, + IWINFO_80211_AD, + IWINFO_80211_AX, ++ IWINFO_80211_AH, + + /* keep last */ + IWINFO_80211_COUNT +@@ -43,6 +44,9 @@ enum iwinfo_80211 { + #define IWINFO_80211_AC (1 << IWINFO_80211_AC) + #define IWINFO_80211_AD (1 << IWINFO_80211_AD) + #define IWINFO_80211_AX (1 << IWINFO_80211_AX) ++#define IWINFO_80211_AH (1 << IWINFO_80211_AH) ++ ++#define S1G_CHAN_WIDTH_OFFSET (2) + + extern const char * const IWINFO_80211_NAMES[IWINFO_80211_COUNT]; + +@@ -52,7 +56,7 @@ enum iwinfo_band { + IWINFO_BAND_5, + IWINFO_BAND_6, + IWINFO_BAND_60, +- ++ IWINFO_BAND_900, + /* keep last */ + IWINFO_BAND_COUNT + }; +@@ -61,6 +65,7 @@ enum iwinfo_band { + #define IWINFO_BAND_5 (1 << IWINFO_BAND_5) + #define IWINFO_BAND_6 (1 << IWINFO_BAND_6) + #define IWINFO_BAND_60 (1 << IWINFO_BAND_60) ++#define IWINFO_BAND_900 (1 << IWINFO_BAND_900) + + extern const char * const IWINFO_BAND_NAMES[IWINFO_BAND_COUNT]; + +@@ -290,6 +295,15 @@ struct iwinfo_crypto_entry { + uint16_t pair_ciphers; + uint8_t auth_suites; + uint8_t auth_algs; ++ ++ /* RSNXE data */ ++ uint8_t prot_twt:1; ++ uint8_t sae_h2e:1; ++ uint8_t sae_pk:1; ++ uint8_t secure_ltf:1; ++ uint8_t secure_rtt:1; ++ uint8_t prot_range_neg:1; ++ uint8_t pad0:2; + }; + + struct iwinfo_scanlist_ht_chan_entry { +@@ -304,6 +318,19 @@ struct iwinfo_scanlist_vht_chan_entry { + uint8_t center_chan_2; + }; + ++struct iwinfo_scanlist_ah_chan_entry { ++ uint8_t primary_chan; ++ uint8_t chan_width; ++}; ++ ++static uint16_t ah_chan_width[] = { ++ 1, /* 1 MHz*/ ++ 2, /* 2 MHz*/ ++ 4, /* 4 MHz*/ ++ 8, /* 8 MHz*/ ++ 16, /* 16 MHz*/ ++}; ++ + extern const char * const ht_secondary_offset[4]; + /* 0 = 20 MHz + 1 = 40 MHz or higher (refer to vht if supported) */ +@@ -327,6 +354,7 @@ struct iwinfo_scanlist_entry { + struct iwinfo_crypto_entry crypto; + struct iwinfo_scanlist_ht_chan_entry ht_chan_info; + struct iwinfo_scanlist_vht_chan_entry vht_chan_info; ++ struct iwinfo_scanlist_ah_chan_entry ah_chan_info; + }; + + struct iwinfo_country_entry { +@@ -412,6 +440,7 @@ extern const struct iwinfo_ops wext_ops; + extern const struct iwinfo_ops madwifi_ops; + extern const struct iwinfo_ops nl80211_ops; + extern const struct iwinfo_ops wl_ops; ++extern const struct iwinfo_ops dot11ah_ops; + + #include "iwinfo/utils.h" + +diff --git a/include/iwinfo/utils.h b/include/iwinfo/utils.h +index 7b8ceea..d2a2788 100644 +--- a/include/iwinfo/utils.h ++++ b/include/iwinfo/utils.h +@@ -66,6 +66,7 @@ int iwinfo_hardware_id_from_mtd(struct iwinfo_hardware_id *id); + + void iwinfo_parse_rsn(struct iwinfo_crypto_entry *c, uint8_t *data, uint8_t len, + uint16_t defcipher, uint8_t defauth); ++void iwinfo_parse_rsnxe(struct iwinfo_crypto_entry *c, uint8_t *data, uint8_t len); + + struct uci_section *iwinfo_uci_get_radio(const char *name, const char *type); + void iwinfo_uci_free(void); +diff --git a/iwinfo_cli.c b/iwinfo_cli.c +index 5dcee9a..790803c 100644 +--- a/iwinfo_cli.c ++++ b/iwinfo_cli.c +@@ -69,12 +69,12 @@ static char * format_channel(int ch) + + static char * format_frequency(int freq) + { +- static char buf[11]; ++ static char buf[15]; + + if (freq <= 0) + snprintf(buf, sizeof(buf), "unknown"); + else +- snprintf(buf, sizeof(buf), "%.3f GHz", ((float)freq / 1000.0)); ++ snprintf(buf, sizeof(buf), "%.3f %s", ((float)freq / 1000.0), freq > 500000 ? "MHz" : "GHz"); + + return buf; + } +@@ -351,6 +351,19 @@ static const char* format_chan_width(bool vht, uint8_t width) + return "unknown"; + } + ++static const char* format_ah_chan_width(uint8_t width) ++{ ++ if (width < ARRAY_SIZE(ah_chan_width)) ++ switch (ah_chan_width[width]) { ++ case 1: return "1 MHz"; ++ case 2: return "2 MHz"; ++ case 4: return "4 MHz"; ++ case 8: return "8 MHz"; ++ case 16: return "16 MHz"; ++ } ++ ++ return "unknown"; ++} + + static const char * print_type(const struct iwinfo_ops *iw, const char *ifname) + { +@@ -689,13 +702,16 @@ static void print_scanlist(const struct iwinfo_ops *iw, const char *ifname) + format_quality_max(e->quality_max)); + printf(" Encryption: %s\n", + format_encryption(&e->crypto)); +- printf(" HT Operation:\n"); +- printf(" Primary Channel: %d\n", +- e->ht_chan_info.primary_chan); +- printf(" Secondary Channel Offset: %s\n", +- ht_secondary_offset[e->ht_chan_info.secondary_chan_off]); +- printf(" Channel Width: %s\n", +- format_chan_width(false, e->ht_chan_info.chan_width)); ++ ++ if (e->ht_chan_info.primary_chan) { ++ printf(" HT Operation:\n"); ++ printf(" Primary Channel: %d\n", ++ e->ht_chan_info.primary_chan); ++ printf(" Secondary Channel Offset: %s\n", ++ ht_secondary_offset[e->ht_chan_info.secondary_chan_off]); ++ printf(" Channel Width: %s\n", ++ format_chan_width(false, e->ht_chan_info.chan_width)); ++ } + + if (e->vht_chan_info.center_chan_1) { + printf(" VHT Operation:\n"); +@@ -707,6 +723,14 @@ static void print_scanlist(const struct iwinfo_ops *iw, const char *ifname) + format_chan_width(true, e->vht_chan_info.chan_width)); + } + ++ if (e->ah_chan_info.primary_chan) { ++ printf(" AH Operation:\n"); ++ printf(" Channel Width: %s\n", ++ format_ah_chan_width(e->ah_chan_info.chan_width)); ++ printf(" Primary Channel: %d\n", ++ e->ah_chan_info.primary_chan); ++ } ++ + printf("\n"); + } + } +@@ -912,7 +936,7 @@ int main(int argc, char **argv) + char *p; + const struct iwinfo_ops *iw; + glob_t globbuf; +- ++ + if (argc > 1 && argc < 3) + { + fprintf(stderr, +@@ -1031,7 +1055,6 @@ int main(int argc, char **argv) + } + } + } +- + iwinfo_finish(); + + return rv; +diff --git a/iwinfo_lib.c b/iwinfo_lib.c +index 579efc4..02fbd9c 100644 +--- a/iwinfo_lib.c ++++ b/iwinfo_lib.c +@@ -30,6 +30,7 @@ const char * const IWINFO_80211_NAMES[IWINFO_80211_COUNT] = { + "ac", + "ad", + "ax", ++ "ah", + }; + + const char * const IWINFO_BAND_NAMES[IWINFO_BAND_COUNT] = { +@@ -37,6 +38,7 @@ const char * const IWINFO_BAND_NAMES[IWINFO_BAND_COUNT] = { + "5 GHz", + "6 GHz", + "60 GHz", ++ "900 MHz", + }; + + const char * const IWINFO_CIPHER_NAMES[IWINFO_CIPHER_COUNT] = { +@@ -383,6 +385,7 @@ const struct iwinfo_iso3166_label IWINFO_ISO3166_NAMES[] = { + + static const struct iwinfo_ops *backends[] = { + #ifdef USE_NL80211 ++ &dot11ah_ops, + &nl80211_ops, + #endif + #ifdef USE_MADWIFI +diff --git a/iwinfo_lua.c b/iwinfo_lua.c +index ecf257d..9def386 100644 +--- a/iwinfo_lua.c ++++ b/iwinfo_lua.c +@@ -554,6 +554,9 @@ static int iwinfo_L_hwmodelist(lua_State *L, int (*func)(const char *, int *)) + lua_pushboolean(L, hwmodes & IWINFO_80211_AX); + lua_setfield(L, -2, "ax"); + ++ lua_pushboolean(L, hwmodes & IWINFO_80211_AH); ++ lua_setfield(L, -2, "ah"); ++ + return 1; + } + +diff --git a/iwinfo_morsecli.c b/iwinfo_morsecli.c +new file mode 100644 +index 0000000..7156bd8 +--- /dev/null ++++ b/iwinfo_morsecli.c +@@ -0,0 +1,122 @@ ++/* ++ * iwinfo - Wireless Information Library - morsecli interface ++ * ++ * Copyright (C) 2023 Morse Micro ++ * ++ * The iwinfo library is free software: you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License version 2 ++ * as published by the Free Software Foundation. ++ * ++ * The iwinfo library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ * See the GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License along ++ * with the iwinfo library. If not, see http://www.gnu.org/licenses/. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++ ++/* popen using exec (rather than like system()). ++ * ++ * Since I have an irrational dislike of using /bin/sh unnecessarily, ++ * and we're potentially calling this quite a lot :( ++ */ ++static FILE *execvp_popen_read(const char *file, const char *const argv[]) ++{ ++ int pipefd[2]; ++ ++ if (pipe(pipefd)) return NULL; ++ ++ switch (fork()) ++ { ++ case 0: /* child */ ++ close(pipefd[0]); ++ dup2(pipefd[1], STDOUT_FILENO); ++ freopen("/dev/null", "w", stderr); /* throw away stderr, to avoid noise in output */ ++ execvp(file, (char * const*)argv); ++ exit(1); ++ break; ++ default: /* parent */ ++ close(pipefd[1]); ++ return fdopen(pipefd[0], "r"); ++ case -1: ++ close(pipefd[1]); ++ return NULL; ++ } ++} ++ ++/* Query particular stats from morse_cli. ++ * ++ * Style of function echos nl80211_hostapd_query. ++ * ++ * This is the YAGNI version of this, since hopefully ++ * it will be replaced by netlink queries in time. ++ * ++ * I don't use morse_cli's filter, since it's hard to construct ++ * a regex covering all the possible options (and filter ++ * doesn't do anything smart and still queries all the stats, ++ * so we may as well not use the regex engine at all). ++ * ++ * Notably: ++ * - uses the plain text output rather than JSON ++ * (which means it can't handle nested stats) ++ * - only handles integer stats ++ */ ++int __morse_cli_stats_query(const char *ifname, ...) ++{ ++ va_list ap, ap_cur; ++ int *dest; ++ char *search, *key, *val, buf[128]; ++ int found = 0; ++ FILE *fp; ++ /* For now, the only stat we want is noise, ++ * so restrict to PHY core. ++ */ ++ const char *const argv[] = {"morse_cli", "-i", ifname, "stats", "-u", NULL}; ++ ++ fp = execvp_popen_read("morse_cli", argv); ++ ++ if (!fp) ++ return 0; ++ ++ va_start(ap, ifname); ++ ++ /* iterate applicable lines and copy found values into dest buffers */ ++ while (fgets(buf, sizeof(buf), fp)) ++ { ++ key = strtok(buf, ":\n"); ++ val = strtok(NULL, "\n"); ++ ++ if (!key || !val || !*key) ++ continue; ++ ++ va_copy(ap_cur, ap); ++ ++ while ((search = va_arg(ap_cur, char *)) != NULL) ++ { ++ dest = va_arg(ap_cur, int *); ++ ++ if (!strcmp(search, key)) ++ { ++ *dest = atoi(val); ++ found++; ++ break; ++ } ++ } ++ ++ va_end(ap_cur); ++ } ++ ++ va_end(ap); ++ ++ fclose(fp); ++ ++ return found; ++} +\ No newline at end of file +diff --git a/iwinfo_morsecli.h b/iwinfo_morsecli.h +new file mode 100644 +index 0000000..320f433 +--- /dev/null ++++ b/iwinfo_morsecli.h +@@ -0,0 +1,27 @@ ++/* ++ * iwinfo - Wireless Information Library - morsecli headers ++ * ++ * Copyright (C) 2023 Morse Micro ++ * ++ * The iwinfo library is free software: you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License version 2 ++ * as published by the Free Software Foundation. ++ * ++ * The iwinfo library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ * See the GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License along ++ * with the iwinfo library. If not, see http://www.gnu.org/licenses/. ++ */ ++ ++#ifndef __IWINFO_MORSECLI_H_ ++#define __IWINFO_MORSECLI_H_ ++ ++#define morse_cli_stats_query(ifname, ...) \ ++ __morse_cli_stats_query(ifname, ##__VA_ARGS__, NULL) ++ ++int __morse_cli_stats_query(const char *ifname, ...); ++ ++#endif +\ No newline at end of file +diff --git a/iwinfo_nl80211.c b/iwinfo_nl80211.c +index 2200249..dcb29b3 100644 +--- a/iwinfo_nl80211.c ++++ b/iwinfo_nl80211.c +@@ -2,6 +2,7 @@ + * iwinfo - Wireless Information Library - NL80211 Backend + * + * Copyright (C) 2010-2013 Jo-Philipp Wich ++ * Copyright 2022 Morse Micro + * + * The iwinfo library is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 +@@ -30,6 +31,7 @@ + #include + + #include "iwinfo_nl80211.h" ++#include "iwinfo_morsecli.h" + + #define min(x, y) ((x) < (y)) ? (x) : (y) + +@@ -408,8 +410,11 @@ static int nl80211_phy_idx_from_uci(const char *name) + int idx = -1; + + s = iwinfo_uci_get_radio(name, "mac80211"); +- if (!s) +- goto out; ++ if (!s){ ++ s = iwinfo_uci_get_radio(name, "morse"); ++ if(!s) ++ goto out; ++ } + + opt = uci_lookup_option_string(uci_ctx, s, "path"); + idx = nl80211_phy_idx_from_path(opt); +@@ -1331,15 +1336,16 @@ static int nl80211_get_bssid(const char *ifname, char *buf) + + res = nl80211_phy2ifname(ifname); + +- /* try to obtain mac address via NL80211_CMD_GET_INTERFACE */ +- nl80211_request(res ? res : ifname, NL80211_CMD_GET_INTERFACE, 0, +- nl80211_get_macaddr_cb, &sb); ++ /* try to find bssid from scan dump results (for AP, this fails ++ * and it goes to NL80211_CMD_GET_INTERFACE)*/ ++ nl80211_request(res ? res : ifname, ++ NL80211_CMD_GET_SCAN, NLM_F_DUMP, ++ nl80211_get_ssid_bssid_cb, &sb); + +- /* failed, try to find bssid from scan dump results */ ++ /* failed, try to obtain mac address via NL80211_CMD_GET_INTERFACE */ + if (sb.bssid[0] == 0) +- nl80211_request(res ? res : ifname, +- NL80211_CMD_GET_SCAN, NLM_F_DUMP, +- nl80211_get_ssid_bssid_cb, &sb); ++ nl80211_request(res ? res : ifname, NL80211_CMD_GET_INTERFACE, 0, ++ nl80211_get_macaddr_cb, &sb); + + /* failed, try to find mac from hostapd info */ + if ((sb.bssid[0] == 0) && +@@ -1771,7 +1777,8 @@ static const struct { + { "EAP-SUITE-B-192", 4, IWINFO_KMGMT_8021x }, + { "EAP-SUITE-B", 4, IWINFO_KMGMT_8021x }, + { "EAP-SHA384", 4, IWINFO_KMGMT_8021x }, +- { "EAP-SHA256", 0, IWINFO_KMGMT_8021x }, ++ /* SHA256 counts as WPA3 as long as pmf is enabled; we check this below. */ ++ { "EAP-SHA256", 4, IWINFO_KMGMT_8021x }, + { "PSK-SHA256", 0, IWINFO_KMGMT_PSK }, + { "NONE", 0, IWINFO_KMGMT_NONE }, + { "None", 0, IWINFO_KMGMT_NONE }, +@@ -1782,7 +1789,7 @@ static const struct { + }; + + static void parse_wpa_suites(const char *str, int defversion, +- uint8_t *versions, uint8_t *suites) ++ uint8_t *versions, uint8_t *suites, int pmf) + { + size_t l; + int i, version; +@@ -1817,6 +1824,35 @@ static void parse_wpa_suites(const char *str, int defversion, + + p = q + strspn(q, sep); + } ++ ++ /* Handle ieee80211w/pmf (management frame protection). ++ * ++ * Strictly: ++ * ieee80211w=2 && wpa_key_mgmt=WPA-EAP-SHA256 ++ * => WPA3-Enterprise ++ * ieee80211w=1 && wpa_key_mgmt=WPA-EAP WPA-EAP-SHA256 ++ * => WPA3-Enterprise transition ++ * ++ * Here we just try to aggressively downgrade (i.e. if no pmf, ++ * not WPA3-Enterprise, and if not required then WPA2/WPA3). ++ * This _will_ allow some invalid configurations through ++ * and count certain undefined configurations as WPA2/WPA3 ++ * (e.g. WPA-EAP-SHA256 only and ieee80211=1). ++ */ ++ if ((*suites & IWINFO_KMGMT_8021x) && (*versions & 4)) ++ switch(pmf) ++ { ++ case 0: /* if disabled, it's not WPA3 */ ++ *versions &= ~4; ++ *versions |= defversion; ++ break; ++ case 1: /* if not required, not only WPA3 */ ++ *versions |= defversion; ++ break; ++ case 2: /* pmf required */ ++ default: /* if no pmf info - e.g. from scan */ ++ break; ++ } + } + + static const struct { +@@ -1871,6 +1907,7 @@ static int nl80211_get_encryption(const char *ifname, char *buf) + uint8_t wpa_version = 0; + char wpa[2], wpa_key_mgmt[64], wpa_pairwise[16], wpa_groupwise[16]; + char auth_algs[2], wep_key0[27], wep_key1[27], wep_key2[27], wep_key3[27]; ++ char ieee80211w[2], pmf[2]; + char mode[16]; + + struct iwinfo_crypto_entry *c = (struct iwinfo_crypto_entry *)buf; +@@ -1880,6 +1917,7 @@ static int nl80211_get_encryption(const char *ifname, char *buf) + "pairwise_cipher", wpa_pairwise, sizeof(wpa_pairwise), + "group_cipher", wpa_groupwise, sizeof(wpa_groupwise), + "key_mgmt", wpa_key_mgmt, sizeof(wpa_key_mgmt), ++ "pmf", pmf, sizeof(pmf), + "mode", mode, sizeof(mode))) + { + /* WEP or Open */ +@@ -1928,7 +1966,7 @@ static int nl80211_get_encryption(const char *ifname, char *buf) + wpa_version = 1; + } + +- parse_wpa_suites(p, wpa_version, &c->wpa_version, &c->auth_suites); ++ parse_wpa_suites(p, wpa_version, &c->wpa_version, &c->auth_suites, atoi(pmf)); + + c->enabled = !!(c->wpa_version && c->auth_suites); + } +@@ -1941,6 +1979,7 @@ static int nl80211_get_encryption(const char *ifname, char *buf) + "wpa", wpa, sizeof(wpa), + "wpa_key_mgmt", wpa_key_mgmt, sizeof(wpa_key_mgmt), + "wpa_pairwise", wpa_pairwise, sizeof(wpa_pairwise), ++ "ieee80211w", ieee80211w, sizeof(ieee80211w), + "auth_algs", auth_algs, sizeof(auth_algs), + "wep_key0", wep_key0, sizeof(wep_key0), + "wep_key1", wep_key1, sizeof(wep_key1), +@@ -1959,7 +1998,7 @@ static int nl80211_get_encryption(const char *ifname, char *buf) + if (!strncmp(p, "FT-", 3)) + p += 3; + +- parse_wpa_suites(p, atoi(wpa), &c->wpa_version, &c->auth_suites); ++ parse_wpa_suites(p, atoi(wpa), &c->wpa_version, &c->auth_suites, atoi(ieee80211w)); + } + + c->enabled = c->wpa_version ? 1 : 0; +@@ -2531,7 +2570,7 @@ static void nl80211_get_scancrypto(char *spec, struct iwinfo_crypto_entry *c) + + c->enabled = 1; + +- parse_wpa_suites(suites, wpa_version, &c->wpa_version, &c->auth_suites); ++ parse_wpa_suites(suites, wpa_version, &c->wpa_version, &c->auth_suites, -1); + parse_wpa_ciphers(suites, &c->pair_ciphers); + } + } +@@ -2588,6 +2627,9 @@ static void nl80211_get_scanlist_ie(struct nlattr **bss, + e->vht_chan_info.center_chan_2 = ie[4]; + } + break; ++ case 244: /* RSNXE */ ++ iwinfo_parse_rsnxe(&e->crypto, ie + 2, ie[1]); ++ break; + } + + ielen -= ie[1] + 2; +@@ -3634,7 +3676,7 @@ const struct iwinfo_ops nl80211_ops = { + .mbssid_support = nl80211_get_mbssid_support, + .hwmodelist = nl80211_get_hwmodelist, + .htmodelist = nl80211_get_htmodelist, +- .htmode = nl80211_get_htmode, ++ .htmode = nl80211_get_htmode, + .mode = nl80211_get_mode, + .ssid = nl80211_get_ssid, + .bssid = nl80211_get_bssid, +@@ -3653,3 +3695,723 @@ const struct iwinfo_ops nl80211_ops = { + .phy_path = nl80211_phy_path, + .close = nl80211_close + }; ++ ++ ++/* ++ * Morse Micro Shim Layer ++ * ++ */ ++ ++#include "dot11ah_channel.h" ++ ++country_channel_map_t *g_map = NULL; ++ ++static bool nl80211_is_halow(const char *ifname) ++{ ++ const struct iwinfo_hardware_entry *e = nl80211_get_hardware_entry(ifname); ++ if (!e) ++ return false; ++ ++ if (strcmp(e->device_name, "HaLow WiFi")) ++ return false; ++ ++ return true; ++} ++ ++static inline void _sanitise_rate_entry(struct iwinfo_rate_entry *re){ ++ if(re == NULL) ++ return; ++ re->rate = s1g_rate(re->rate, re->mhz); ++ re->mhz /= 20; ++ re->is_vht = 0; ++ re->is_ht = 1; ++} ++ ++ ++/* ++ * These fill_signal handlers need to be modified for s1g as they work per station. ++ * And it's more correct to handle the per station values in the per station handler ++ */ ++ ++static uint8_t nl80211_get_bandwidth(struct nlattr **ri, uint32_t size){ ++ if ((NL80211_RATE_INFO_5_MHZ_WIDTH < size) ++ && (ri[NL80211_RATE_INFO_5_MHZ_WIDTH])) ++ return 5; ++ else if ((NL80211_RATE_INFO_5_MHZ_WIDTH < size) ++ && (ri[NL80211_RATE_INFO_10_MHZ_WIDTH])) ++ return 10; ++ else if ((NL80211_RATE_INFO_5_MHZ_WIDTH < size) ++ && (ri[NL80211_RATE_INFO_40_MHZ_WIDTH])) ++ return 40; ++ else if ((NL80211_RATE_INFO_5_MHZ_WIDTH < size) ++ && (ri[NL80211_RATE_INFO_80_MHZ_WIDTH])) ++ return 80; ++ else if ((NL80211_RATE_INFO_5_MHZ_WIDTH < size) ++ && (ri[NL80211_RATE_INFO_80P80_MHZ_WIDTH] || ++ ri[NL80211_RATE_INFO_160_MHZ_WIDTH])) ++ return 160; ++ else ++ return 20; ++} ++ ++static int dot11ah_fill_signal_cb(struct nl_msg *msg, void *arg) ++{ ++ int8_t dbm; ++ int16_t mbit; ++ struct nl80211_rssi_rate *rr = arg; ++ struct nlattr **attr = nl80211_parse(msg); ++ struct nlattr *sinfo[NL80211_STA_INFO_MAX + 1]; ++ struct nlattr *rinfo[NL80211_RATE_INFO_MAX + 1]; ++ ++ static struct nla_policy stats_policy[NL80211_STA_INFO_MAX + 1] = { ++ [NL80211_STA_INFO_INACTIVE_TIME] = { .type = NLA_U32 }, ++ [NL80211_STA_INFO_RX_BYTES] = { .type = NLA_U32 }, ++ [NL80211_STA_INFO_TX_BYTES] = { .type = NLA_U32 }, ++ [NL80211_STA_INFO_RX_PACKETS] = { .type = NLA_U32 }, ++ [NL80211_STA_INFO_TX_PACKETS] = { .type = NLA_U32 }, ++ [NL80211_STA_INFO_SIGNAL] = { .type = NLA_U8 }, ++ [NL80211_STA_INFO_TX_BITRATE] = { .type = NLA_NESTED }, ++ [NL80211_STA_INFO_LLID] = { .type = NLA_U16 }, ++ [NL80211_STA_INFO_PLID] = { .type = NLA_U16 }, ++ [NL80211_STA_INFO_PLINK_STATE] = { .type = NLA_U8 }, ++ }; ++ ++ 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 }, ++ }; ++ ++ if (attr[NL80211_ATTR_STA_INFO]) ++ { ++ if (!nla_parse_nested(sinfo, NL80211_STA_INFO_MAX, ++ attr[NL80211_ATTR_STA_INFO], stats_policy)) ++ { ++ if (sinfo[NL80211_STA_INFO_SIGNAL]) ++ { ++ dbm = nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL]); ++ rr->rssi = (rr->rssi * rr->rssi_samples + dbm) / (rr->rssi_samples + 1); ++ rr->rssi_samples++; ++ } ++ ++ if (sinfo[NL80211_STA_INFO_TX_BITRATE]) ++ { ++ if (!nla_parse_nested(rinfo, NL80211_RATE_INFO_MAX, ++ sinfo[NL80211_STA_INFO_TX_BITRATE], ++ rate_policy)) ++ { ++ if (rinfo[NL80211_RATE_INFO_BITRATE]) ++ { ++ uint8_t mhz = nl80211_get_bandwidth(rinfo, NL80211_RATE_INFO_MAX); ++ mbit = nla_get_u16(rinfo[NL80211_RATE_INFO_BITRATE]); ++ mbit = s1g_rate(mbit, mhz); ++ rr->rate = (rr->rate * rr->rate_samples + mbit) / (rr->rate_samples + 1); ++ rr->rate_samples++; ++ } ++ } ++ } ++ } ++ } ++ ++ return NL_SKIP; ++} ++ ++static void dot11ah_fill_signal(const char *ifname, struct nl80211_rssi_rate *r) ++{ ++ DIR *d; ++ struct dirent *de; ++ ++ memset(r, 0, sizeof(*r)); ++ ++ if ((d = opendir("/sys/class/net")) != NULL) ++ { ++ while ((de = readdir(d)) != NULL) ++ { ++ if (!strncmp(de->d_name, ifname, strlen(ifname)) && ++ (!de->d_name[strlen(ifname)] || ++ !strncmp(&de->d_name[strlen(ifname)], ".sta", 4))) ++ { ++ nl80211_request(de->d_name, NL80211_CMD_GET_STATION, ++ NLM_F_DUMP, dot11ah_fill_signal_cb, r); ++ } ++ } ++ ++ closedir(d); ++ } ++} ++ ++static int dot11ah_probe(const char *ifname) ++{ ++ if (!nl80211_ifname2phy(ifname)) ++ return 0; ++ ++ if (!nl80211_is_halow(ifname)) ++ return 0; ++ ++ g_map = set_s1g_channel_map(); ++ return 1; ++} ++ ++static int dot11ah_get_center_chan2(const char *ifname, int *buf) ++{ ++ if(nl80211_get_center_chan2(ifname, buf) < 0) ++ return -1; ++ ++ channel_to_halow_freq_t *ch_entry = get_s1g(g_map, *buf); ++ if(ch_entry == NULL) ++ return -1; ++ ++ *buf = ch_entry->halow_channel; ++ ++ return 0; ++} ++ ++static int dot11ah_get_center_chan1(const char *ifname, int *buf) ++{ ++ if(nl80211_get_center_chan1(ifname, buf) < 0) ++ return -1; ++ ++ channel_to_halow_freq_t *ch_entry = get_s1g(g_map, *buf); ++ if(ch_entry == NULL) ++ return -1; ++ ++ *buf = ch_entry->halow_channel; ++ ++ return 0; ++ ++} ++ ++static int dot11ah_get_channel(const char *ifname, int *buf) ++{ ++ if(dot11ah_get_center_chan1(ifname, buf) == 0) ++ return 0; ++ ++ if(nl80211_get_channel(ifname, buf) < 0) ++ return -1; ++ ++ channel_to_halow_freq_t *ch_entry = get_s1g(g_map, *buf); ++ if(ch_entry == NULL) ++ return -1; ++ ++ *buf = ch_entry->halow_channel; ++ ++ return 0; ++ ++} ++ ++static int dot11ah_get_frequency(const char *ifname, int *buf) ++{ ++ *buf = 0; ++ ++ if (dot11ah_get_center_chan1(ifname, buf) < 0) ++ if (dot11ah_get_channel(ifname, buf) < 0) ++ return -1; ++ ++ *buf = (int) (get_freq(g_map, *buf)*1000); ++ ++ return (*buf == 0) ? -1 : 0; ++} ++ ++static int dot11ah_get_bitrate(const char *ifname, int *buf) ++{ ++ struct nl80211_rssi_rate rr; ++ ++ dot11ah_fill_signal(ifname, &rr); ++ ++ if (rr.rate_samples) ++ { ++ *buf = (rr.rate * 100); ++ return 0; ++ } ++ ++ return -1; ++} ++ ++static int dot11ah_get_hwmodelist(const char *ifname, int *buf) ++{ ++ *buf = IWINFO_80211_AH; ++ return 0; ++} ++ ++static int dot11ah_get_htmodelist(const char *ifname, int *buf) ++{ ++ *buf = IWINFO_HTMODE_NOHT; ++ return 0; ++} ++ ++static int dot11ah_get_htmode(const char *ifname, int *buf) ++{ ++ int chan; ++ dot11ah_get_channel(ifname, &chan); ++ if(!g_map || !chan) ++ return 0; ++ *buf = s1g_chan2bw(g_map, chan); ++ return 0; ++} ++ ++static int dot11ah_get_country(const char *ifname, char *buf) ++{ ++ s1g_get_country(buf); ++ return 0; ++} ++ ++static int dot11ah_get_noise(const char *ifname, int *buf) ++{ ++ if (!morse_cli_stats_query(ifname, "Noise dBm", buf)) ++ return -1; ++ else ++ return 0; ++} ++ ++static int dot11ah_get_assoclist(const char *ifname, char *buf, int *len) ++{ ++ struct iwinfo_assoclist_entry *ae; ++ int noise = 0; ++ ++ if (nl80211_get_assoclist(ifname, buf, len) < 0) ++ return -1; ++ ++ dot11ah_get_noise(ifname, &noise); ++ ++ for (char *p = buf; p < (buf + *len); p += sizeof(struct iwinfo_assoclist_entry)) ++ { ++ ae = (struct iwinfo_assoclist_entry *) p; ++ ae->noise = noise; ++ _sanitise_rate_entry(&ae->rx_rate); ++ _sanitise_rate_entry(&ae->tx_rate); ++ } ++ ++ return 0; ++} ++ ++static int dot11ah_get_scanlist(const char *ifname, char *buf, int *len) ++{ ++ struct iwinfo_scanlist_entry *se; ++ channel_to_halow_freq_t *ch_entry, *prim_chan; ++ if(nl80211_get_scanlist(ifname, buf, len) < 0) ++ return -1; ++ ++ for(char *p = buf; p < (buf + *len); p += sizeof(struct iwinfo_scanlist_entry)){ ++ se = (struct iwinfo_scanlist_entry *) p; ++ ++ ch_entry = get_s1g(g_map, se->channel); ++ prim_chan = get_s1g(g_map, se->ht_chan_info.primary_chan); ++ se->channel = ch_entry->halow_channel; ++ se->band = IWINFO_BAND_900; ++ se->mhz = get_freq(g_map, se->channel)*1000; ++ ++ if (se->vht_chan_info.center_chan_1) ++ { ++ ch_entry = get_s1g(g_map, se->vht_chan_info.center_chan_1); ++ se->channel = ch_entry->halow_channel; ++ se->vht_chan_info.center_chan_1 = 0; ++ }else if(se->ht_chan_info.secondary_chan_off == 1) ++ { ++ se->channel += 1; ++ }else if(se->ht_chan_info.secondary_chan_off == 3) ++ { ++ se->channel -= 1; ++ } ++ se->ht_chan_info.secondary_chan_off=0; ++ se->ht_chan_info.primary_chan=0; ++ se->ah_chan_info.primary_chan=prim_chan->halow_channel; ++ se->ah_chan_info.chan_width=s1g_chan2bw(g_map, se->channel); ++ ++ se->crypto.wpa_version |= 4; ++ if(se->crypto.sae_h2e == 1) ++ se->crypto.auth_suites |= IWINFO_KMGMT_SAE; ++ else ++ se->crypto.auth_suites |= IWINFO_KMGMT_OWE; ++ se->crypto.group_ciphers |= IWINFO_CIPHER_CCMP; ++ se->crypto.group_ciphers &= ~(IWINFO_CIPHER_WEP40 | IWINFO_CIPHER_WEP104); ++ se->crypto.pair_ciphers |= IWINFO_CIPHER_CCMP; ++ se->crypto.pair_ciphers &= ~(IWINFO_CIPHER_WEP40 | IWINFO_CIPHER_WEP104); ++ } ++ ++ return 0; ++} ++ ++int dot11ah_freq_compare(const void *a, const void *b) ++{ ++ const struct iwinfo_freqlist_entry *fe1 = (const struct iwinfo_freqlist_entry *) a; ++ const struct iwinfo_freqlist_entry *fe2 = (const struct iwinfo_freqlist_entry *) b; ++ return ( fe1->mhz - fe2->mhz ); ++} ++ ++static int dot11ah_get_freqlist(const char *ifname, char *buf, int *len) ++{ ++ struct iwinfo_freqlist_entry *fe; ++ channel_to_halow_freq_t *ch_entry; ++ const size_t fe_size = sizeof(struct iwinfo_freqlist_entry); ++ if(nl80211_get_freqlist(ifname, buf, len) < 0) ++ return -1; ++ ++ for(char *p = buf; p < (buf + *len); p += fe_size){ ++ fe = (struct iwinfo_freqlist_entry *) p; ++ ch_entry = get_s1g(g_map, fe->channel); ++ fe->channel = ch_entry->halow_channel; ++ fe->mhz = get_freq(g_map, fe->channel)*1000; ++ fe->band = IWINFO_BAND_900; ++ fe->flags = 0; ++ } ++ ++ qsort(buf, *len/fe_size, fe_size, dot11ah_freq_compare); ++ return 0; ++} ++ ++static int dot11ah_get_countrylist(const char *ifname, char *buf, int *len) ++{ ++ int count; ++ struct iwinfo_country_entry *e = (struct iwinfo_country_entry *)buf; ++ ++ const country_channel_map_t **halow_map = s1g_mapped_channel(); ++ char higher='0',lower='0'; ++ e->iso3166 = (int)higher*256+lower; ++ e->ccode[0] = '0'; ++ e->ccode[1] = '0'; ++ e->ccode[2] = 0; ++ e++; ++ count=1; ++ while((*halow_map)->country[0]) ++ { ++ higher = (*halow_map)->country[0]; ++ lower = (*halow_map)->country[1]; ++ e->iso3166 = higher * 256 + lower; ++ e->ccode[0] = higher; ++ e->ccode[1] = lower; ++ e->ccode[2] = 0; ++ e++; ++ halow_map++; ++ count++; ++ } ++ *len = (count * sizeof(struct iwinfo_country_entry)); ++ return 0; ++} ++ ++/* ++ * modified function to look for the interface in wpa_supplicant_s1g ++ */ ++static int dot11ah_wpactl_connect(const char *ifname, struct sockaddr_un *local) ++{ ++ struct sockaddr_un remote = { 0 }; ++ size_t remote_length, local_length; ++ ++ int sock = socket(PF_UNIX, SOCK_DGRAM, 0); ++ if (sock < 0) ++ return sock; ++ ++ remote.sun_family = AF_UNIX; ++ remote_length = sizeof(remote.sun_family) + ++ sprintf(remote.sun_path, "/var/run/wpa_supplicant-%s/%s", ++ ifname, ifname); ++ ++ if (fcntl(sock, F_SETFD, fcntl(sock, F_GETFD) | FD_CLOEXEC) < 0) ++ { ++ close(sock); ++ return -1; ++ } ++ ++ if (connect(sock, (struct sockaddr *)&remote, remote_length)) ++ { ++ remote_length = sizeof(remote.sun_family) + ++ sprintf(remote.sun_path, "/var/run/wpa_supplicant_s1g/%s", ifname); ++ ++ if (connect(sock, (struct sockaddr *)&remote, remote_length)) ++ { ++ close(sock); ++ return -1; ++ } ++ } ++ ++ local->sun_family = AF_UNIX; ++ local_length = sizeof(local->sun_family) + ++ sprintf(local->sun_path, "/var/run/iwinfo-%s-%d", ifname, getpid()); ++ ++ if (bind(sock, (struct sockaddr *)local, local_length) < 0) ++ { ++ close(sock); ++ return -1; ++ } ++ ++ return sock; ++} ++ ++/* function copied as is from nl80211 version*/ ++static int __dot11ah_wpactl_query(const char *ifname, ...) ++{ ++ va_list ap, ap_cur; ++ struct sockaddr_un local = { 0 }; ++ int len, mode, found = 0, sock = -1; ++ char *search, *dest, *key, *val, *line, *pos, buf[512]; ++ ++ if (nl80211_get_mode(ifname, &mode)) ++ return 0; ++ ++ if (mode != IWINFO_OPMODE_CLIENT && ++ mode != IWINFO_OPMODE_ADHOC && ++ mode != IWINFO_OPMODE_MESHPOINT) ++ return 0; ++ ++ sock = dot11ah_wpactl_connect(ifname, &local); ++ ++ if (sock < 0) ++ return 0; ++ ++ va_start(ap, ifname); ++ ++ /* clear all destination buffers */ ++ va_copy(ap_cur, ap); ++ ++ while ((search = va_arg(ap_cur, char *)) != NULL) ++ { ++ dest = va_arg(ap_cur, char *); ++ len = va_arg(ap_cur, int); ++ ++ memset(dest, 0, len); ++ } ++ ++ va_end(ap_cur); ++ ++ send(sock, "STATUS", 6, 0); ++ ++ while (true) ++ { ++ if (nl80211_wpactl_recv(sock, buf, sizeof(buf)) <= 0) ++ break; ++ ++ if (buf[0] == '<') ++ continue; ++ ++ for (line = strtok_r(buf, "\n", &pos); ++ line != NULL; ++ line = strtok_r(NULL, "\n", &pos)) ++ { ++ key = strtok(line, "="); ++ val = strtok(NULL, "\n"); ++ ++ if (!key || !val) ++ continue; ++ ++ va_copy(ap_cur, ap); ++ ++ while ((search = va_arg(ap_cur, char *)) != NULL) ++ { ++ dest = va_arg(ap_cur, char *); ++ len = va_arg(ap_cur, int); ++ ++ if (!strcmp(search, key)) ++ { ++ strncpy(dest, val, len - 1); ++ found++; ++ break; ++ } ++ } ++ ++ va_end(ap_cur); ++ } ++ ++ break; ++ } ++ ++ va_end(ap); ++ ++ close(sock); ++ unlink(local.sun_path); ++ ++ return found; ++} ++ ++#define dot11ah_wpactl_query(ifname, ...) \ ++ __dot11ah_wpactl_query(ifname, ##__VA_ARGS__, NULL) ++ ++/* function copied as is from nl80211 version*/ ++static int dot11ah_get_encryption(const char *ifname, char *buf) ++{ ++ char *p; ++ int opmode; ++ uint8_t wpa_version = 0; ++ char wpa[2], wpa_key_mgmt[64], wpa_pairwise[16], wpa_groupwise[16]; ++ char auth_algs[2], wep_key0[27], wep_key1[27], wep_key2[27], wep_key3[27]; ++ char ieee80211w[2], pmf[2]; ++ char mode[16]; ++ ++ struct iwinfo_crypto_entry *c = (struct iwinfo_crypto_entry *)buf; ++ ++ /* WPA supplicant */ ++ if (dot11ah_wpactl_query(ifname, ++ "pairwise_cipher", wpa_pairwise, sizeof(wpa_pairwise), ++ "group_cipher", wpa_groupwise, sizeof(wpa_groupwise), ++ "key_mgmt", wpa_key_mgmt, sizeof(wpa_key_mgmt), ++ "pmf", pmf, sizeof(pmf), ++ "mode", mode, sizeof(mode))) ++ { ++ /* WEP or Open */ ++ if (!strcmp(wpa_key_mgmt, "NONE")) ++ { ++ parse_wpa_ciphers(wpa_pairwise, &c->pair_ciphers); ++ parse_wpa_ciphers(wpa_groupwise, &c->group_ciphers); ++ ++ if (c->pair_ciphers != 0 && c->pair_ciphers != IWINFO_CIPHER_NONE) { ++ c->enabled = 1; ++ c->auth_suites = IWINFO_KMGMT_NONE; ++ c->auth_algs = IWINFO_AUTH_OPEN | IWINFO_AUTH_SHARED; ++ } ++ else { ++ c->pair_ciphers = 0; ++ c->group_ciphers = 0; ++ } ++ } ++ ++ /* MESH with SAE */ ++ else if (!strcmp(mode, "mesh") && !strcmp(wpa_key_mgmt, "UNKNOWN")) ++ { ++ c->enabled = 1; ++ c->wpa_version = 4; ++ c->auth_suites = IWINFO_KMGMT_SAE; ++ c->pair_ciphers = IWINFO_CIPHER_CCMP; ++ c->group_ciphers = IWINFO_CIPHER_CCMP; ++ } ++ ++ /* WPA */ ++ else ++ { ++ parse_wpa_ciphers(wpa_pairwise, &c->pair_ciphers); ++ parse_wpa_ciphers(wpa_groupwise, &c->group_ciphers); ++ ++ p = wpa_key_mgmt; ++ ++ if (!strncmp(p, "WPA2-", 5) || !strncmp(p, "WPA2/", 5)) ++ { ++ p += 5; ++ wpa_version = 2; ++ } ++ else if (!strncmp(p, "WPA-", 4)) ++ { ++ p += 4; ++ wpa_version = 1; ++ } ++ ++ parse_wpa_suites(p, wpa_version, &c->wpa_version, &c->auth_suites, atoi(pmf)); ++ ++ c->enabled = !!(c->wpa_version && c->auth_suites); ++ } ++ ++ return 0; ++ } ++ ++ /* Hostapd */ ++ else if (nl80211_hostapd_query(ifname, ++ "wpa", wpa, sizeof(wpa), ++ "wpa_key_mgmt", wpa_key_mgmt, sizeof(wpa_key_mgmt), ++ "wpa_pairwise", wpa_pairwise, sizeof(wpa_pairwise), ++ "ieee80211w", ieee80211w, sizeof(ieee80211w), ++ "auth_algs", auth_algs, sizeof(auth_algs), ++ "wep_key0", wep_key0, sizeof(wep_key0), ++ "wep_key1", wep_key1, sizeof(wep_key1), ++ "wep_key2", wep_key2, sizeof(wep_key2), ++ "wep_key3", wep_key3, sizeof(wep_key3))) ++ { ++ c->wpa_version = 0; ++ ++ if (wpa_key_mgmt[0]) ++ { ++ for (p = strtok(wpa_key_mgmt, " \t"); p != NULL; p = strtok(NULL, " \t")) ++ { ++ if (!strncmp(p, "WPA-", 4)) ++ p += 4; ++ ++ if (!strncmp(p, "FT-", 3)) ++ p += 3; ++ ++ parse_wpa_suites(p, atoi(wpa), &c->wpa_version, &c->auth_suites, atoi(ieee80211w)); ++ } ++ ++ c->enabled = c->wpa_version ? 1 : 0; ++ } ++ ++ if (wpa_pairwise[0]) ++ parse_wpa_ciphers(wpa_pairwise, &c->pair_ciphers); ++ ++ if (auth_algs[0]) ++ { ++ switch (atoi(auth_algs)) ++ { ++ case 1: ++ c->auth_algs |= IWINFO_AUTH_OPEN; ++ break; ++ ++ case 2: ++ c->auth_algs |= IWINFO_AUTH_SHARED; ++ break; ++ ++ case 3: ++ c->auth_algs |= IWINFO_AUTH_OPEN; ++ c->auth_algs |= IWINFO_AUTH_SHARED; ++ break; ++ } ++ ++ c->pair_ciphers |= nl80211_check_wepkey(wep_key0); ++ c->pair_ciphers |= nl80211_check_wepkey(wep_key1); ++ c->pair_ciphers |= nl80211_check_wepkey(wep_key2); ++ c->pair_ciphers |= nl80211_check_wepkey(wep_key3); ++ ++ c->enabled = (c->auth_algs && c->pair_ciphers) ? 1 : 0; ++ } ++ ++ c->group_ciphers = c->pair_ciphers; ++ ++ return 0; ++ } ++ ++ /* Ad-Hoc or Mesh interfaces without wpa_supplicant are open */ ++ else if (!nl80211_get_mode(ifname, &opmode) && ++ (opmode == IWINFO_OPMODE_ADHOC || ++ opmode == IWINFO_OPMODE_MESHPOINT)) ++ { ++ c->enabled = 0; ++ ++ return 0; ++ } ++ ++ ++ return -1; ++} ++ ++const struct iwinfo_ops dot11ah_ops = { ++ .name = "dot11ah", ++ .probe = dot11ah_probe, ++ .channel = dot11ah_get_channel, ++ .center_chan1 = dot11ah_get_center_chan1, ++ .center_chan2 = dot11ah_get_center_chan2, ++ .frequency = dot11ah_get_frequency, ++ .frequency_offset = nl80211_get_frequency_offset, ++ .txpower = nl80211_get_txpower, ++ .txpower_offset = nl80211_get_txpower_offset, ++ .bitrate = dot11ah_get_bitrate, ++ .signal = nl80211_get_signal, ++ .noise = dot11ah_get_noise, ++ .quality = nl80211_get_quality, ++ .quality_max = nl80211_get_quality_max, ++ .mbssid_support = nl80211_get_mbssid_support, ++ .hwmodelist = dot11ah_get_hwmodelist, ++ .htmodelist = dot11ah_get_htmodelist, ++ .htmode = dot11ah_get_htmode, ++ .mode = nl80211_get_mode, ++ .ssid = nl80211_get_ssid, ++ .bssid = nl80211_get_bssid, ++ .country = dot11ah_get_country, ++ .hardware_id = nl80211_get_hardware_id, ++ .hardware_name = nl80211_get_hardware_name, ++ .encryption = dot11ah_get_encryption, ++ .phyname = nl80211_get_phyname, ++ .assoclist = dot11ah_get_assoclist, ++ .txpwrlist = nl80211_get_txpwrlist, ++ .scanlist = dot11ah_get_scanlist, ++ .freqlist = dot11ah_get_freqlist, ++ .countrylist = dot11ah_get_countrylist, ++ .survey = nl80211_get_survey, ++ .lookup_phy = nl80211_lookup_phyname, ++ .phy_path = nl80211_phy_path, ++ .close = nl80211_close ++}; +\ No newline at end of file +diff --git a/iwinfo_utils.c b/iwinfo_utils.c +index d96cbb3..a7c8abe 100644 +--- a/iwinfo_utils.c ++++ b/iwinfo_utils.c +@@ -2,6 +2,7 @@ + * iwinfo - Wireless Information Library - Shared utility routines + * + * Copyright (C) 2010 Jo-Philipp Wich ++ * Copyright 2022 Morse Micro + * + * The iwinfo library is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 +@@ -151,8 +152,8 @@ uint8_t iwinfo_ghz2band(uint32_t ghz) + + size_t iwinfo_format_hwmodes(int modes, char *buf, size_t len) + { +- // bit numbers as per IWINFO_80211_*: ad ac ax a b g n +- const int order[IWINFO_80211_COUNT] = { 5, 4, 6, 0, 1, 2, 3 }; ++ // bit numbers as per IWINFO_80211_*: ad ac ax ah a b g n ++ const int order[IWINFO_80211_COUNT] = { 5, 4, 6, 7, 0, 1, 2, 3 }; + size_t res = 0; + int i; + +@@ -599,6 +600,25 @@ void iwinfo_parse_rsn(struct iwinfo_crypto_entry *c, uint8_t *data, uint8_t len, + len -= 2 + (count * 4); + } + ++void iwinfo_parse_rsnxe(struct iwinfo_crypto_entry *c, uint8_t *data, uint8_t len) ++{ ++ if (len > 2) ++ return; ++ ++ c->prot_twt = (data[0] >> 4) & 0x1; ++ c->sae_h2e = (data[0] >> 5) & 0x1; ++ c->sae_pk = (data[0] >> 6) & 0x1; ++ len -= 1; ++ data += 1; ++ ++ if (len == 0) ++ return; ++ ++ c->secure_ltf = (data[0]) & 0x1; ++ c->secure_rtt = (data[0] >> 1) & 0x1; ++ c->prot_range_neg = (data[0] >> 2) & 0x1; ++} ++ + struct uci_section *iwinfo_uci_get_radio(const char *name, const char *type) + { + struct uci_ptr ptr = { -- 2.34.1