17f904d7eSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2e9f207f0SJiri Benc /* 3e9f207f0SJiri Benc * mac80211 debugfs for wireless PHYs 4e9f207f0SJiri Benc * 5e9f207f0SJiri Benc * Copyright 2007 Johannes Berg <johannes@sipsolutions.net> 6d98ad83eSJohannes Berg * Copyright 2013-2014 Intel Mobile Communications GmbH 7*963d0e8dSJohannes Berg * Copyright (C) 2018 - 2019, 2021-2022 Intel Corporation 8e9f207f0SJiri Benc */ 9e9f207f0SJiri Benc 10e9f207f0SJiri Benc #include <linux/debugfs.h> 11e9f207f0SJiri Benc #include <linux/rtnetlink.h> 129399b86cSMichal Kazior #include <linux/vmalloc.h> 13e9f207f0SJiri Benc #include "ieee80211_i.h" 1424487981SJohannes Berg #include "driver-ops.h" 152c8dccc7SJohannes Berg #include "rate.h" 16e9f207f0SJiri Benc #include "debugfs.h" 17e9f207f0SJiri Benc 1807caf9d6SEliad Peller #define DEBUGFS_FORMAT_BUFFER_SIZE 100 1907caf9d6SEliad Peller 2007caf9d6SEliad Peller int mac80211_format_buffer(char __user *userbuf, size_t count, 2107caf9d6SEliad Peller loff_t *ppos, char *fmt, ...) 2207caf9d6SEliad Peller { 2307caf9d6SEliad Peller va_list args; 2407caf9d6SEliad Peller char buf[DEBUGFS_FORMAT_BUFFER_SIZE]; 2507caf9d6SEliad Peller int res; 2607caf9d6SEliad Peller 2707caf9d6SEliad Peller va_start(args, fmt); 2807caf9d6SEliad Peller res = vscnprintf(buf, sizeof(buf), fmt, args); 2907caf9d6SEliad Peller va_end(args); 3007caf9d6SEliad Peller 3107caf9d6SEliad Peller return simple_read_from_buffer(userbuf, count, ppos, buf, res); 3207caf9d6SEliad Peller } 3307caf9d6SEliad Peller 34279daf64SBen Greear #define DEBUGFS_READONLY_FILE_FN(name, fmt, value...) \ 35e9f207f0SJiri Benc static ssize_t name## _read(struct file *file, char __user *userbuf, \ 36e9f207f0SJiri Benc size_t count, loff_t *ppos) \ 37e9f207f0SJiri Benc { \ 38e9f207f0SJiri Benc struct ieee80211_local *local = file->private_data; \ 39e9f207f0SJiri Benc \ 4007caf9d6SEliad Peller return mac80211_format_buffer(userbuf, count, ppos, \ 4107caf9d6SEliad Peller fmt "\n", ##value); \ 42279daf64SBen Greear } 43279daf64SBen Greear 44279daf64SBen Greear #define DEBUGFS_READONLY_FILE_OPS(name) \ 45e9f207f0SJiri Benc static const struct file_operations name## _ops = { \ 46e9f207f0SJiri Benc .read = name## _read, \ 47234e3405SStephen Boyd .open = simple_open, \ 482b18ab36SArnd Bergmann .llseek = generic_file_llseek, \ 49e9f207f0SJiri Benc }; 50e9f207f0SJiri Benc 51279daf64SBen Greear #define DEBUGFS_READONLY_FILE(name, fmt, value...) \ 52279daf64SBen Greear DEBUGFS_READONLY_FILE_FN(name, fmt, value) \ 53279daf64SBen Greear DEBUGFS_READONLY_FILE_OPS(name) 54279daf64SBen Greear 55e9f207f0SJiri Benc #define DEBUGFS_ADD(name) \ 5684674ef4STom Rix debugfs_create_file(#name, 0400, phyd, local, &name## _ops) 57e9f207f0SJiri Benc 58827b1fb4SJohannes Berg #define DEBUGFS_ADD_MODE(name, mode) \ 597bcfaf2fSJohannes Berg debugfs_create_file(#name, mode, phyd, local, &name## _ops); 60e9f207f0SJiri Benc 61e9f207f0SJiri Benc 62c90142a5SThomas Pedersen DEBUGFS_READONLY_FILE(hw_conf, "%x", 63c90142a5SThomas Pedersen local->hw.conf.flags); 6483bdf2a1SBen Greear DEBUGFS_READONLY_FILE(user_power, "%d", 6583bdf2a1SBen Greear local->user_power_level); 6683bdf2a1SBen Greear DEBUGFS_READONLY_FILE(power, "%d", 6783bdf2a1SBen Greear local->hw.conf.power_level); 6807caf9d6SEliad Peller DEBUGFS_READONLY_FILE(total_ps_buffered, "%d", 69e9f207f0SJiri Benc local->total_ps_buffered); 7007caf9d6SEliad Peller DEBUGFS_READONLY_FILE(wep_iv, "%#08x", 71e9f207f0SJiri Benc local->wep_iv & 0xffffff); 7207caf9d6SEliad Peller DEBUGFS_READONLY_FILE(rate_ctrl_alg, "%s", 73af65cd96SJohannes Berg local->rate_ctrl ? local->rate_ctrl->ops->name : "hw/driver"); 743b5d665bSAlina Friedrichsen 758d51dbb8SToke Høiland-Jørgensen static ssize_t aqm_read(struct file *file, 768d51dbb8SToke Høiland-Jørgensen char __user *user_buf, 778d51dbb8SToke Høiland-Jørgensen size_t count, 788d51dbb8SToke Høiland-Jørgensen loff_t *ppos) 799399b86cSMichal Kazior { 808d51dbb8SToke Høiland-Jørgensen struct ieee80211_local *local = file->private_data; 819399b86cSMichal Kazior struct fq *fq = &local->fq; 828d51dbb8SToke Høiland-Jørgensen char buf[200]; 839399b86cSMichal Kazior int len = 0; 849399b86cSMichal Kazior 859399b86cSMichal Kazior spin_lock_bh(&local->fq.lock); 869399b86cSMichal Kazior rcu_read_lock(); 879399b86cSMichal Kazior 888d51dbb8SToke Høiland-Jørgensen len = scnprintf(buf, sizeof(buf), 899399b86cSMichal Kazior "access name value\n" 909399b86cSMichal Kazior "R fq_flows_cnt %u\n" 919399b86cSMichal Kazior "R fq_backlog %u\n" 929399b86cSMichal Kazior "R fq_overlimit %u\n" 932a4e675dSToke Høiland-Jørgensen "R fq_overmemory %u\n" 949399b86cSMichal Kazior "R fq_collisions %u\n" 952a4e675dSToke Høiland-Jørgensen "R fq_memory_usage %u\n" 962a4e675dSToke Høiland-Jørgensen "RW fq_memory_limit %u\n" 979399b86cSMichal Kazior "RW fq_limit %u\n" 989399b86cSMichal Kazior "RW fq_quantum %u\n", 999399b86cSMichal Kazior fq->flows_cnt, 1009399b86cSMichal Kazior fq->backlog, 1012a4e675dSToke Høiland-Jørgensen fq->overmemory, 1029399b86cSMichal Kazior fq->overlimit, 1039399b86cSMichal Kazior fq->collisions, 1042a4e675dSToke Høiland-Jørgensen fq->memory_usage, 1052a4e675dSToke Høiland-Jørgensen fq->memory_limit, 1069399b86cSMichal Kazior fq->limit, 1079399b86cSMichal Kazior fq->quantum); 1089399b86cSMichal Kazior 1099399b86cSMichal Kazior rcu_read_unlock(); 1109399b86cSMichal Kazior spin_unlock_bh(&local->fq.lock); 1119399b86cSMichal Kazior 1129399b86cSMichal Kazior return simple_read_from_buffer(user_buf, count, ppos, 1138d51dbb8SToke Høiland-Jørgensen buf, len); 1149399b86cSMichal Kazior } 1159399b86cSMichal Kazior 1169399b86cSMichal Kazior static ssize_t aqm_write(struct file *file, 1179399b86cSMichal Kazior const char __user *user_buf, 1189399b86cSMichal Kazior size_t count, 1199399b86cSMichal Kazior loff_t *ppos) 1209399b86cSMichal Kazior { 1218d51dbb8SToke Høiland-Jørgensen struct ieee80211_local *local = file->private_data; 1229399b86cSMichal Kazior char buf[100]; 1239399b86cSMichal Kazior 1246020d534SShayne Chen if (count >= sizeof(buf)) 1259399b86cSMichal Kazior return -EINVAL; 1269399b86cSMichal Kazior 1279399b86cSMichal Kazior if (copy_from_user(buf, user_buf, count)) 1289399b86cSMichal Kazior return -EFAULT; 1299399b86cSMichal Kazior 1306020d534SShayne Chen if (count && buf[count - 1] == '\n') 1316020d534SShayne Chen buf[count - 1] = '\0'; 1326020d534SShayne Chen else 1336020d534SShayne Chen buf[count] = '\0'; 1349399b86cSMichal Kazior 1359399b86cSMichal Kazior if (sscanf(buf, "fq_limit %u", &local->fq.limit) == 1) 1369399b86cSMichal Kazior return count; 1372a4e675dSToke Høiland-Jørgensen else if (sscanf(buf, "fq_memory_limit %u", &local->fq.memory_limit) == 1) 1382a4e675dSToke Høiland-Jørgensen return count; 1399399b86cSMichal Kazior else if (sscanf(buf, "fq_quantum %u", &local->fq.quantum) == 1) 1409399b86cSMichal Kazior return count; 1419399b86cSMichal Kazior 1429399b86cSMichal Kazior return -EINVAL; 1439399b86cSMichal Kazior } 1449399b86cSMichal Kazior 1459399b86cSMichal Kazior static const struct file_operations aqm_ops = { 1469399b86cSMichal Kazior .write = aqm_write, 1479399b86cSMichal Kazior .read = aqm_read, 1488d51dbb8SToke Høiland-Jørgensen .open = simple_open, 1499399b86cSMichal Kazior .llseek = default_llseek, 1509399b86cSMichal Kazior }; 1519399b86cSMichal Kazior 152e322c07fSLorenzo Bianconi static ssize_t airtime_flags_read(struct file *file, 153e322c07fSLorenzo Bianconi char __user *user_buf, 154e322c07fSLorenzo Bianconi size_t count, loff_t *ppos) 155e322c07fSLorenzo Bianconi { 156e322c07fSLorenzo Bianconi struct ieee80211_local *local = file->private_data; 157e322c07fSLorenzo Bianconi char buf[128] = {}, *pos, *end; 158e322c07fSLorenzo Bianconi 159e322c07fSLorenzo Bianconi pos = buf; 160e322c07fSLorenzo Bianconi end = pos + sizeof(buf) - 1; 161e322c07fSLorenzo Bianconi 162e322c07fSLorenzo Bianconi if (local->airtime_flags & AIRTIME_USE_TX) 163e322c07fSLorenzo Bianconi pos += scnprintf(pos, end - pos, "AIRTIME_TX\t(%lx)\n", 164e322c07fSLorenzo Bianconi AIRTIME_USE_TX); 165e322c07fSLorenzo Bianconi if (local->airtime_flags & AIRTIME_USE_RX) 166e322c07fSLorenzo Bianconi pos += scnprintf(pos, end - pos, "AIRTIME_RX\t(%lx)\n", 167e322c07fSLorenzo Bianconi AIRTIME_USE_RX); 168e322c07fSLorenzo Bianconi 169e322c07fSLorenzo Bianconi return simple_read_from_buffer(user_buf, count, ppos, buf, 170e322c07fSLorenzo Bianconi strlen(buf)); 171e322c07fSLorenzo Bianconi } 172e322c07fSLorenzo Bianconi 173e322c07fSLorenzo Bianconi static ssize_t airtime_flags_write(struct file *file, 174e322c07fSLorenzo Bianconi const char __user *user_buf, 175e322c07fSLorenzo Bianconi size_t count, loff_t *ppos) 176e322c07fSLorenzo Bianconi { 177e322c07fSLorenzo Bianconi struct ieee80211_local *local = file->private_data; 178e322c07fSLorenzo Bianconi char buf[16]; 179e322c07fSLorenzo Bianconi 1806020d534SShayne Chen if (count >= sizeof(buf)) 181e322c07fSLorenzo Bianconi return -EINVAL; 182e322c07fSLorenzo Bianconi 183e322c07fSLorenzo Bianconi if (copy_from_user(buf, user_buf, count)) 184e322c07fSLorenzo Bianconi return -EFAULT; 185e322c07fSLorenzo Bianconi 1866020d534SShayne Chen if (count && buf[count - 1] == '\n') 1876020d534SShayne Chen buf[count - 1] = '\0'; 1886020d534SShayne Chen else 1896020d534SShayne Chen buf[count] = '\0'; 190e322c07fSLorenzo Bianconi 191e322c07fSLorenzo Bianconi if (kstrtou16(buf, 0, &local->airtime_flags)) 192e322c07fSLorenzo Bianconi return -EINVAL; 193e322c07fSLorenzo Bianconi 194e322c07fSLorenzo Bianconi return count; 195e322c07fSLorenzo Bianconi } 196e322c07fSLorenzo Bianconi 197e322c07fSLorenzo Bianconi static const struct file_operations airtime_flags_ops = { 198e322c07fSLorenzo Bianconi .write = airtime_flags_write, 199e322c07fSLorenzo Bianconi .read = airtime_flags_read, 200e322c07fSLorenzo Bianconi .open = simple_open, 201e322c07fSLorenzo Bianconi .llseek = default_llseek, 202e322c07fSLorenzo Bianconi }; 203e322c07fSLorenzo Bianconi 2043db2c560SFelix Fietkau static ssize_t aql_pending_read(struct file *file, 2053db2c560SFelix Fietkau char __user *user_buf, 2063db2c560SFelix Fietkau size_t count, loff_t *ppos) 2073db2c560SFelix Fietkau { 2083db2c560SFelix Fietkau struct ieee80211_local *local = file->private_data; 2093db2c560SFelix Fietkau char buf[400]; 2103db2c560SFelix Fietkau int len = 0; 2113db2c560SFelix Fietkau 2123db2c560SFelix Fietkau len = scnprintf(buf, sizeof(buf), 2133db2c560SFelix Fietkau "AC AQL pending\n" 2143db2c560SFelix Fietkau "VO %u us\n" 2153db2c560SFelix Fietkau "VI %u us\n" 2163db2c560SFelix Fietkau "BE %u us\n" 2173db2c560SFelix Fietkau "BK %u us\n" 2183db2c560SFelix Fietkau "total %u us\n", 2193db2c560SFelix Fietkau atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_VO]), 2203db2c560SFelix Fietkau atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_VI]), 2213db2c560SFelix Fietkau atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_BE]), 2223db2c560SFelix Fietkau atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_BK]), 2233db2c560SFelix Fietkau atomic_read(&local->aql_total_pending_airtime)); 2243db2c560SFelix Fietkau return simple_read_from_buffer(user_buf, count, ppos, 2253db2c560SFelix Fietkau buf, len); 2263db2c560SFelix Fietkau } 2273db2c560SFelix Fietkau 2283db2c560SFelix Fietkau static const struct file_operations aql_pending_ops = { 2293db2c560SFelix Fietkau .read = aql_pending_read, 2303db2c560SFelix Fietkau .open = simple_open, 2313db2c560SFelix Fietkau .llseek = default_llseek, 2323db2c560SFelix Fietkau }; 2333db2c560SFelix Fietkau 2343ace10f5SKan Yan static ssize_t aql_txq_limit_read(struct file *file, 2353ace10f5SKan Yan char __user *user_buf, 2363ace10f5SKan Yan size_t count, 2373ace10f5SKan Yan loff_t *ppos) 2383ace10f5SKan Yan { 2393ace10f5SKan Yan struct ieee80211_local *local = file->private_data; 2403ace10f5SKan Yan char buf[400]; 2413ace10f5SKan Yan int len = 0; 2423ace10f5SKan Yan 2433ace10f5SKan Yan len = scnprintf(buf, sizeof(buf), 2443ace10f5SKan Yan "AC AQL limit low AQL limit high\n" 2453ace10f5SKan Yan "VO %u %u\n" 2463ace10f5SKan Yan "VI %u %u\n" 2473ace10f5SKan Yan "BE %u %u\n" 2483ace10f5SKan Yan "BK %u %u\n", 249942741daSFelix Fietkau local->aql_txq_limit_low[IEEE80211_AC_VO], 250942741daSFelix Fietkau local->aql_txq_limit_high[IEEE80211_AC_VO], 251942741daSFelix Fietkau local->aql_txq_limit_low[IEEE80211_AC_VI], 252942741daSFelix Fietkau local->aql_txq_limit_high[IEEE80211_AC_VI], 253942741daSFelix Fietkau local->aql_txq_limit_low[IEEE80211_AC_BE], 254942741daSFelix Fietkau local->aql_txq_limit_high[IEEE80211_AC_BE], 255942741daSFelix Fietkau local->aql_txq_limit_low[IEEE80211_AC_BK], 256942741daSFelix Fietkau local->aql_txq_limit_high[IEEE80211_AC_BK]); 2573ace10f5SKan Yan return simple_read_from_buffer(user_buf, count, ppos, 2583ace10f5SKan Yan buf, len); 2593ace10f5SKan Yan } 2603ace10f5SKan Yan 2613ace10f5SKan Yan static ssize_t aql_txq_limit_write(struct file *file, 2623ace10f5SKan Yan const char __user *user_buf, 2633ace10f5SKan Yan size_t count, 2643ace10f5SKan Yan loff_t *ppos) 2653ace10f5SKan Yan { 2663ace10f5SKan Yan struct ieee80211_local *local = file->private_data; 2673ace10f5SKan Yan char buf[100]; 2683ace10f5SKan Yan u32 ac, q_limit_low, q_limit_high, q_limit_low_old, q_limit_high_old; 2693ace10f5SKan Yan struct sta_info *sta; 2703ace10f5SKan Yan 2716020d534SShayne Chen if (count >= sizeof(buf)) 2723ace10f5SKan Yan return -EINVAL; 2733ace10f5SKan Yan 2743ace10f5SKan Yan if (copy_from_user(buf, user_buf, count)) 2753ace10f5SKan Yan return -EFAULT; 2763ace10f5SKan Yan 2776020d534SShayne Chen if (count && buf[count - 1] == '\n') 2786020d534SShayne Chen buf[count - 1] = '\0'; 2796020d534SShayne Chen else 2806020d534SShayne Chen buf[count] = '\0'; 2813ace10f5SKan Yan 2823ace10f5SKan Yan if (sscanf(buf, "%u %u %u", &ac, &q_limit_low, &q_limit_high) != 3) 2833ace10f5SKan Yan return -EINVAL; 2843ace10f5SKan Yan 2853ace10f5SKan Yan if (ac >= IEEE80211_NUM_ACS) 2863ace10f5SKan Yan return -EINVAL; 2873ace10f5SKan Yan 288942741daSFelix Fietkau q_limit_low_old = local->aql_txq_limit_low[ac]; 289942741daSFelix Fietkau q_limit_high_old = local->aql_txq_limit_high[ac]; 2903ace10f5SKan Yan 291942741daSFelix Fietkau local->aql_txq_limit_low[ac] = q_limit_low; 292942741daSFelix Fietkau local->aql_txq_limit_high[ac] = q_limit_high; 2933ace10f5SKan Yan 2943ace10f5SKan Yan mutex_lock(&local->sta_mtx); 2953ace10f5SKan Yan list_for_each_entry(sta, &local->sta_list, list) { 2963ace10f5SKan Yan /* If a sta has customized queue limits, keep it */ 2973ace10f5SKan Yan if (sta->airtime[ac].aql_limit_low == q_limit_low_old && 2983ace10f5SKan Yan sta->airtime[ac].aql_limit_high == q_limit_high_old) { 2993ace10f5SKan Yan sta->airtime[ac].aql_limit_low = q_limit_low; 3003ace10f5SKan Yan sta->airtime[ac].aql_limit_high = q_limit_high; 3013ace10f5SKan Yan } 3023ace10f5SKan Yan } 3033ace10f5SKan Yan mutex_unlock(&local->sta_mtx); 3043ace10f5SKan Yan return count; 3053ace10f5SKan Yan } 3063ace10f5SKan Yan 3073ace10f5SKan Yan static const struct file_operations aql_txq_limit_ops = { 3083ace10f5SKan Yan .write = aql_txq_limit_write, 3093ace10f5SKan Yan .read = aql_txq_limit_read, 3103ace10f5SKan Yan .open = simple_open, 3113ace10f5SKan Yan .llseek = default_llseek, 3123ace10f5SKan Yan }; 3133ace10f5SKan Yan 314e908435eSLorenzo Bianconi static ssize_t aql_enable_read(struct file *file, char __user *user_buf, 315e908435eSLorenzo Bianconi size_t count, loff_t *ppos) 316e908435eSLorenzo Bianconi { 317e908435eSLorenzo Bianconi char buf[3]; 318e908435eSLorenzo Bianconi int len; 319e908435eSLorenzo Bianconi 320e908435eSLorenzo Bianconi len = scnprintf(buf, sizeof(buf), "%d\n", 321e908435eSLorenzo Bianconi !static_key_false(&aql_disable.key)); 322e908435eSLorenzo Bianconi 323e908435eSLorenzo Bianconi return simple_read_from_buffer(user_buf, count, ppos, buf, len); 324e908435eSLorenzo Bianconi } 325e908435eSLorenzo Bianconi 326e908435eSLorenzo Bianconi static ssize_t aql_enable_write(struct file *file, const char __user *user_buf, 327e908435eSLorenzo Bianconi size_t count, loff_t *ppos) 328e908435eSLorenzo Bianconi { 329e908435eSLorenzo Bianconi bool aql_disabled = static_key_false(&aql_disable.key); 330e908435eSLorenzo Bianconi char buf[3]; 331e908435eSLorenzo Bianconi size_t len; 332e908435eSLorenzo Bianconi 333e908435eSLorenzo Bianconi if (count > sizeof(buf)) 334e908435eSLorenzo Bianconi return -EINVAL; 335e908435eSLorenzo Bianconi 336e908435eSLorenzo Bianconi if (copy_from_user(buf, user_buf, count)) 337e908435eSLorenzo Bianconi return -EFAULT; 338e908435eSLorenzo Bianconi 339e908435eSLorenzo Bianconi buf[sizeof(buf) - 1] = '\0'; 340e908435eSLorenzo Bianconi len = strlen(buf); 341e908435eSLorenzo Bianconi if (len > 0 && buf[len - 1] == '\n') 342e908435eSLorenzo Bianconi buf[len - 1] = 0; 343e908435eSLorenzo Bianconi 344e908435eSLorenzo Bianconi if (buf[0] == '0' && buf[1] == '\0') { 345e908435eSLorenzo Bianconi if (!aql_disabled) 346e908435eSLorenzo Bianconi static_branch_inc(&aql_disable); 347e908435eSLorenzo Bianconi } else if (buf[0] == '1' && buf[1] == '\0') { 348e908435eSLorenzo Bianconi if (aql_disabled) 349e908435eSLorenzo Bianconi static_branch_dec(&aql_disable); 350e908435eSLorenzo Bianconi } else { 351e908435eSLorenzo Bianconi return -EINVAL; 352e908435eSLorenzo Bianconi } 353e908435eSLorenzo Bianconi 354e908435eSLorenzo Bianconi return count; 355e908435eSLorenzo Bianconi } 356e908435eSLorenzo Bianconi 357e908435eSLorenzo Bianconi static const struct file_operations aql_enable_ops = { 358e908435eSLorenzo Bianconi .write = aql_enable_write, 359e908435eSLorenzo Bianconi .read = aql_enable_read, 360e908435eSLorenzo Bianconi .open = simple_open, 361e908435eSLorenzo Bianconi .llseek = default_llseek, 362e908435eSLorenzo Bianconi }; 363e908435eSLorenzo Bianconi 364276d9e82SJulius Niedworok static ssize_t force_tx_status_read(struct file *file, 365276d9e82SJulius Niedworok char __user *user_buf, 366276d9e82SJulius Niedworok size_t count, 367276d9e82SJulius Niedworok loff_t *ppos) 368276d9e82SJulius Niedworok { 369276d9e82SJulius Niedworok struct ieee80211_local *local = file->private_data; 370276d9e82SJulius Niedworok char buf[3]; 371276d9e82SJulius Niedworok int len = 0; 372276d9e82SJulius Niedworok 373276d9e82SJulius Niedworok len = scnprintf(buf, sizeof(buf), "%d\n", (int)local->force_tx_status); 374276d9e82SJulius Niedworok 375276d9e82SJulius Niedworok return simple_read_from_buffer(user_buf, count, ppos, 376276d9e82SJulius Niedworok buf, len); 377276d9e82SJulius Niedworok } 378276d9e82SJulius Niedworok 379276d9e82SJulius Niedworok static ssize_t force_tx_status_write(struct file *file, 380276d9e82SJulius Niedworok const char __user *user_buf, 381276d9e82SJulius Niedworok size_t count, 382276d9e82SJulius Niedworok loff_t *ppos) 383276d9e82SJulius Niedworok { 384276d9e82SJulius Niedworok struct ieee80211_local *local = file->private_data; 385276d9e82SJulius Niedworok char buf[3]; 386276d9e82SJulius Niedworok 3876020d534SShayne Chen if (count >= sizeof(buf)) 388276d9e82SJulius Niedworok return -EINVAL; 389276d9e82SJulius Niedworok 390276d9e82SJulius Niedworok if (copy_from_user(buf, user_buf, count)) 391276d9e82SJulius Niedworok return -EFAULT; 392276d9e82SJulius Niedworok 3936020d534SShayne Chen if (count && buf[count - 1] == '\n') 3946020d534SShayne Chen buf[count - 1] = '\0'; 3956020d534SShayne Chen else 3966020d534SShayne Chen buf[count] = '\0'; 397276d9e82SJulius Niedworok 398276d9e82SJulius Niedworok if (buf[0] == '0' && buf[1] == '\0') 399276d9e82SJulius Niedworok local->force_tx_status = 0; 400276d9e82SJulius Niedworok else if (buf[0] == '1' && buf[1] == '\0') 401276d9e82SJulius Niedworok local->force_tx_status = 1; 402276d9e82SJulius Niedworok else 403276d9e82SJulius Niedworok return -EINVAL; 404276d9e82SJulius Niedworok 405276d9e82SJulius Niedworok return count; 406276d9e82SJulius Niedworok } 407276d9e82SJulius Niedworok 408276d9e82SJulius Niedworok static const struct file_operations force_tx_status_ops = { 409276d9e82SJulius Niedworok .write = force_tx_status_write, 410276d9e82SJulius Niedworok .read = force_tx_status_read, 411276d9e82SJulius Niedworok .open = simple_open, 412276d9e82SJulius Niedworok .llseek = default_llseek, 413276d9e82SJulius Niedworok }; 414276d9e82SJulius Niedworok 4152ad4814fSJohannes Berg #ifdef CONFIG_PM 416827b1fb4SJohannes Berg static ssize_t reset_write(struct file *file, const char __user *user_buf, 417827b1fb4SJohannes Berg size_t count, loff_t *ppos) 418827b1fb4SJohannes Berg { 419827b1fb4SJohannes Berg struct ieee80211_local *local = file->private_data; 420f5baf287SJohannes Berg int ret; 421827b1fb4SJohannes Berg 422827b1fb4SJohannes Berg rtnl_lock(); 423adaed1b9SJohannes Berg wiphy_lock(local->hw.wiphy); 424eecc4800SJohannes Berg __ieee80211_suspend(&local->hw, NULL); 425f5baf287SJohannes Berg ret = __ieee80211_resume(&local->hw); 426adaed1b9SJohannes Berg wiphy_unlock(local->hw.wiphy); 427f5baf287SJohannes Berg 428f5baf287SJohannes Berg if (ret) 429f5baf287SJohannes Berg cfg80211_shutdown_all_interfaces(local->hw.wiphy); 430f5baf287SJohannes Berg 431827b1fb4SJohannes Berg rtnl_unlock(); 432827b1fb4SJohannes Berg 433827b1fb4SJohannes Berg return count; 434827b1fb4SJohannes Berg } 435827b1fb4SJohannes Berg 436827b1fb4SJohannes Berg static const struct file_operations reset_ops = { 437827b1fb4SJohannes Berg .write = reset_write, 438234e3405SStephen Boyd .open = simple_open, 4396038f373SArnd Bergmann .llseek = noop_llseek, 440827b1fb4SJohannes Berg }; 4412ad4814fSJohannes Berg #endif 442827b1fb4SJohannes Berg 44368920c97SAndrey Ryabinin static const char *hw_flag_names[] = { 44430686bf7SJohannes Berg #define FLAG(F) [IEEE80211_HW_##F] = #F 44530686bf7SJohannes Berg FLAG(HAS_RATE_CONTROL), 44630686bf7SJohannes Berg FLAG(RX_INCLUDES_FCS), 44730686bf7SJohannes Berg FLAG(HOST_BROADCAST_PS_BUFFERING), 44830686bf7SJohannes Berg FLAG(SIGNAL_UNSPEC), 44930686bf7SJohannes Berg FLAG(SIGNAL_DBM), 45030686bf7SJohannes Berg FLAG(NEED_DTIM_BEFORE_ASSOC), 45130686bf7SJohannes Berg FLAG(SPECTRUM_MGMT), 45230686bf7SJohannes Berg FLAG(AMPDU_AGGREGATION), 45330686bf7SJohannes Berg FLAG(SUPPORTS_PS), 45430686bf7SJohannes Berg FLAG(PS_NULLFUNC_STACK), 45530686bf7SJohannes Berg FLAG(SUPPORTS_DYNAMIC_PS), 45630686bf7SJohannes Berg FLAG(MFP_CAPABLE), 45730686bf7SJohannes Berg FLAG(WANT_MONITOR_VIF), 45830686bf7SJohannes Berg FLAG(NO_AUTO_VIF), 45930686bf7SJohannes Berg FLAG(SW_CRYPTO_CONTROL), 46030686bf7SJohannes Berg FLAG(SUPPORT_FAST_XMIT), 46130686bf7SJohannes Berg FLAG(REPORTS_TX_ACK_STATUS), 46230686bf7SJohannes Berg FLAG(CONNECTION_MONITOR), 46330686bf7SJohannes Berg FLAG(QUEUE_CONTROL), 46430686bf7SJohannes Berg FLAG(SUPPORTS_PER_STA_GTK), 46530686bf7SJohannes Berg FLAG(AP_LINK_PS), 46630686bf7SJohannes Berg FLAG(TX_AMPDU_SETUP_IN_HW), 46730686bf7SJohannes Berg FLAG(SUPPORTS_RC_TABLE), 46830686bf7SJohannes Berg FLAG(P2P_DEV_ADDR_FOR_INTF), 46930686bf7SJohannes Berg FLAG(TIMING_BEACON_ONLY), 47030686bf7SJohannes Berg FLAG(SUPPORTS_HT_CCK_RATES), 47130686bf7SJohannes Berg FLAG(CHANCTX_STA_CSA), 47230686bf7SJohannes Berg FLAG(SUPPORTS_CLONED_SKBS), 47330686bf7SJohannes Berg FLAG(SINGLE_SCAN_ON_ALL_BANDS), 474b98fb44fSArik Nemtsov FLAG(TDLS_WIDER_BW), 47599e7ca44SEmmanuel Grumbach FLAG(SUPPORTS_AMSDU_IN_AMPDU), 47635afa588SHelmut Schaa FLAG(BEACON_TX_STATUS), 47731104891SJohannes Berg FLAG(NEEDS_UNIQUE_STA_ADDR), 478412a6d80SSara Sharon FLAG(SUPPORTS_REORDERING_BUFFER), 479c9c5962bSJohannes Berg FLAG(USES_RSS), 4806e0456b5SFelix Fietkau FLAG(TX_AMSDU), 4816e0456b5SFelix Fietkau FLAG(TX_FRAG_LIST), 482e8a24cd4SRajkumar Manoharan FLAG(REPORTS_LOW_ACK), 483f3fe4e93SSara Sharon FLAG(SUPPORTS_TX_FRAG), 484e2fb1b83SYingying Tang FLAG(SUPPORTS_TDLS_BUFFER_STA), 48594ba9271SIlan Peer FLAG(DEAUTH_NEED_MGD_TX_PREP), 4867c181f4fSBen Caradoc-Davies FLAG(DOESNT_SUPPORT_QOS_NDP), 487adf8ed01SJohannes Berg FLAG(BUFF_MMPDU_TXQ), 48809b4a4faSJohannes Berg FLAG(SUPPORTS_VHT_EXT_NSS_BW), 4890eeb2b67SSara Sharon FLAG(STA_MMPDU_TXQ), 49077f7ffdcSFelix Fietkau FLAG(TX_STATUS_NO_AMPDU_LEN), 491caf56338SSara Sharon FLAG(SUPPORTS_MULTI_BSSID), 492caf56338SSara Sharon FLAG(SUPPORTS_ONLY_HE_MULTI_BSSID), 493dc3998ecSAlexander Wetzel FLAG(AMPDU_KEYBORDER_SUPPORT), 4946aea26ceSFelix Fietkau FLAG(SUPPORTS_TX_ENCAP_OFFLOAD), 49580a915ecSFelix Fietkau FLAG(SUPPORTS_RX_DECAP_OFFLOAD), 49655f8205eSSriram R FLAG(SUPPORTS_CONC_MON_RX_DECAP), 4976d945a33SLorenzo Bianconi FLAG(DETECTS_COLOR_COLLISION), 498*963d0e8dSJohannes Berg FLAG(MLO_MCAST_MULTI_LINK_TX), 49930686bf7SJohannes Berg #undef FLAG 50030686bf7SJohannes Berg }; 50130686bf7SJohannes Berg 502279daf64SBen Greear static ssize_t hwflags_read(struct file *file, char __user *user_buf, 503279daf64SBen Greear size_t count, loff_t *ppos) 504279daf64SBen Greear { 505279daf64SBen Greear struct ieee80211_local *local = file->private_data; 50630686bf7SJohannes Berg size_t bufsz = 30 * NUM_IEEE80211_HW_FLAGS; 50730686bf7SJohannes Berg char *buf = kzalloc(bufsz, GFP_KERNEL); 50830686bf7SJohannes Berg char *pos = buf, *end = buf + bufsz - 1; 509279daf64SBen Greear ssize_t rv; 51030686bf7SJohannes Berg int i; 511279daf64SBen Greear 512d15b8459SJoe Perches if (!buf) 51330686bf7SJohannes Berg return -ENOMEM; 514d15b8459SJoe Perches 51530686bf7SJohannes Berg /* fail compilation if somebody adds or removes 51630686bf7SJohannes Berg * a flag without updating the name array above 51730686bf7SJohannes Berg */ 51868920c97SAndrey Ryabinin BUILD_BUG_ON(ARRAY_SIZE(hw_flag_names) != NUM_IEEE80211_HW_FLAGS); 51930686bf7SJohannes Berg 52030686bf7SJohannes Berg for (i = 0; i < NUM_IEEE80211_HW_FLAGS; i++) { 52130686bf7SJohannes Berg if (test_bit(i, local->hw.flags)) 5224633dfc3SMohammed Shafi Shajakhan pos += scnprintf(pos, end - pos, "%s\n", 52330686bf7SJohannes Berg hw_flag_names[i]); 52430686bf7SJohannes Berg } 525279daf64SBen Greear 526279daf64SBen Greear rv = simple_read_from_buffer(user_buf, count, ppos, buf, strlen(buf)); 527279daf64SBen Greear kfree(buf); 528279daf64SBen Greear return rv; 529279daf64SBen Greear } 530199d69f2SBenoit Papillault 5314a5eccaaSBen Greear static ssize_t misc_read(struct file *file, char __user *user_buf, 5324a5eccaaSBen Greear size_t count, loff_t *ppos) 5334a5eccaaSBen Greear { 5344a5eccaaSBen Greear struct ieee80211_local *local = file->private_data; 5354a5eccaaSBen Greear /* Max len of each line is 16 characters, plus 9 for 'pending:\n' */ 5364a5eccaaSBen Greear size_t bufsz = IEEE80211_MAX_QUEUES * 16 + 9; 537b2347a32SDan Carpenter char *buf; 538b2347a32SDan Carpenter char *pos, *end; 5394a5eccaaSBen Greear ssize_t rv; 5404a5eccaaSBen Greear int i; 5414a5eccaaSBen Greear int ln; 5424a5eccaaSBen Greear 543b2347a32SDan Carpenter buf = kzalloc(bufsz, GFP_KERNEL); 544b2347a32SDan Carpenter if (!buf) 545b2347a32SDan Carpenter return -ENOMEM; 546b2347a32SDan Carpenter 547b2347a32SDan Carpenter pos = buf; 548b2347a32SDan Carpenter end = buf + bufsz - 1; 549b2347a32SDan Carpenter 5504a5eccaaSBen Greear pos += scnprintf(pos, end - pos, "pending:\n"); 5514a5eccaaSBen Greear 5524a5eccaaSBen Greear for (i = 0; i < IEEE80211_MAX_QUEUES; i++) { 5534a5eccaaSBen Greear ln = skb_queue_len(&local->pending[i]); 5544a5eccaaSBen Greear pos += scnprintf(pos, end - pos, "[%i] %d\n", 5554a5eccaaSBen Greear i, ln); 5564a5eccaaSBen Greear } 5574a5eccaaSBen Greear 5584a5eccaaSBen Greear rv = simple_read_from_buffer(user_buf, count, ppos, buf, strlen(buf)); 5594a5eccaaSBen Greear kfree(buf); 5604a5eccaaSBen Greear return rv; 5614a5eccaaSBen Greear } 5624a5eccaaSBen Greear 563db2e6bd4SJohannes Berg static ssize_t queues_read(struct file *file, char __user *user_buf, 564db2e6bd4SJohannes Berg size_t count, loff_t *ppos) 565db2e6bd4SJohannes Berg { 566db2e6bd4SJohannes Berg struct ieee80211_local *local = file->private_data; 567db2e6bd4SJohannes Berg unsigned long flags; 568db2e6bd4SJohannes Berg char buf[IEEE80211_MAX_QUEUES * 20]; 569db2e6bd4SJohannes Berg int q, res = 0; 570db2e6bd4SJohannes Berg 571db2e6bd4SJohannes Berg spin_lock_irqsave(&local->queue_stop_reason_lock, flags); 572db2e6bd4SJohannes Berg for (q = 0; q < local->hw.queues; q++) 573db2e6bd4SJohannes Berg res += sprintf(buf + res, "%02d: %#.8lx/%d\n", q, 574db2e6bd4SJohannes Berg local->queue_stop_reasons[q], 5753b8d81e0SJohannes Berg skb_queue_len(&local->pending[q])); 576db2e6bd4SJohannes Berg spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags); 577db2e6bd4SJohannes Berg 578db2e6bd4SJohannes Berg return simple_read_from_buffer(user_buf, count, ppos, buf, res); 579db2e6bd4SJohannes Berg } 580db2e6bd4SJohannes Berg 581279daf64SBen Greear DEBUGFS_READONLY_FILE_OPS(hwflags); 582279daf64SBen Greear DEBUGFS_READONLY_FILE_OPS(queues); 5834a5eccaaSBen Greear DEBUGFS_READONLY_FILE_OPS(misc); 584db2e6bd4SJohannes Berg 585e9f207f0SJiri Benc /* statistics stuff */ 586e9f207f0SJiri Benc 587e9f207f0SJiri Benc static ssize_t format_devstat_counter(struct ieee80211_local *local, 588e9f207f0SJiri Benc char __user *userbuf, 589e9f207f0SJiri Benc size_t count, loff_t *ppos, 590e9f207f0SJiri Benc int (*printvalue)(struct ieee80211_low_level_stats *stats, char *buf, 591e9f207f0SJiri Benc int buflen)) 592e9f207f0SJiri Benc { 593e9f207f0SJiri Benc struct ieee80211_low_level_stats stats; 594e9f207f0SJiri Benc char buf[20]; 595e9f207f0SJiri Benc int res; 596e9f207f0SJiri Benc 59775636525SJohannes Berg rtnl_lock(); 59824487981SJohannes Berg res = drv_get_stats(local, &stats); 599e9f207f0SJiri Benc rtnl_unlock(); 60024487981SJohannes Berg if (res) 60124487981SJohannes Berg return res; 602e9f207f0SJiri Benc res = printvalue(&stats, buf, sizeof(buf)); 603e9f207f0SJiri Benc return simple_read_from_buffer(userbuf, count, ppos, buf, res); 604e9f207f0SJiri Benc } 605e9f207f0SJiri Benc 606e9f207f0SJiri Benc #define DEBUGFS_DEVSTATS_FILE(name) \ 607e9f207f0SJiri Benc static int print_devstats_##name(struct ieee80211_low_level_stats *stats,\ 608e9f207f0SJiri Benc char *buf, int buflen) \ 609e9f207f0SJiri Benc { \ 610e9f207f0SJiri Benc return scnprintf(buf, buflen, "%u\n", stats->name); \ 611e9f207f0SJiri Benc } \ 612e9f207f0SJiri Benc static ssize_t stats_ ##name## _read(struct file *file, \ 613e9f207f0SJiri Benc char __user *userbuf, \ 614e9f207f0SJiri Benc size_t count, loff_t *ppos) \ 615e9f207f0SJiri Benc { \ 616e9f207f0SJiri Benc return format_devstat_counter(file->private_data, \ 617e9f207f0SJiri Benc userbuf, \ 618e9f207f0SJiri Benc count, \ 619e9f207f0SJiri Benc ppos, \ 620e9f207f0SJiri Benc print_devstats_##name); \ 621e9f207f0SJiri Benc } \ 622e9f207f0SJiri Benc \ 623e9f207f0SJiri Benc static const struct file_operations stats_ ##name## _ops = { \ 624e9f207f0SJiri Benc .read = stats_ ##name## _read, \ 625234e3405SStephen Boyd .open = simple_open, \ 6262b18ab36SArnd Bergmann .llseek = generic_file_llseek, \ 627e9f207f0SJiri Benc }; 628e9f207f0SJiri Benc 629453a2a82SJohannes Berg #ifdef CONFIG_MAC80211_DEBUG_COUNTERS 630f1160434SJohannes Berg #define DEBUGFS_STATS_ADD(name) \ 631f1160434SJohannes Berg debugfs_create_u32(#name, 0400, statsd, &local->name); 632453a2a82SJohannes Berg #endif 6332826bcd8SFelix Fietkau #define DEBUGFS_DEVSTATS_ADD(name) \ 6347bcfaf2fSJohannes Berg debugfs_create_file(#name, 0400, statsd, local, &stats_ ##name## _ops); 635e9f207f0SJiri Benc 636e9f207f0SJiri Benc DEBUGFS_DEVSTATS_FILE(dot11ACKFailureCount); 637e9f207f0SJiri Benc DEBUGFS_DEVSTATS_FILE(dot11RTSFailureCount); 638e9f207f0SJiri Benc DEBUGFS_DEVSTATS_FILE(dot11FCSErrorCount); 639e9f207f0SJiri Benc DEBUGFS_DEVSTATS_FILE(dot11RTSSuccessCount); 640e9f207f0SJiri Benc 641e9f207f0SJiri Benc void debugfs_hw_add(struct ieee80211_local *local) 642e9f207f0SJiri Benc { 643e9f207f0SJiri Benc struct dentry *phyd = local->hw.wiphy->debugfsdir; 644e9f207f0SJiri Benc struct dentry *statsd; 645e9f207f0SJiri Benc 646e9f207f0SJiri Benc if (!phyd) 647e9f207f0SJiri Benc return; 648e9f207f0SJiri Benc 649e9f207f0SJiri Benc local->debugfs.keys = debugfs_create_dir("keys", phyd); 650e9f207f0SJiri Benc 651e9f207f0SJiri Benc DEBUGFS_ADD(total_ps_buffered); 652e9f207f0SJiri Benc DEBUGFS_ADD(wep_iv); 653bddb2afcSJohannes Berg DEBUGFS_ADD(rate_ctrl_alg); 654db2e6bd4SJohannes Berg DEBUGFS_ADD(queues); 6554a5eccaaSBen Greear DEBUGFS_ADD(misc); 6562ad4814fSJohannes Berg #ifdef CONFIG_PM 657827b1fb4SJohannes Berg DEBUGFS_ADD_MODE(reset, 0200); 6582ad4814fSJohannes Berg #endif 659279daf64SBen Greear DEBUGFS_ADD(hwflags); 66083bdf2a1SBen Greear DEBUGFS_ADD(user_power); 66183bdf2a1SBen Greear DEBUGFS_ADD(power); 662c90142a5SThomas Pedersen DEBUGFS_ADD(hw_conf); 663276d9e82SJulius Niedworok DEBUGFS_ADD_MODE(force_tx_status, 0600); 664e908435eSLorenzo Bianconi DEBUGFS_ADD_MODE(aql_enable, 0600); 6653db2c560SFelix Fietkau DEBUGFS_ADD(aql_pending); 6668d51dbb8SToke Høiland-Jørgensen 6678d51dbb8SToke Høiland-Jørgensen if (local->ops->wake_tx_queue) 6689399b86cSMichal Kazior DEBUGFS_ADD_MODE(aqm, 0600); 669e9f207f0SJiri Benc 670e322c07fSLorenzo Bianconi DEBUGFS_ADD_MODE(airtime_flags, 0600); 671b4809e94SToke Høiland-Jørgensen 6723ace10f5SKan Yan DEBUGFS_ADD(aql_txq_limit); 6733ace10f5SKan Yan debugfs_create_u32("aql_threshold", 0600, 6743ace10f5SKan Yan phyd, &local->aql_threshold); 6753ace10f5SKan Yan 676e9f207f0SJiri Benc statsd = debugfs_create_dir("statistics", phyd); 677e9f207f0SJiri Benc 678e9f207f0SJiri Benc /* if the dir failed, don't put all the other things into the root! */ 679e9f207f0SJiri Benc if (!statsd) 680e9f207f0SJiri Benc return; 681e9f207f0SJiri Benc 682c206ca67SJohannes Berg #ifdef CONFIG_MAC80211_DEBUG_COUNTERS 683f1160434SJohannes Berg DEBUGFS_STATS_ADD(dot11TransmittedFragmentCount); 684f1160434SJohannes Berg DEBUGFS_STATS_ADD(dot11MulticastTransmittedFrameCount); 685f1160434SJohannes Berg DEBUGFS_STATS_ADD(dot11FailedCount); 686f1160434SJohannes Berg DEBUGFS_STATS_ADD(dot11RetryCount); 687f1160434SJohannes Berg DEBUGFS_STATS_ADD(dot11MultipleRetryCount); 688f1160434SJohannes Berg DEBUGFS_STATS_ADD(dot11FrameDuplicateCount); 689f1160434SJohannes Berg DEBUGFS_STATS_ADD(dot11ReceivedFragmentCount); 690f1160434SJohannes Berg DEBUGFS_STATS_ADD(dot11MulticastReceivedFrameCount); 691f1160434SJohannes Berg DEBUGFS_STATS_ADD(dot11TransmittedFrameCount); 692f1160434SJohannes Berg DEBUGFS_STATS_ADD(tx_handlers_drop); 693f1160434SJohannes Berg DEBUGFS_STATS_ADD(tx_handlers_queued); 694f1160434SJohannes Berg DEBUGFS_STATS_ADD(tx_handlers_drop_wep); 695f1160434SJohannes Berg DEBUGFS_STATS_ADD(tx_handlers_drop_not_assoc); 696f1160434SJohannes Berg DEBUGFS_STATS_ADD(tx_handlers_drop_unauth_port); 697f1160434SJohannes Berg DEBUGFS_STATS_ADD(rx_handlers_drop); 698f1160434SJohannes Berg DEBUGFS_STATS_ADD(rx_handlers_queued); 699f1160434SJohannes Berg DEBUGFS_STATS_ADD(rx_handlers_drop_nullfunc); 700f1160434SJohannes Berg DEBUGFS_STATS_ADD(rx_handlers_drop_defrag); 701f1160434SJohannes Berg DEBUGFS_STATS_ADD(tx_expand_skb_head); 702f1160434SJohannes Berg DEBUGFS_STATS_ADD(tx_expand_skb_head_cloned); 703f1160434SJohannes Berg DEBUGFS_STATS_ADD(rx_expand_skb_head_defrag); 704f1160434SJohannes Berg DEBUGFS_STATS_ADD(rx_handlers_fragments); 705f1160434SJohannes Berg DEBUGFS_STATS_ADD(tx_status_drop); 706e9f207f0SJiri Benc #endif 7072826bcd8SFelix Fietkau DEBUGFS_DEVSTATS_ADD(dot11ACKFailureCount); 7082826bcd8SFelix Fietkau DEBUGFS_DEVSTATS_ADD(dot11RTSFailureCount); 7092826bcd8SFelix Fietkau DEBUGFS_DEVSTATS_ADD(dot11FCSErrorCount); 7102826bcd8SFelix Fietkau DEBUGFS_DEVSTATS_ADD(dot11RTSSuccessCount); 711e9f207f0SJiri Benc } 712