// SPDX-License-Identifier: GPL-2.0 /****************************************************************************** * * Copyright (C) 2020 SeekWave Technology Co.,Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation; * * This program 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. * ******************************************************************************/ #include #include #include "skw_core.h" #include "skw_regd.h" #include "skw_msg.h" #include "skw_log.h" #include "skw_db.h" static int skw_regd_show(struct seq_file *seq, void *data) { struct wiphy *wiphy = seq->private; struct skw_core *skw = wiphy_priv(wiphy); seq_puts(seq, "\n"); seq_printf(seq, "country: %c%c\n", skw->country[0], skw->country[1]); seq_puts(seq, "\n"); return 0; } static int skw_regd_open(struct inode *inode, struct file *file) { return single_open(file, skw_regd_show, inode->i_private); } static ssize_t skw_regd_write(struct file *fp, const char __user *buf, size_t size, loff_t *off) { u8 country[3] = {0}; struct wiphy *wiphy = fp->f_inode->i_private; int ret = 0; if (size != 3) { skw_err("invalid len: %zd\n", size); return size; } if (copy_from_user(&country, buf, size)) { skw_err("copy failed\n"); return size; } ret = skw_set_regdom(wiphy, (char *)country); if (ret) skw_err("set country failed, ret: %d\n", ret); return size; } static const struct file_operations skw_regd_fops = { .owner = THIS_MODULE, .open = skw_regd_open, .read = seq_read, .write = skw_regd_write, .llseek = seq_lseek, .release = single_release, }; static bool skw_alpha2_equal(const char *alpha2_x, const char *alpha2_y) { if (!alpha2_x || !alpha2_y) return false; return alpha2_x[0] == alpha2_y[0] && alpha2_x[1] == alpha2_y[1]; } static bool skw_freq_in_rule_band(const struct ieee80211_freq_range *freq_range, u32 freq_khz) { #define ONE_GHZ_IN_KHZ 1000000 u32 limit = freq_khz > 45 * ONE_GHZ_IN_KHZ ? 20 * ONE_GHZ_IN_KHZ : 2 * ONE_GHZ_IN_KHZ; if (abs(freq_khz - freq_range->start_freq_khz) <= limit) return true; if (abs(freq_khz - freq_range->end_freq_khz) <= limit) return true; return false; #undef ONE_GHZ_IN_KHZ } static bool skw_does_bw_fit_range(const struct ieee80211_freq_range *freq_range, u32 center_freq_khz, u32 bw_khz) { u32 start_freq_khz, end_freq_khz; start_freq_khz = center_freq_khz - (bw_khz / 2); end_freq_khz = center_freq_khz + (bw_khz / 2); if (start_freq_khz >= freq_range->start_freq_khz && end_freq_khz <= freq_range->end_freq_khz) return true; return false; } static const struct ieee80211_regdomain *skw_get_regd(const char *alpha2) { int i; const struct ieee80211_regdomain *regdom; if (!is_skw_valid_reg_code(alpha2)) { skw_err("Invalid alpha\n"); return NULL; } for (i = 0; i < skw_regdb_size; i++) { regdom = skw_regdb[i]; if (skw_alpha2_equal(alpha2, regdom->alpha2)) return regdom; } skw_warn("country: %c%c not support\n", alpha2[0], alpha2[1]); return NULL; } static bool is_skw_valid_reg_rule(const struct ieee80211_reg_rule *rule) { u32 freq_diff; const struct ieee80211_freq_range *freq_range = &rule->freq_range; if (freq_range->start_freq_khz <= 0 || freq_range->end_freq_khz <= 0) { skw_dbg("invalid, start: %d, end: %d\n", freq_range->start_freq_khz, freq_range->end_freq_khz); return false; } if (freq_range->start_freq_khz > freq_range->end_freq_khz) { skw_dbg("invalid, start: %d > end: %d\n", freq_range->start_freq_khz, freq_range->end_freq_khz); return false; } freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; if (freq_range->end_freq_khz <= freq_range->start_freq_khz || freq_range->max_bandwidth_khz > freq_diff) { skw_dbg("invalid, start: %d, end: %d, max band: %d, diff: %d\n", freq_range->start_freq_khz, freq_range->end_freq_khz, freq_range->max_bandwidth_khz, freq_diff); return false; } return true; } static bool is_skw_valid_rd(const struct ieee80211_regdomain *rd) { int i; const struct ieee80211_reg_rule *reg_rule = NULL; for (i = 0; i < rd->n_reg_rules; i++) { reg_rule = &rd->reg_rules[i]; if (!is_skw_valid_reg_rule(reg_rule)) return false; } return true; } static const struct ieee80211_reg_rule * skw_freq_reg_info(const struct ieee80211_regdomain *regd, u32 freq) { int i; bool band_rule_found = false; bool bw_fits = false; if (!regd) return ERR_PTR(-EINVAL); for (i = 0; i < regd->n_reg_rules; i++) { const struct ieee80211_reg_rule *rr; const struct ieee80211_freq_range *fr = NULL; rr = ®d->reg_rules[i]; fr = &rr->freq_range; if (!band_rule_found) band_rule_found = skw_freq_in_rule_band(fr, freq); bw_fits = skw_does_bw_fit_range(fr, freq, MHZ_TO_KHZ(20)); if (band_rule_found && bw_fits) return rr; } if (!band_rule_found) return ERR_PTR(-ERANGE); return ERR_PTR(-EINVAL); } static const struct ieee80211_reg_rule *skw_regd_rule(struct wiphy *wiphy, u32 freq) { u32 freq_khz = MHZ_TO_KHZ(freq); struct skw_core *skw = wiphy_priv(wiphy); if (skw->regd || skw_regd_self_mamaged(wiphy)) return skw_freq_reg_info(skw->regd, freq_khz); return freq_reg_info(wiphy, freq_khz); } int skw_cmd_set_regdom(struct wiphy *wiphy, const char *alpha2) { int ret; int i, idx, band; struct ieee80211_supported_band *sband; struct skw_regdom regd = {}; struct skw_core *skw = wiphy_priv(wiphy); struct skw_reg_rule *rule = ®d.rules[0]; const struct ieee80211_reg_rule *rr = NULL, *_rr = NULL; #define SKW_MAX_POWER(rr) (MBM_TO_DBM(rr->power_rule.max_eirp)) #define SKW_MAX_GAIN(rr) (MBI_TO_DBI(rr->power_rule.max_antenna_gain)) regd.country[0] = alpha2[0]; regd.country[1] = alpha2[1]; regd.country[2] = 0; for (idx = 0, band = 0; band < NUM_NL80211_BANDS; band++) { sband = wiphy->bands[band]; if (!sband) continue; for (i = 0; i < sband->n_channels; i++) { struct ieee80211_channel *chn = &sband->channels[i]; rr = skw_regd_rule(wiphy, chn->center_freq); if (IS_ERR(rr) || rr->flags & SKW_RRF_NO_IR) continue; if (rr != _rr) { regd.nr_reg_rules++; rule = ®d.rules[idx++]; rule->nr_channel = 0; rule->start_channel = chn->hw_value; rule->max_power = SKW_MAX_POWER(rr); rule->max_gain = SKW_MAX_GAIN(rr); rule->flags = rr->flags; _rr = rr; } rule->nr_channel = chn->hw_value - rule->start_channel + 1; } } if (!regd.nr_reg_rules) return 0; for (i = 0; i < regd.nr_reg_rules; i++) { skw_dbg("%d @ %d, power: %d, gain: %d, flags: 0x%x\n", regd.rules[i].start_channel, regd.rules[i].nr_channel, regd.rules[i].max_power, regd.rules[i].max_gain, regd.rules[i].flags); } ret = skw_msg_xmit(wiphy, 0, SKW_CMD_SET_REGD, ®d, sizeof(regd), NULL, 0); if (!ret) { skw->country[0] = alpha2[0]; skw->country[1] = alpha2[1]; } else { skw_warn("failed, country: %c%c, rules: %d, ret: %d\n", alpha2[0], alpha2[1], regd.nr_reg_rules, ret); } return ret; } static int __skw_set_wiphy_regd(struct wiphy *wiphy, struct ieee80211_regdomain *rd) { int ret = 0; struct skw_core *skw = wiphy_priv(wiphy); skw->regd = rd; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0) if (rtnl_is_locked()) ret = skw_set_wiphy_regd_sync(wiphy, rd); else ret = regulatory_set_wiphy_regd(wiphy, rd); #else wiphy_apply_custom_regulatory(wiphy, rd); #endif return ret; } int skw_set_wiphy_regd(struct wiphy *wiphy, const char *country) { const struct ieee80211_regdomain *regd; if (!skw_regd_self_mamaged(wiphy)) return 0; regd = skw_get_regd(country); if (!regd) return -EINVAL; if (!is_skw_valid_rd(regd)) return -EINVAL; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0) if (country[0] == '0' && country[1] == '0') wiphy->regulatory_flags &= ~REGULATORY_DISABLE_BEACON_HINTS; else wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS; #endif return __skw_set_wiphy_regd(wiphy, (void *)regd); } int skw_set_regdom(struct wiphy *wiphy, char *country) { int ret; skw_dbg("country: %c%c\n", country[0], country[1]); if (!is_skw_valid_reg_code(country)) { skw_err("Invalid country code: %c%c\n", country[0], country[1]); return -EINVAL; } if (skw_regd_self_mamaged(wiphy)) { ret = skw_set_wiphy_regd(wiphy, country); if (!ret) ret = skw_cmd_set_regdom(wiphy, country); return ret; } return regulatory_hint(wiphy, country); } void skw_regd_init(struct wiphy *wiphy) { skw_debugfs_file(SKW_WIPHY_DENTRY(wiphy), "regdom", 0666, &skw_regd_fops, wiphy); }