mac80211: remove extra patch accidentally added during rebase
Signed-off-by: Felix Fietkau <nbd@nbd.name>
This commit is contained in:
		| @@ -1,888 +0,0 @@ | |||||||
| From: Felix Fietkau <nbd@nbd.name> |  | ||||||
| Date: Mon, 1 Feb 2021 10:47:58 +0100 |  | ||||||
| Subject: [PATCH] mac80211: minstrel_ht: add debugfs monitoring/controlling |  | ||||||
|  API |  | ||||||
|  |  | ||||||
| This allows user space to monitor tx status and take over rate control |  | ||||||
| functionality. |  | ||||||
|  |  | ||||||
| Signed-off-by: Felix Fietkau <nbd@nbd.name> |  | ||||||
| --- |  | ||||||
|  create mode 100644 net/mac80211/rc80211_minstrel_ht_api.c |  | ||||||
|  |  | ||||||
| --- a/local-symbols |  | ||||||
| +++ b/local-symbols |  | ||||||
| @@ -49,6 +49,7 @@ LIB80211_DEBUG= |  | ||||||
|  MAC80211= |  | ||||||
|  MAC80211_HAS_RC= |  | ||||||
|  MAC80211_RC_MINSTREL= |  | ||||||
| +MAC80211_RC_MINSTREL_DEBUGFS_API= |  | ||||||
|  MAC80211_RC_DEFAULT_MINSTREL= |  | ||||||
|  MAC80211_RC_DEFAULT= |  | ||||||
|  MAC80211_MESH= |  | ||||||
| --- a/net/mac80211/Kconfig |  | ||||||
| +++ b/net/mac80211/Kconfig |  | ||||||
| @@ -29,6 +29,15 @@ config MAC80211_RC_MINSTREL |  | ||||||
|  	help |  | ||||||
|  	  This option enables the 'minstrel' TX rate control algorithm |  | ||||||
|   |  | ||||||
| +config MAC80211_RC_MINSTREL_DEBUGFS_API |  | ||||||
| +	bool "Minstrel debugfs userspace control API" |  | ||||||
| +	depends on MAC80211_RC_MINSTREL |  | ||||||
| +	depends on MAC80211_DEBUGFS |  | ||||||
| +	select RELAY |  | ||||||
| +	help |  | ||||||
| +	  This option creates debugfs files that allow user space to observe |  | ||||||
| +	  and/or control minstrel rate selection behavior |  | ||||||
| + |  | ||||||
|  choice |  | ||||||
|  	prompt "Default rate control algorithm" |  | ||||||
|  	depends on MAC80211_HAS_RC |  | ||||||
| --- a/net/mac80211/Makefile |  | ||||||
| +++ b/net/mac80211/Makefile |  | ||||||
| @@ -61,6 +61,9 @@ rc80211_minstrel-y := \ |  | ||||||
|  rc80211_minstrel-$(CPTCFG_MAC80211_DEBUGFS) += \ |  | ||||||
|  	rc80211_minstrel_ht_debugfs.o |  | ||||||
|   |  | ||||||
| +rc80211_minstrel-$(CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API) += \ |  | ||||||
| +	rc80211_minstrel_ht_api.o |  | ||||||
| + |  | ||||||
|  mac80211-$(CPTCFG_MAC80211_RC_MINSTREL) += $(rc80211_minstrel-y) |  | ||||||
|   |  | ||||||
|  ccflags-y += -DDEBUG |  | ||||||
| --- a/net/mac80211/rc80211_minstrel_ht.c |  | ||||||
| +++ b/net/mac80211/rc80211_minstrel_ht.c |  | ||||||
| @@ -276,7 +276,8 @@ static const u8 minstrel_sample_seq[] = |  | ||||||
|  }; |  | ||||||
|   |  | ||||||
|  static void |  | ||||||
| -minstrel_ht_update_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi); |  | ||||||
| +minstrel_ht_update_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, |  | ||||||
| +			 bool force); |  | ||||||
|   |  | ||||||
|  /* |  | ||||||
|   * Some VHT MCSes are invalid (when Ndbps / Nes is not an integer) |  | ||||||
| @@ -346,7 +347,7 @@ minstrel_vht_get_group_idx(struct ieee80 |  | ||||||
|   |  | ||||||
|  static struct minstrel_rate_stats * |  | ||||||
|  minstrel_ht_get_stats(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, |  | ||||||
| -		      struct ieee80211_tx_rate *rate) |  | ||||||
| +		      struct ieee80211_tx_rate *rate, u16 *dest_idx) |  | ||||||
|  { |  | ||||||
|  	int group, idx; |  | ||||||
|   |  | ||||||
| @@ -381,6 +382,7 @@ minstrel_ht_get_stats(struct minstrel_pr |  | ||||||
|   |  | ||||||
|  	idx = 0; |  | ||||||
|  out: |  | ||||||
| +	*dest_idx = MI_RATE(group, idx); |  | ||||||
|  	return &mi->groups[group].rates[idx]; |  | ||||||
|  } |  | ||||||
|   |  | ||||||
| @@ -1024,6 +1026,8 @@ minstrel_ht_update_stats(struct minstrel |  | ||||||
|  			tp_rate = tmp_legacy_tp_rate; |  | ||||||
|   |  | ||||||
|  		for (i = MCS_GROUP_RATES - 1; i >= 0; i--) { |  | ||||||
| +			bool changed; |  | ||||||
| + |  | ||||||
|  			if (!(mi->supported[group] & BIT(i))) |  | ||||||
|  				continue; |  | ||||||
|   |  | ||||||
| @@ -1031,7 +1035,11 @@ minstrel_ht_update_stats(struct minstrel |  | ||||||
|   |  | ||||||
|  			mrs = &mg->rates[i]; |  | ||||||
|  			mrs->retry_updated = false; |  | ||||||
| +			changed = mrs->attempts > 0; |  | ||||||
|  			minstrel_ht_calc_rate_stats(mp, mrs); |  | ||||||
| +			if (changed) |  | ||||||
| +				minstrel_ht_report_rate_update(mp, mi, index, |  | ||||||
| +							       mrs); |  | ||||||
|   |  | ||||||
|  			if (mrs->att_hist) |  | ||||||
|  				last_prob = max(last_prob, mrs->prob_avg); |  | ||||||
| @@ -1080,7 +1088,8 @@ minstrel_ht_update_stats(struct minstrel |  | ||||||
|   |  | ||||||
|  	mi->max_prob_rate = tmp_max_prob_rate; |  | ||||||
|   |  | ||||||
| -	minstrel_ht_refill_sample_rates(mi); |  | ||||||
| +	if (!minstrel_ht_manual_mode(mp)) |  | ||||||
| +		minstrel_ht_refill_sample_rates(mi); |  | ||||||
|   |  | ||||||
|  #ifdef CPTCFG_MAC80211_DEBUGFS |  | ||||||
|  	/* use fixed index if set */ |  | ||||||
| @@ -1177,6 +1186,7 @@ minstrel_ht_tx_status(void *priv, struct |  | ||||||
|  	struct minstrel_priv *mp = priv; |  | ||||||
|  	u32 update_interval = mp->update_interval; |  | ||||||
|  	bool last, update = false; |  | ||||||
| +	u16 rate_list[IEEE80211_TX_MAX_RATES] = {}; |  | ||||||
|  	int i; |  | ||||||
|   |  | ||||||
|  	/* This packet was aggregated but doesn't carry status info */ |  | ||||||
| @@ -1208,13 +1218,15 @@ minstrel_ht_tx_status(void *priv, struct |  | ||||||
|  		last = (i == IEEE80211_TX_MAX_RATES - 1) || |  | ||||||
|  		       !minstrel_ht_txstat_valid(mp, mi, &ar[i + 1]); |  | ||||||
|   |  | ||||||
| -		rate = minstrel_ht_get_stats(mp, mi, &ar[i]); |  | ||||||
| +		rate = minstrel_ht_get_stats(mp, mi, &ar[i], &rate_list[i]); |  | ||||||
|  		if (last) |  | ||||||
|  			rate->success += info->status.ampdu_ack_len; |  | ||||||
|   |  | ||||||
|  		rate->attempts += ar[i].count * info->status.ampdu_len; |  | ||||||
|  	} |  | ||||||
|   |  | ||||||
| +	minstrel_ht_report_tx_status(mp, mi, info, rate_list, i); |  | ||||||
| + |  | ||||||
|  	if (mp->hw->max_rates > 1) { |  | ||||||
|  		/* |  | ||||||
|  		 * check for sudden death of spatial multiplexing, |  | ||||||
| @@ -1236,7 +1248,7 @@ minstrel_ht_tx_status(void *priv, struct |  | ||||||
|  	} |  | ||||||
|   |  | ||||||
|  	if (update) |  | ||||||
| -		minstrel_ht_update_rates(mp, mi); |  | ||||||
| +		minstrel_ht_update_rates(mp, mi, false); |  | ||||||
|  } |  | ||||||
|   |  | ||||||
|  static void |  | ||||||
| @@ -1299,7 +1311,7 @@ minstrel_calc_retransmit(struct minstrel |  | ||||||
|  } |  | ||||||
|   |  | ||||||
|   |  | ||||||
| -static void |  | ||||||
| +void |  | ||||||
|  minstrel_ht_set_rate(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, |  | ||||||
|                       struct ieee80211_sta_rates *ratetbl, int offset, int index) |  | ||||||
|  { |  | ||||||
| @@ -1408,11 +1420,15 @@ minstrel_ht_get_max_amsdu_len(struct min |  | ||||||
|  } |  | ||||||
|   |  | ||||||
|  static void |  | ||||||
| -minstrel_ht_update_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) |  | ||||||
| +minstrel_ht_update_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, |  | ||||||
| +			 bool force) |  | ||||||
|  { |  | ||||||
|  	struct ieee80211_sta_rates *rates; |  | ||||||
|  	int i = 0; |  | ||||||
|   |  | ||||||
| +	if (minstrel_ht_manual_mode(mp) && !force) |  | ||||||
| +		return; |  | ||||||
| + |  | ||||||
|  	rates = kzalloc(sizeof(*rates), GFP_ATOMIC); |  | ||||||
|  	if (!rates) |  | ||||||
|  		return; |  | ||||||
| @@ -1439,7 +1455,7 @@ minstrel_ht_get_sample_rate(struct minst |  | ||||||
|  { |  | ||||||
|  	u8 seq; |  | ||||||
|   |  | ||||||
| -	if (mp->hw->max_rates > 1) { |  | ||||||
| +	if (mp->hw->max_rates > 1 && !minstrel_ht_manual_mode(mp)) { |  | ||||||
|  		seq = mi->sample_seq; |  | ||||||
|  		mi->sample_seq = (seq + 1) % ARRAY_SIZE(minstrel_sample_seq); |  | ||||||
|  		seq = minstrel_sample_seq[seq]; |  | ||||||
| @@ -1689,7 +1705,9 @@ minstrel_ht_update_caps(void *priv, stru |  | ||||||
|   |  | ||||||
|  	/* create an initial rate table with the lowest supported rates */ |  | ||||||
|  	minstrel_ht_update_stats(mp, mi); |  | ||||||
| -	minstrel_ht_update_rates(mp, mi); |  | ||||||
| +	minstrel_ht_update_rates(mp, mi, true); |  | ||||||
| + |  | ||||||
| +	minstrel_ht_sta_update(mp, mi); |  | ||||||
|  } |  | ||||||
|   |  | ||||||
|  static void |  | ||||||
| @@ -1725,12 +1743,18 @@ minstrel_ht_alloc_sta(void *priv, struct |  | ||||||
|  			max_rates = sband->n_bitrates; |  | ||||||
|  	} |  | ||||||
|   |  | ||||||
| -	return kzalloc(sizeof(*mi), gfp); |  | ||||||
| +	mi = kzalloc(sizeof(*mi), gfp); |  | ||||||
| +#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API |  | ||||||
| +	INIT_LIST_HEAD(&mi->list); |  | ||||||
| +#endif |  | ||||||
| + |  | ||||||
| +	return mi; |  | ||||||
|  } |  | ||||||
|   |  | ||||||
|  static void |  | ||||||
|  minstrel_ht_free_sta(void *priv, struct ieee80211_sta *sta, void *priv_sta) |  | ||||||
|  { |  | ||||||
| +	minstrel_ht_sta_remove(priv, priv_sta); |  | ||||||
|  	kfree(priv_sta); |  | ||||||
|  } |  | ||||||
|   |  | ||||||
| @@ -1841,12 +1865,14 @@ static void minstrel_ht_add_debugfs(stru |  | ||||||
|  	mp->fixed_rate_idx = (u32) -1; |  | ||||||
|  	debugfs_create_u32("fixed_rate_idx", S_IRUGO | S_IWUGO, debugfsdir, |  | ||||||
|  			   &mp->fixed_rate_idx); |  | ||||||
| +	minstrel_ht_add_debugfs_api(hw, priv, debugfsdir); |  | ||||||
|  } |  | ||||||
|  #endif |  | ||||||
|   |  | ||||||
|  static void |  | ||||||
|  minstrel_ht_free(void *priv) |  | ||||||
|  { |  | ||||||
| +	minstrel_ht_remove_debugfs_api(priv); |  | ||||||
|  	kfree(priv); |  | ||||||
|  } |  | ||||||
|   |  | ||||||
| --- a/net/mac80211/rc80211_minstrel_ht.h |  | ||||||
| +++ b/net/mac80211/rc80211_minstrel_ht.h |  | ||||||
| @@ -72,6 +72,10 @@ |  | ||||||
|  #define MINSTREL_SAMPLE_RATES		5 /* rates per sample type */ |  | ||||||
|  #define MINSTREL_SAMPLE_INTERVAL	(HZ / 50) |  | ||||||
|   |  | ||||||
| +#define MINSTREL_MONITOR_STA		BIT(0) |  | ||||||
| +#define MINSTREL_MONITOR_TXS		BIT(1) |  | ||||||
| +#define MINSTREL_MONITOR_STATS		BIT(2) |  | ||||||
| + |  | ||||||
|  struct minstrel_priv { |  | ||||||
|  	struct ieee80211_hw *hw; |  | ||||||
|  	bool has_mrr; |  | ||||||
| @@ -93,6 +97,13 @@ struct minstrel_priv { |  | ||||||
|  	 */ |  | ||||||
|  	u32 fixed_rate_idx; |  | ||||||
|  #endif |  | ||||||
| +#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API |  | ||||||
| +	struct rchan *relay_ev; |  | ||||||
| +	struct list_head stations; |  | ||||||
| +	spinlock_t lock; |  | ||||||
| +	u8 monitor; |  | ||||||
| +	bool manual; |  | ||||||
| +#endif |  | ||||||
|  }; |  | ||||||
|   |  | ||||||
|   |  | ||||||
| @@ -153,6 +164,9 @@ struct minstrel_sample_category { |  | ||||||
|  }; |  | ||||||
|   |  | ||||||
|  struct minstrel_ht_sta { |  | ||||||
| +#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API |  | ||||||
| +	struct list_head list; |  | ||||||
| +#endif |  | ||||||
|  	struct ieee80211_sta *sta; |  | ||||||
|   |  | ||||||
|  	/* ampdu length (average, per sampling interval) */ |  | ||||||
| @@ -197,6 +211,80 @@ struct minstrel_ht_sta { |  | ||||||
|  }; |  | ||||||
|   |  | ||||||
|  void minstrel_ht_add_sta_debugfs(void *priv, void *priv_sta, struct dentry *dir); |  | ||||||
| + |  | ||||||
| +#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API |  | ||||||
| +void minstrel_ht_sta_update(struct minstrel_priv *mp, struct minstrel_ht_sta *mi); |  | ||||||
| +void minstrel_ht_sta_remove(struct minstrel_priv *mp, struct minstrel_ht_sta *mi); |  | ||||||
| +void __minstrel_ht_report_tx_status(struct minstrel_priv *mp, |  | ||||||
| +				    struct minstrel_ht_sta *mi, |  | ||||||
| +				    struct ieee80211_tx_info *info, |  | ||||||
| +				    u16 *rate_list, int n_rates); |  | ||||||
| +void __minstrel_ht_report_rate_update(struct minstrel_priv *mp, |  | ||||||
| +				      struct minstrel_ht_sta *mi, u16 rate, |  | ||||||
| +				      struct minstrel_rate_stats *mrs); |  | ||||||
| +void minstrel_ht_add_debugfs_api(struct ieee80211_hw *hw, void *priv, |  | ||||||
| +				 struct dentry *dir); |  | ||||||
| +void minstrel_ht_remove_debugfs_api(void *priv); |  | ||||||
| +#else |  | ||||||
| +static inline void |  | ||||||
| +minstrel_ht_sta_update(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) |  | ||||||
| +{ |  | ||||||
| +} |  | ||||||
| +static inline void |  | ||||||
| +minstrel_ht_sta_remove(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) |  | ||||||
| +{ |  | ||||||
| +} |  | ||||||
| +static inline void |  | ||||||
| +minstrel_ht_add_debugfs_api(struct ieee80211_hw *hw, void *priv, |  | ||||||
| +			    struct dentry *dir) |  | ||||||
| +{ |  | ||||||
| +} |  | ||||||
| +static inline void |  | ||||||
| +minstrel_ht_remove_debugfs_api(void *priv) |  | ||||||
| +{ |  | ||||||
| +} |  | ||||||
| +#endif |  | ||||||
| + |  | ||||||
| +static inline void |  | ||||||
| +minstrel_ht_report_tx_status(struct minstrel_priv *mp, |  | ||||||
| +			     struct minstrel_ht_sta *mi, |  | ||||||
| +			     struct ieee80211_tx_info *info, |  | ||||||
| +			     u16 *rate_list, int n_rates) |  | ||||||
| +{ |  | ||||||
| +#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API |  | ||||||
| +	if (!(mp->monitor & MINSTREL_MONITOR_TXS)) |  | ||||||
| +		return; |  | ||||||
| + |  | ||||||
| +	__minstrel_ht_report_tx_status(mp, mi, info, rate_list, n_rates); |  | ||||||
| +#endif |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static inline void |  | ||||||
| +minstrel_ht_report_rate_update(struct minstrel_priv *mp, |  | ||||||
| +			       struct minstrel_ht_sta *mi, u16 rate, |  | ||||||
| +			       struct minstrel_rate_stats *mrs) |  | ||||||
| +{ |  | ||||||
| +#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API |  | ||||||
| +	if (!(mp->monitor & MINSTREL_MONITOR_STATS)) |  | ||||||
| +		return; |  | ||||||
| + |  | ||||||
| +	__minstrel_ht_report_rate_update(mp, mi, rate, mrs); |  | ||||||
| +#endif |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static inline bool |  | ||||||
| +minstrel_ht_manual_mode(struct minstrel_priv *mp) |  | ||||||
| +{ |  | ||||||
| +#ifdef CPTCFG_MAC80211_RC_MINSTREL_DEBUGFS_API |  | ||||||
| +	return mp->manual; |  | ||||||
| +#else |  | ||||||
| +	return false; |  | ||||||
| +#endif |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +void minstrel_ht_set_rate(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, |  | ||||||
| +						  struct ieee80211_sta_rates *ratetbl, int offset, |  | ||||||
| +						  int index); |  | ||||||
|  int minstrel_ht_get_tp_avg(struct minstrel_ht_sta *mi, int group, int rate, |  | ||||||
|  			   int prob_avg); |  | ||||||
|   |  | ||||||
| --- /dev/null |  | ||||||
| +++ b/net/mac80211/rc80211_minstrel_ht_api.c |  | ||||||
| @@ -0,0 +1,540 @@ |  | ||||||
| +// SPDX-License-Identifier: GPL-2.0-only |  | ||||||
| +/* |  | ||||||
| + * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name> |  | ||||||
| + */ |  | ||||||
| +#include <linux/kernel.h> |  | ||||||
| +#include <linux/debugfs.h> |  | ||||||
| +#include <linux/relay.h> |  | ||||||
| +#include <net/mac80211.h> |  | ||||||
| +#include "rc80211_minstrel_ht.h" |  | ||||||
| + |  | ||||||
| +enum sta_cmd { |  | ||||||
| +	STA_CMD_PROBE, |  | ||||||
| +	STA_CMD_RATES, |  | ||||||
| +}; |  | ||||||
| + |  | ||||||
| +static void |  | ||||||
| +minstrel_ht_print_rate_durations(struct seq_file *s, int group) |  | ||||||
| +{ |  | ||||||
| +	const struct mcs_group *g = &minstrel_mcs_groups[group]; |  | ||||||
| +	int n_rates; |  | ||||||
| +	int i; |  | ||||||
| + |  | ||||||
| +	if (g->flags & IEEE80211_TX_RC_VHT_MCS) |  | ||||||
| +		n_rates = 10; |  | ||||||
| +	else |  | ||||||
| +		n_rates = 8; |  | ||||||
| + |  | ||||||
| +	seq_printf(s, "%x", g->duration[0] << g->shift); |  | ||||||
| +	for (i = 1; i < n_rates; i++) |  | ||||||
| +		seq_printf(s, ",%x", g->duration[i] << g->shift); |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static int |  | ||||||
| +minstrel_ht_read_api_info(struct seq_file *s, void *data) |  | ||||||
| +{ |  | ||||||
| +	int i; |  | ||||||
| + |  | ||||||
| +	seq_printf(s, "#group;index;offset;type;nss;bw;gi;airtime\n"); |  | ||||||
| +	seq_printf(s, "#sta;action;macaddr;overhead_mcs;overhead_legacy;supported\n"); |  | ||||||
| +	seq_printf(s, "#txs;macaddr;num_frames;num_acked;probe;rates;counts\n"); |  | ||||||
| +	seq_printf(s, "#stats;macaddr;rate;avg_prob;avg_tp;cur_success;cur_attempts;hist_success;hist_attempts\n"); |  | ||||||
| +	seq_printf(s, "#rates;macaddr;rates;counts\n"); |  | ||||||
| +	seq_printf(s, "#probe;macaddr;rate\n"); |  | ||||||
| +	for (i = 0; i < MINSTREL_GROUPS_NB; i++) { |  | ||||||
| +		const struct mcs_group *g = &minstrel_mcs_groups[i]; |  | ||||||
| +		const char *type; |  | ||||||
| + |  | ||||||
| +		if (i == MINSTREL_CCK_GROUP) |  | ||||||
| +			type = "cck"; |  | ||||||
| +		else if (i == MINSTREL_OFDM_GROUP) |  | ||||||
| +			type = "ofdm"; |  | ||||||
| +		else if (g->flags & IEEE80211_TX_RC_VHT_MCS) |  | ||||||
| +			type = "vht"; |  | ||||||
| +		else |  | ||||||
| +			type = "ht"; |  | ||||||
| + |  | ||||||
| +		seq_printf(s, "group;%x;%x;%s;%x;%x;%x;", |  | ||||||
| +			   i, (u32) MI_RATE(i, 0), type, g->streams, g->bw, |  | ||||||
| +			   !!(g->flags & IEEE80211_TX_RC_SHORT_GI)); |  | ||||||
| +		minstrel_ht_print_rate_durations(s, i); |  | ||||||
| +		seq_printf(s, "\n"); |  | ||||||
| +	} |  | ||||||
| + |  | ||||||
| +	return 0; |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static struct dentry * |  | ||||||
| +create_buf_file_cb(const char *filename, struct dentry *parent, umode_t mode, |  | ||||||
| +		   struct rchan_buf *buf, int *is_global) |  | ||||||
| +{ |  | ||||||
| +	struct dentry *f; |  | ||||||
| + |  | ||||||
| +	f = debugfs_create_file("api_event", mode, parent, buf, |  | ||||||
| +				&relay_file_operations); |  | ||||||
| +	if (IS_ERR(f)) |  | ||||||
| +		return NULL; |  | ||||||
| + |  | ||||||
| +	*is_global = 1; |  | ||||||
| + |  | ||||||
| +	return f; |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static int |  | ||||||
| +remove_buf_file_cb(struct dentry *f) |  | ||||||
| +{ |  | ||||||
| +	debugfs_remove(f); |  | ||||||
| + |  | ||||||
| +	return 0; |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static struct rchan_callbacks relay_ev_cb = { |  | ||||||
| +	.create_buf_file = create_buf_file_cb, |  | ||||||
| +	.remove_buf_file = remove_buf_file_cb, |  | ||||||
| +}; |  | ||||||
| + |  | ||||||
| +static void |  | ||||||
| +minstrel_ht_dump_sta(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, |  | ||||||
| +		     const char *type) |  | ||||||
| +{ |  | ||||||
| +	char info[64 + MINSTREL_GROUPS_NB * 4]; |  | ||||||
| +	int ofs = 0; |  | ||||||
| +	int i; |  | ||||||
| + |  | ||||||
| +	ofs += scnprintf(info + ofs, sizeof(info) - ofs, "%llx;sta;%s;%pM;%x;%x;", |  | ||||||
| +			(unsigned long long)ktime_get_boottime_ns(), |  | ||||||
| +			 type, mi->sta->addr, mi->overhead, mi->overhead_legacy); |  | ||||||
| + |  | ||||||
| +	ofs += scnprintf(info + ofs, sizeof(info) - ofs, "%x", |  | ||||||
| +			 mi->supported[0]); |  | ||||||
| +	for (i = 1; i < MINSTREL_GROUPS_NB; i++) |  | ||||||
| +		ofs += scnprintf(info + ofs, sizeof(info) - ofs, ",%x", |  | ||||||
| +				 mi->supported[i]); |  | ||||||
| + |  | ||||||
| +	ofs += scnprintf(info + ofs, sizeof(info) - ofs, "\n"); |  | ||||||
| +	relay_write(mp->relay_ev, info, ofs); |  | ||||||
| +	relay_flush(mp->relay_ev); |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static void |  | ||||||
| +__minstrel_ht_dump_stations(struct minstrel_priv *mp, const char *type) |  | ||||||
| +{ |  | ||||||
| +	struct minstrel_ht_sta *mi; |  | ||||||
| + |  | ||||||
| +	list_for_each_entry(mi, &mp->stations, list) |  | ||||||
| +		minstrel_ht_dump_sta(mp, mi, type); |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static void |  | ||||||
| +minstrel_ht_dump_stations(struct minstrel_priv *mp) |  | ||||||
| +{ |  | ||||||
| +	spin_lock_bh(&mp->lock); |  | ||||||
| +	__minstrel_ht_dump_stations(mp, "dump"); |  | ||||||
| +	spin_unlock_bh(&mp->lock); |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static void |  | ||||||
| +minstrel_ht_api_start(struct minstrel_priv *mp, char *params) |  | ||||||
| +{ |  | ||||||
| +	char *cur; |  | ||||||
| +	u8 mask = 0; |  | ||||||
| + |  | ||||||
| +	spin_lock_bh(&mp->lock); |  | ||||||
| + |  | ||||||
| +	while ((cur = strsep(¶ms, ";")) != NULL) { |  | ||||||
| +		if (!strlen(cur)) |  | ||||||
| +			break; |  | ||||||
| + |  | ||||||
| +		if (!strcmp(cur, "txs")) |  | ||||||
| +			mask |= MINSTREL_MONITOR_TXS; |  | ||||||
| +		else if (!strcmp(cur, "sta")) |  | ||||||
| +			mask |= MINSTREL_MONITOR_STA; |  | ||||||
| +		else if (!strcmp(cur, "stats")) |  | ||||||
| +			mask |= MINSTREL_MONITOR_STATS; |  | ||||||
| +	} |  | ||||||
| + |  | ||||||
| +	if (!mask) |  | ||||||
| +		mask = MINSTREL_MONITOR_TXS; |  | ||||||
| + |  | ||||||
| +	if (!mp->monitor) |  | ||||||
| +		__minstrel_ht_dump_stations(mp, "add"); |  | ||||||
| +	mp->monitor = mask | MINSTREL_MONITOR_STA; |  | ||||||
| + |  | ||||||
| +	spin_unlock_bh(&mp->lock); |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static void |  | ||||||
| +minstrel_ht_api_stop(struct minstrel_priv *mp) |  | ||||||
| +{ |  | ||||||
| +	spin_lock_bh(&mp->lock); |  | ||||||
| +	mp->monitor = 0; |  | ||||||
| +	relay_reset(mp->relay_ev); |  | ||||||
| +	spin_unlock_bh(&mp->lock); |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static void |  | ||||||
| +minstrel_ht_reset_sample_table(struct minstrel_ht_sta *mi) |  | ||||||
| +{ |  | ||||||
| +	memset(mi->sample[MINSTREL_SAMPLE_TYPE_INC].sample_rates, 0, |  | ||||||
| +	       sizeof(mi->sample[MINSTREL_SAMPLE_TYPE_INC].sample_rates)); |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static void |  | ||||||
| +minstrel_ht_api_set_manual(struct minstrel_priv *mp, bool manual) |  | ||||||
| +{ |  | ||||||
| +	struct minstrel_ht_sta *mi; |  | ||||||
| + |  | ||||||
| +	mp->manual = manual; |  | ||||||
| + |  | ||||||
| +	spin_lock_bh(&mp->lock); |  | ||||||
| +	list_for_each_entry(mi, &mp->stations, list) |  | ||||||
| +		minstrel_ht_reset_sample_table(mi); |  | ||||||
| +	spin_unlock_bh(&mp->lock); |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static struct minstrel_ht_sta * |  | ||||||
| +minstrel_ht_api_get_sta(struct minstrel_priv *mp, const u8 *macaddr) |  | ||||||
| +{ |  | ||||||
| +	struct minstrel_ht_sta *mi; |  | ||||||
| + |  | ||||||
| +	list_for_each_entry(mi, &mp->stations, list) { |  | ||||||
| +		if (!memcmp(mi->sta->addr, macaddr, ETH_ALEN)) |  | ||||||
| +			return mi; |  | ||||||
| +	} |  | ||||||
| + |  | ||||||
| +	return NULL; |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static int |  | ||||||
| +minstrel_ht_get_args(char **dest, int dest_size, char *str, char *sep) |  | ||||||
| +{ |  | ||||||
| +	int i, n; |  | ||||||
| + |  | ||||||
| +	for (i = 0, n = 0; i < dest_size; i++) { |  | ||||||
| +		if (!str) { |  | ||||||
| +			dest[i] = NULL; |  | ||||||
| +			continue; |  | ||||||
| +		} |  | ||||||
| + |  | ||||||
| +		dest[i] = strsep(&str, sep); |  | ||||||
| +		if (dest[i]) |  | ||||||
| +			n++; |  | ||||||
| +	} |  | ||||||
| + |  | ||||||
| +	return n; |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static bool |  | ||||||
| +minstrel_ht_valid_rate(struct minstrel_ht_sta *mi, u32 rate) |  | ||||||
| +{ |  | ||||||
| +	int group, idx; |  | ||||||
| + |  | ||||||
| +	group = MI_RATE_GROUP(rate); |  | ||||||
| +	if (group >= MINSTREL_GROUPS_NB) |  | ||||||
| +		return false; |  | ||||||
| + |  | ||||||
| +	idx = MI_RATE_IDX(rate); |  | ||||||
| + |  | ||||||
| +	return !!(mi->supported[group] & BIT(idx)); |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static int |  | ||||||
| +minstrel_ht_rate_from_str(struct minstrel_ht_sta *mi, const char *str) |  | ||||||
| +{ |  | ||||||
| +	unsigned int rate; |  | ||||||
| + |  | ||||||
| +	if (kstrtouint(str, 16, &rate)) |  | ||||||
| +		return -EINVAL; |  | ||||||
| + |  | ||||||
| +	if (!minstrel_ht_valid_rate(mi, rate)) |  | ||||||
| +		return -EINVAL; |  | ||||||
| + |  | ||||||
| +	return rate; |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static int |  | ||||||
| +minstrel_ht_set_probe_rate(struct minstrel_ht_sta *mi, const char *rate_str) |  | ||||||
| +{ |  | ||||||
| +	u16 *sample_rates; |  | ||||||
| +	int rate, i; |  | ||||||
| + |  | ||||||
| +	if (!rate_str) |  | ||||||
| +		return -EINVAL; |  | ||||||
| + |  | ||||||
| +	rate = minstrel_ht_rate_from_str(mi, rate_str); |  | ||||||
| +	if (rate < 0) |  | ||||||
| +		return rate; |  | ||||||
| + |  | ||||||
| +	sample_rates = mi->sample[MINSTREL_SAMPLE_TYPE_INC].sample_rates; |  | ||||||
| +	for (i = 0; i < MINSTREL_SAMPLE_RATES; i++) { |  | ||||||
| +		if (sample_rates[i]) |  | ||||||
| +			continue; |  | ||||||
| + |  | ||||||
| +		sample_rates[i] = rate; |  | ||||||
| +		mi->sample_time = jiffies; |  | ||||||
| +		return 0; |  | ||||||
| +	} |  | ||||||
| + |  | ||||||
| +	return -ENOSPC; |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static int |  | ||||||
| +minstrel_ht_set_rates(struct minstrel_priv *mp, struct minstrel_ht_sta *mi, |  | ||||||
| +		      char *rate_str, char *count_str) |  | ||||||
| +{ |  | ||||||
| +	struct ieee80211_sta_rates *ratetbl; |  | ||||||
| +	unsigned int count; |  | ||||||
| +	char *countlist[4]; |  | ||||||
| +	char *ratelist[4]; |  | ||||||
| +	int rate; |  | ||||||
| +	int n_rates; |  | ||||||
| +	int n_count; |  | ||||||
| +	int err = -EINVAL; |  | ||||||
| +	int i; |  | ||||||
| + |  | ||||||
| +	if (!rate_str || !count_str) |  | ||||||
| +		return -EINVAL; |  | ||||||
| + |  | ||||||
| +	ratetbl = kzalloc(sizeof(*ratetbl), GFP_ATOMIC); |  | ||||||
| +	if (!ratetbl) |  | ||||||
| +		return -ENOMEM; |  | ||||||
| + |  | ||||||
| +	n_rates = minstrel_ht_get_args(ratelist, ARRAY_SIZE(ratelist), |  | ||||||
| +				       rate_str, ","); |  | ||||||
| +	n_count = minstrel_ht_get_args(countlist, ARRAY_SIZE(countlist), |  | ||||||
| +				       count_str, ","); |  | ||||||
| +	for (i = 0; i < min(n_rates, n_count); i++) { |  | ||||||
| +		rate = minstrel_ht_rate_from_str(mi, ratelist[i]); |  | ||||||
| +		if (rate < 0) |  | ||||||
| +			goto error; |  | ||||||
| + |  | ||||||
| +		if (kstrtouint(countlist[0], 16, &count)) |  | ||||||
| +			goto error; |  | ||||||
| + |  | ||||||
| +		minstrel_ht_set_rate(mp, mi, ratetbl, i, rate); |  | ||||||
| +		ratetbl->rate[i].count = count; |  | ||||||
| +		ratetbl->rate[i].count_rts = count; |  | ||||||
| +		ratetbl->rate[i].count_cts = count; |  | ||||||
| +	} |  | ||||||
| + |  | ||||||
| +	rate_control_set_rates(mp->hw, mi->sta, ratetbl); |  | ||||||
| + |  | ||||||
| +	return 0; |  | ||||||
| + |  | ||||||
| +error: |  | ||||||
| +	kfree(ratetbl); |  | ||||||
| +	return err; |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static int |  | ||||||
| +minstrel_ht_api_sta_cmd(struct minstrel_priv *mp, enum sta_cmd cmd, |  | ||||||
| +			char *arg_str) |  | ||||||
| +{ |  | ||||||
| +	struct minstrel_ht_sta *mi; |  | ||||||
| +	uint8_t macaddr[ETH_ALEN]; |  | ||||||
| +	char *args[3]; |  | ||||||
| +	int n_args; |  | ||||||
| +	int ret = -EINVAL; |  | ||||||
| + |  | ||||||
| +	spin_lock_bh(&mp->lock); |  | ||||||
| +	if (!mp->manual) |  | ||||||
| +		goto out; |  | ||||||
| + |  | ||||||
| +	n_args = minstrel_ht_get_args(args, ARRAY_SIZE(args), arg_str, ";"); |  | ||||||
| +	if (!args[0]) |  | ||||||
| +		goto out; |  | ||||||
| + |  | ||||||
| +	if (!mac_pton(args[0], macaddr)) |  | ||||||
| +		goto out; |  | ||||||
| + |  | ||||||
| +	mi = minstrel_ht_api_get_sta(mp, macaddr); |  | ||||||
| +	if (!mi) { |  | ||||||
| +		ret = -ENOENT; |  | ||||||
| +		goto out; |  | ||||||
| +	} |  | ||||||
| + |  | ||||||
| +	switch (cmd) { |  | ||||||
| +	case STA_CMD_PROBE: |  | ||||||
| +		ret = minstrel_ht_set_probe_rate(mi, args[1]); |  | ||||||
| +		break; |  | ||||||
| +	case STA_CMD_RATES: |  | ||||||
| +		ret = minstrel_ht_set_rates(mp, mi, args[1], args[2]); |  | ||||||
| +		break; |  | ||||||
| +	} |  | ||||||
| + |  | ||||||
| +out: |  | ||||||
| +	spin_unlock_bh(&mp->lock); |  | ||||||
| + |  | ||||||
| +	return ret; |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static ssize_t |  | ||||||
| +minstrel_ht_control_write(struct file *file, const char __user *userbuf, |  | ||||||
| +			  size_t count, loff_t *ppos) |  | ||||||
| +{ |  | ||||||
| +	struct minstrel_priv *mp = file->private_data; |  | ||||||
| +	char *pos, *cur; |  | ||||||
| +	char buf[64]; |  | ||||||
| +	size_t len = count; |  | ||||||
| +	int err; |  | ||||||
| + |  | ||||||
| +	if (len > sizeof(buf) - 1) |  | ||||||
| +		return -EINVAL; |  | ||||||
| + |  | ||||||
| +	if (copy_from_user(buf, userbuf, len)) |  | ||||||
| +		return -EFAULT; |  | ||||||
| + |  | ||||||
| +	if (count > 0 && buf[len - 1] == '\n') |  | ||||||
| +		len--; |  | ||||||
| + |  | ||||||
| +	buf[len] = 0; |  | ||||||
| +	if (!len) |  | ||||||
| +		return count; |  | ||||||
| + |  | ||||||
| +	pos = buf; |  | ||||||
| +	cur = strsep(&pos, ";"); |  | ||||||
| + |  | ||||||
| +	err = 0; |  | ||||||
| +	if (!strcmp(cur, "dump")) |  | ||||||
| +		minstrel_ht_dump_stations(mp); |  | ||||||
| +	else if (!strcmp(cur, "start")) |  | ||||||
| +		minstrel_ht_api_start(mp, pos); |  | ||||||
| +	else if (!strcmp(cur, "stop")) |  | ||||||
| +		minstrel_ht_api_stop(mp); |  | ||||||
| +	else if (!strcmp(cur, "manual")) |  | ||||||
| +		minstrel_ht_api_set_manual(mp, true); |  | ||||||
| +	else if (!strcmp(cur, "auto")) |  | ||||||
| +		minstrel_ht_api_set_manual(mp, false); |  | ||||||
| +	else if (!strcmp(cur, "rates")) |  | ||||||
| +		err = minstrel_ht_api_sta_cmd(mp, STA_CMD_RATES, pos); |  | ||||||
| +	else if (!strcmp(cur, "probe")) |  | ||||||
| +		err = minstrel_ht_api_sta_cmd(mp, STA_CMD_PROBE, pos); |  | ||||||
| +	else |  | ||||||
| +		err = -EINVAL; |  | ||||||
| + |  | ||||||
| +	if (err) |  | ||||||
| +		return err; |  | ||||||
| + |  | ||||||
| +	return count; |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +static const struct file_operations fops_control = { |  | ||||||
| +	.open = simple_open, |  | ||||||
| +	.llseek = generic_file_llseek, |  | ||||||
| +	.write = minstrel_ht_control_write, |  | ||||||
| +}; |  | ||||||
| + |  | ||||||
| +void minstrel_ht_sta_update(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) |  | ||||||
| +{ |  | ||||||
| +	bool add = list_empty(&mi->list); |  | ||||||
| + |  | ||||||
| +	spin_lock_bh(&mp->lock); |  | ||||||
| +	if (add) |  | ||||||
| +		list_add(&mi->list, &mp->stations); |  | ||||||
| +	if (mp->monitor) |  | ||||||
| +		minstrel_ht_dump_sta(mp, mi, add ? "add" : "update"); |  | ||||||
| +	spin_unlock_bh(&mp->lock); |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +void minstrel_ht_sta_remove(struct minstrel_priv *mp, struct minstrel_ht_sta *mi) |  | ||||||
| +{ |  | ||||||
| +	char info[64]; |  | ||||||
| +	int ofs = 0; |  | ||||||
| + |  | ||||||
| +	spin_lock_bh(&mp->lock); |  | ||||||
| +	list_del_init(&mi->list); |  | ||||||
| + |  | ||||||
| +	if (!mp->monitor) |  | ||||||
| +		goto out; |  | ||||||
| + |  | ||||||
| +	ofs = scnprintf(info, sizeof(info), "%llx;sta;remove;%pM;;;\n", |  | ||||||
| +			(unsigned long long)ktime_get_boottime_ns(), |  | ||||||
| +			mi->sta->addr); |  | ||||||
| +	relay_write(mp->relay_ev, info, ofs); |  | ||||||
| +	relay_flush(mp->relay_ev); |  | ||||||
| + |  | ||||||
| +out: |  | ||||||
| +	spin_unlock_bh(&mp->lock); |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +void __minstrel_ht_report_tx_status(struct minstrel_priv *mp, |  | ||||||
| +				    struct minstrel_ht_sta *mi, |  | ||||||
| +				    struct ieee80211_tx_info *info, |  | ||||||
| +				    u16 *rate_list, |  | ||||||
| +				    int n_rates) |  | ||||||
| +{ |  | ||||||
| +	char txs[64 + IEEE80211_TX_MAX_RATES * 8]; |  | ||||||
| +	int ofs = 0; |  | ||||||
| +	int i; |  | ||||||
| + |  | ||||||
| +	if (!n_rates) |  | ||||||
| +		return; |  | ||||||
| + |  | ||||||
| +	ofs += scnprintf(txs, sizeof(txs), "%llx;txs;%pM;%x;%x;%x;", |  | ||||||
| +			 (unsigned long long)ktime_get_boottime_ns(), |  | ||||||
| +			 mi->sta->addr, |  | ||||||
| +			 info->status.ampdu_len, |  | ||||||
| +			 info->status.ampdu_ack_len, |  | ||||||
| +			 !!(info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE)); |  | ||||||
| + |  | ||||||
| +	ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, "%x", |  | ||||||
| +			 rate_list[0]); |  | ||||||
| +	for (i = 1; i < n_rates; i++) |  | ||||||
| +		ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, ",%x", |  | ||||||
| +				 rate_list[i]); |  | ||||||
| + |  | ||||||
| +	ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, ";%x", |  | ||||||
| +			 info->status.rates[0].count); |  | ||||||
| +	for (i = 1; i < n_rates; i++) |  | ||||||
| +		ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, ",%x", |  | ||||||
| +				 info->status.rates[i].count); |  | ||||||
| +	ofs += scnprintf(txs + ofs, sizeof(txs) - ofs, "\n"); |  | ||||||
| +	relay_write(mp->relay_ev, txs, ofs); |  | ||||||
| +	relay_flush(mp->relay_ev); |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +void __minstrel_ht_report_rate_update(struct minstrel_priv *mp, |  | ||||||
| +				      struct minstrel_ht_sta *mi, u16 rate, |  | ||||||
| +				      struct minstrel_rate_stats *mrs) |  | ||||||
| +{ |  | ||||||
| +	char stat[100]; |  | ||||||
| +	int ofs; |  | ||||||
| +	int tp; |  | ||||||
| + |  | ||||||
| +	tp = minstrel_ht_get_tp_avg(mi, MI_RATE_GROUP(rate), MI_RATE_IDX(rate), |  | ||||||
| +				    mrs->prob_avg); |  | ||||||
| + |  | ||||||
| +	ofs = scnprintf(stat, sizeof(stat), |  | ||||||
| +			"%llx;stats;%pM;%x;%x;%x;%x;%x;%x;%x\n", |  | ||||||
| +			(unsigned long long)ktime_get_boottime_ns(), |  | ||||||
| +			mi->sta->addr, rate, |  | ||||||
| +			MINSTREL_TRUNC(mrs->prob_avg * 1000), tp, |  | ||||||
| +			mrs->last_success, |  | ||||||
| +			mrs->last_attempts, |  | ||||||
| +			mrs->succ_hist, mrs->att_hist); |  | ||||||
| + |  | ||||||
| +	relay_write(mp->relay_ev, stat, ofs); |  | ||||||
| +	relay_flush(mp->relay_ev); |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +void minstrel_ht_add_debugfs_api(struct ieee80211_hw *hw, void *priv, |  | ||||||
| +				 struct dentry *dir) |  | ||||||
| +{ |  | ||||||
| +	struct minstrel_priv *mp = priv; |  | ||||||
| + |  | ||||||
| +	spin_lock_init(&mp->lock); |  | ||||||
| +	INIT_LIST_HEAD(&mp->stations); |  | ||||||
| +	mp->relay_ev = relay_open("api_event", dir, 256, 512, &relay_ev_cb, |  | ||||||
| +				  NULL); |  | ||||||
| +	debugfs_create_devm_seqfile(&hw->wiphy->dev, "api_info", |  | ||||||
| +				    dir, minstrel_ht_read_api_info); |  | ||||||
| +	debugfs_create_file("api_control", 0200, dir, mp, &fops_control); |  | ||||||
| +} |  | ||||||
| + |  | ||||||
| +void minstrel_ht_remove_debugfs_api(void *priv) |  | ||||||
| +{ |  | ||||||
| +	struct minstrel_priv *mp = priv; |  | ||||||
| + |  | ||||||
| +	if (mp->relay_ev) |  | ||||||
| +		relay_close(mp->relay_ev); |  | ||||||
| +} |  | ||||||
		Reference in New Issue
	
	Block a user
	 Felix Fietkau
					Felix Fietkau