mirror of
				https://github.com/Telecominfraproject/wlan-ap.git
				synced 2025-10-30 01:52:51 +00:00 
			
		
		
		
	 f39339564a
			
		
	
	f39339564a
	
	
	
		
			
			1. porting MorseMicro HaLow driver to support HaLow on EAP112 2. Only support FCC regulation because of hardware limitation 3. Add /etc/init.d/halow-gpio-reset to initialize HaLow chip in early stage 4. Add /etc/uci-defaults/aaa-fix-phy0-to-morse to correct the default uci for HaLow radio. Signed-off-by: Ian Chen <ian77_chen@accton.com>
		
			
				
	
	
		
			1996 lines
		
	
	
		
			55 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			1996 lines
		
	
	
		
			55 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From 67adfbc5b4831ecb62a169370c102e0939ae884e Mon Sep 17 00:00:00 2001
 | |
| From: ian77_chen <ian77_chen@accton.com>
 | |
| 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 <stdio.h>
 | |
| ++#include <string.h>
 | |
| ++#include <stdlib.h>
 | |
| ++
 | |
| ++#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;i<map->num_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 <stdarg.h>
 | |
| ++#include <stdlib.h>
 | |
| ++#include <stdio.h>
 | |
| ++#include <string.h>
 | |
| ++#include <unistd.h>
 | |
| ++
 | |
| ++
 | |
| ++/* 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 <xm@subsignal.org>
 | |
| ++ *   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 <stdlib.h>
 | |
| + 
 | |
| + #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 <xm@subsignal.org>
 | |
| ++ *   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
 | |
| 
 |