* Re: [PATCH v3 2/3] PM: dpm_watchdog: Allow disabling DPM watchdog by default
From: Rafael J. Wysocki @ 2026-06-08 14:14 UTC (permalink / raw)
To: Tzung-Bi Shih
Cc: Jonathan Corbet, Rafael J. Wysocki, Greg Kroah-Hartman,
Danilo Krummrich, Shuah Khan, Pavel Machek, Len Brown, linux-doc,
linux-kernel, linux-pm, driver-core, tfiga, senozhatsky,
Randy Dunlap
In-Reply-To: <20260608021526.1023248-3-tzungbi@kernel.org>
On Mon, Jun 8, 2026 at 4:16 AM Tzung-Bi Shih <tzungbi@kernel.org> wrote:
>
> Introduce the CONFIG_DPM_WATCHDOG_DEFAULT_ENABLED Kconfig option to
> allow the device suspend/resume watchdog (DPM watchdog) to be disabled
> by default at compile time.
>
> Additionally, introduce the "dpm_watchdog_enabled" module parameter to
> allow the watchdog to be enabled or disabled at boot time (via
> "power.dpm_watchdog_enabled") and at runtime (via sysfs).
I think that the new module param is more important because the new
config option is just its default value, so I'd rearrange the
changelog.
Also, I think that the "DEFAULT_" part of the new config option name
doesn't provide any additional value, so I'd just drop it.
> This provides flexibility for systems that want the watchdog code
> compiled in but inactive by default, allowing it to be enabled only when
> needed.
>
> Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
> ---
> v3:
> - Add "PM" tag (was missing).
> - Update the format and specify dependencies in kernel-parameters.txt.
> - Update the help message in Kconfig to reflect that dpm_watchdog_enabled
> can be set at runtime as well.
>
> v2: https://lore.kernel.org/all/20260604090756.2884671-3-tzungbi@kernel.org
> - Use module parameter and bool for dpm_watchdog_enabled.
> - Use IS_ENABLED().
>
> v1: https://lore.kernel.org/all/20260528103215.505795-1-tzungbi@kernel.org
>
> Documentation/admin-guide/kernel-parameters.txt | 8 ++++++++
> drivers/base/power/main.c | 11 +++++++++++
> kernel/power/Kconfig | 10 ++++++++++
> 3 files changed, 29 insertions(+)
>
> diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
> index 00375193bd26..f2620764c28e 100644
> --- a/Documentation/admin-guide/kernel-parameters.txt
> +++ b/Documentation/admin-guide/kernel-parameters.txt
> @@ -47,6 +47,7 @@
> PCI PCI bus support is enabled.
> PCIE PCI Express support is enabled.
> PCMCIA The PCMCIA subsystem is enabled.
> + PM Power Management support is enabled.
> PNP Plug & Play support is enabled.
> PPC PowerPC architecture is enabled.
> PPT Parallel port support is enabled.
> @@ -5399,6 +5400,13 @@ Kernel parameters
> function to NULL. On Idle the CPU just reduces
> execution priority.
>
> + power.dpm_watchdog_enabled=
> + [PM] Enable or disable the device suspend/resume
> + watchdog (DPM watchdog). Requires CONFIG_PM_SLEEP and
> + CONFIG_DPM_WATCHDOG enabled.
> + Format: <bool>
> + Default value is set by CONFIG_DPM_WATCHDOG_DEFAULT_ENABLED.
> +
> ppc_strict_facility_enable
> [PPC,ENABLE] This option catches any kernel floating point,
> Altivec, VSX and SPE outside of regions specifically
> diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
> index cd864f3a2799..7822c29b7c8d 100644
> --- a/drivers/base/power/main.c
> +++ b/drivers/base/power/main.c
> @@ -534,6 +534,11 @@ module_param(dpm_watchdog_all_cpu_backtrace, bool, 0644);
> MODULE_PARM_DESC(dpm_watchdog_all_cpu_backtrace,
> "Backtrace all CPUs on DPM watchdog timeout");
>
> +static bool __read_mostly dpm_watchdog_enabled =
> + IS_ENABLED(CONFIG_DPM_WATCHDOG_DEFAULT_ENABLED);
> +module_param(dpm_watchdog_enabled, bool, 0644);
> +MODULE_PARM_DESC(dpm_watchdog_enabled, "Enable DPM watchdog");
> +
> /**
> * dpm_watchdog_handler - Driver suspend / resume watchdog handler.
> * @t: The timer that PM watchdog depends on.
> @@ -577,6 +582,9 @@ static void dpm_watchdog_set(struct dpm_watchdog *wd, struct device *dev)
> {
> struct timer_list *timer = &wd->timer;
>
> + if (!dpm_watchdog_enabled)
> + return;
> +
> wd->dev = dev;
> wd->tsk = current;
> wd->fatal = CONFIG_DPM_WATCHDOG_TIMEOUT == CONFIG_DPM_WATCHDOG_WARNING_TIMEOUT;
> @@ -595,6 +603,9 @@ static void dpm_watchdog_clear(struct dpm_watchdog *wd)
> {
> struct timer_list *timer = &wd->timer;
>
> + if (!dpm_watchdog_enabled)
> + return;
> +
> timer_delete_sync(timer);
> timer_destroy_on_stack(timer);
> }
> diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
> index 530c897311d4..b16bd159074a 100644
> --- a/kernel/power/Kconfig
> +++ b/kernel/power/Kconfig
> @@ -268,6 +268,16 @@ config DPM_WATCHDOG
> captured in pstore device for inspection in subsequent
> boot session.
>
> +config DPM_WATCHDOG_DEFAULT_ENABLED
> + bool "Enable DPM watchdog by default"
> + depends on DPM_WATCHDOG
> + default y
> + help
> + If you say Y here, the DPM watchdog will be enabled by default.
> + If you say N, it will be compiled in but disabled. It can be
> + enabled at boot time via the "power.dpm_watchdog_enabled" kernel
> + parameter or at runtime via sysfs.
> +
> config DPM_WATCHDOG_TIMEOUT
> int "Watchdog timeout to panic in seconds"
> range 1 120
> --
> 2.54.0.1099.g489fc7bff1-goog
>
^ permalink raw reply
* Re: [PATCH v3 1/3] PM: core: Rename module parameters prefix to "power"
From: Rafael J. Wysocki @ 2026-06-08 14:11 UTC (permalink / raw)
To: Tzung-Bi Shih
Cc: Jonathan Corbet, Rafael J. Wysocki, Greg Kroah-Hartman,
Danilo Krummrich, Shuah Khan, Pavel Machek, Len Brown, linux-doc,
linux-kernel, linux-pm, driver-core, tfiga, senozhatsky,
Randy Dunlap
In-Reply-To: <20260608021526.1023248-2-tzungbi@kernel.org>
On Mon, Jun 8, 2026 at 4:16 AM Tzung-Bi Shih <tzungbi@kernel.org> wrote:
>
> Currently, the module parameters defined in drivers/base/power/main.c
> use the default prefix "main" (derived from the filename). The prefix
> "main" is too generic and non-descriptive for power management
> parameters.
>
> Redefine MODULE_PARAM_PREFIX to "power." at the beginning of the file
> to group the module parameters under the "power" namespace instead.
> This makes the parameters more descriptive.
>
> Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
> ---
> v3:
> - No changes.
>
> v2: https://lore.kernel.org/all/20260604090756.2884671-2-tzungbi@kernel.org
> - New to the series.
>
> v1: Doesn't exist.
>
> drivers/base/power/main.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
> index ed48c292f575..cd864f3a2799 100644
> --- a/drivers/base/power/main.c
> +++ b/drivers/base/power/main.c
> @@ -40,6 +40,9 @@
> #include "../base.h"
> #include "power.h"
>
> +#undef MODULE_PARAM_PREFIX
> +#define MODULE_PARAM_PREFIX "power."
"power" may be confused with the power supply support, so I'd rather
use "pm" or even "pm_sleep" (in which case the "dpm_" prefix could be
dropped from the new module param name in the next patch).
> +
> typedef int (*pm_callback_t)(struct device *);
>
> /*
> --
^ permalink raw reply
* Re: [PATCH RFC 2/4] printk: deprecate boot_delay in favour of printk_delay
From: Petr Mladek @ 2026-06-08 14:07 UTC (permalink / raw)
To: Andrew Murray
Cc: Jonathan Corbet, Shuah Khan, Russell King, Florian Fainelli,
Broadcom internal kernel review list, Ray Jui, Scott Branden,
Steven Rostedt, John Ogness, Sergey Senozhatsky, Andrew Morton,
Sebastian Andrzej Siewior, Clark Williams, Randy Dunlap,
Linus Torvalds, linux-doc, linux-kernel, linux-arm-kernel,
linux-rpi-kernel, linux-rt-devel
In-Reply-To: <20260601-deprecate_boot_delay-v1-2-c34c187142a6@thegoodpenguin.co.uk>
On Mon 2026-06-01 00:17:38, Andrew Murray wrote:
> The boot_delay (BOOT_PRINTK_DELAY) kernel parameter and printk_delay sysctl
> are two distinct mechanisms for providing similar functionality which add a
> delay prior to each printed printk message.
>
> boot_delay provides a kernel parameter for delaying printk output from
> kernel start through to boot (SYSTEM_RUNNING), whereas printk_delay is
> configurable only via sysctl and thus is only used post boot.
>
> Let's deprecate the boot_delay feature in favour of printk_delay. In order
> to preserve functionality, we'll also extend printk_delay such that it can
> additionally configured via a kernel parameter.
I would make it clear and say: "via an early kernel parameter".
Note that there are also kernel parameters which can be modified at runtime
via /sys/module/kernel/paramters/<parameter>
Also I would make it clear that this changes the behavior, for
example:
<proposal>
Behavior change:
The delay enabled by both "boot_delay" and "printk_delay" continues
working even in SYSTEM_RUNNING state. It must be explicitly stopped
by setting printk_delay=0 via sysctl.
The delay is skipped when the message is suppressed in all system
states. It used to skipped only for the boot_delay.
</proposal>
> --- a/kernel/printk/printk.c
> +++ b/kernel/printk/printk.c
> @@ -1339,11 +1327,34 @@ static void boot_delay_msec(int level)
> }
> }
> #else
> -static inline void boot_delay_msec(int level)
> +static inline void __init printk_delay_calculate(void)
> +{
> +}
> +
> +static inline void early_boot_delay_msec(void)
> {
It would be nice to print a warning that the early boot delay
does not work, something like:
pr_warn_once("Early boot delay does not work without CONFIG_GENERIC_CALIBRATE_DELAY enabled.\n");
> }
> #endif
>
> +static int __init printk_delay_setup(char *str)
> +{
> + get_option(&str, &printk_delay_msec);
> + if (printk_delay_msec > 10 * 1000)
> + printk_delay_msec = 0;
Sashiko AI warns that this code accepts negative values.
It might cause long delays, see
https://sashiko.dev/#/patchset/20260601-deprecate_boot_delay-v1-0-c34c187142a6%40thegoodpenguin.co.uk
The problem has already been there even before. But it would be nice
to fix it.
> +
> + printk_delay_calculate();
> +
> + return 0;
> +}
> +early_param("printk_delay", printk_delay_setup);
> +
> +static int __init boot_delay_setup(char *str)
> +{
> + pr_warn("boot_delay will soon be deprecated, please use printk_delay instead");
> + return printk_delay_setup(str);
> +}
> +early_param("boot_delay", boot_delay_setup);
> +
> static bool printk_time = IS_ENABLED(CONFIG_PRINTK_TIME);
> module_param_named(time, printk_time, bool, S_IRUGO | S_IWUSR);
Otherwise, it looks good to me.
Best Regards,
Petr
^ permalink raw reply
* Re: [PATCH v2] cpufreq: Documentation: fix freq_step description
From: Rafael J. Wysocki @ 2026-06-08 13:34 UTC (permalink / raw)
To: Zhongqiu Han, Pengjie Zhang
Cc: viresh.kumar, corbet, skhan, linux-pm, linux-doc, linux-kernel,
linuxarm, zhanjie9, prime.zeng, wanghuiqiang, xuwei5, lihuisong,
zhenglifeng1, yubowen8, wangzhi12
In-Reply-To: <210cefc5-c9f2-49e2-b2f6-2a3c1a42218a@oss.qualcomm.com>
On Wed, Jun 3, 2026 at 3:32 PM Zhongqiu Han
<zhongqiu.han@oss.qualcomm.com> wrote:
>
> On 6/3/2026 1:56 PM, Pengjie Zhang wrote:
> > The conservative governor documentation incorrectly states that setting
> > freq_step to 0 will use the default 5% frequency step. In reality, since
> > at least commit 8e677ce83bf4 ("[CPUFREQ] conservative: fixup governor to
> > function more like ondemand logic"), freq_step=0 has always caused the
> > governor to skip frequency updates entirely.
> >
> > Correct the documentation to reflect the actual behavior: freq_step=0
> > disables frequency changes by the governor entirely.
> >
> > Fixes: 2a0e49279850 ("cpufreq: User/admin documentation update and consolidation")
> > Signed-off-by: Pengjie Zhang <zhangpengjie2@huawei.com>
> > ---
> > Changes in v2:
> > - Update commit message to reference the correct historical commit
> > 8e677ce83bf4 instead of b9170836d1aa, as the original implementation
> > had asymmetric behavior for freq_step=0 (suggested by Zhongqiu Han).
> > - Link to v1:https://lore.kernel.org/all/20260529111122.3321645-1-zhangpengjie2@huawei.com/
>
>
> Looks good to me.
>
> Reviewed-by: Zhongqiu Han <zhongqiu.han@oss.qualcomm.com>
Applied as 7.2 material, thanks!
^ permalink raw reply
* Re: [PATCH RFC 1/4] printk: remove BOOT_PRINTK_DELAY config option
From: Petr Mladek @ 2026-06-08 13:22 UTC (permalink / raw)
To: Andrew Murray
Cc: Jonathan Corbet, Shuah Khan, Russell King, Florian Fainelli,
Broadcom internal kernel review list, Ray Jui, Scott Branden,
Steven Rostedt, John Ogness, Sergey Senozhatsky, Andrew Morton,
Sebastian Andrzej Siewior, Clark Williams, Randy Dunlap,
Linus Torvalds, linux-doc, linux-kernel, linux-arm-kernel,
linux-rpi-kernel, linux-rt-devel
In-Reply-To: <20260601-deprecate_boot_delay-v1-1-c34c187142a6@thegoodpenguin.co.uk>
On Mon 2026-06-01 00:17:37, Andrew Murray wrote:
> The boot_delay (BOOT_PRINTK_DELAY) kernel parameter and printk_delay sysctl
> are two distinct mechanisms for providing similar functionality which add a
> delay prior to each printed printk message.
>
> In preparation of combining them into a single configurable feature, let's
> first remove the kconfig option BOOT_PRINTK_DELAY.
>
> Signed-off-by: Andrew Murray <amurray@thegoodpenguin.co.uk>
The option allowed to reduce a bit the vmlinux size when people were
not interested into the functionality. I am not sure if it is worth
it though. I am personally fine with this change.
Reviewed-by: Petr Mladek <pmladek@suse.com>
Best Regards,
Petr
^ permalink raw reply
* [PATCH v2 1/3] hwmon: ina238: add support for samples and update_interval
From: Ferdinand Schwenk via B4 Relay @ 2026-06-08 13:03 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc, richard.leitner,
ferdinand.schwenk
In-Reply-To: <20260608-hwmon-ina238-update-interval-us-v2-v2-0-2d939fbb2ea1@advastore.com>
From: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
Expose INA238 ADC averaging count (AVG) and conversion timing
(VBUSCT/VSHCT/VTCT) through chip-level hwmon attributes:
chip/samples
chip/update_interval
Use per-chip conversion-time lookup tables so the same helpers work
for INA228/INA237/INA238/INA700/INA780 and SQ52206. Cache ADC_CONFIG
in driver data and update it on writes to avoid extra register reads
during read-modify-write updates.
Report update_interval in milliseconds as required by the hwmon ABI.
Compute it from raw ADC cycle time multiplied by the active averaging
count, and apply the inverse mapping on writes so programmed conversion
time tracks the selected sample count.
Clamp user-provided update_interval before unit scaling to prevent
overflow in arithmetic conversions.
Also combine chip attributes in HWMON_CHANNEL_INFO using a bitwise OR
for a single logical chip channel.
---
drivers/hwmon/ina238.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 142 insertions(+), 2 deletions(-)
diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c
index ff67b03189f7..667f78d43a6a 100644
--- a/drivers/hwmon/ina238.c
+++ b/drivers/hwmon/ina238.c
@@ -15,6 +15,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regmap.h>
+#include <linux/util_macros.h>
/* INA238 register definitions */
#define INA238_CONFIG 0x0
@@ -49,6 +50,17 @@
#define INA238_DIAG_ALERT_BUSUL BIT(3)
#define INA238_DIAG_ALERT_POL BIT(2)
+/* INA238_ADC_CONFIG register field masks and shifts */
+#define INA238_ADC_CONFIG_MODE_MASK GENMASK(15, 12)
+#define INA238_ADC_CONFIG_VBUSCT_SHIFT 9
+#define INA238_ADC_CONFIG_VBUSCT_MASK GENMASK(11, 9)
+#define INA238_ADC_CONFIG_VSHCT_SHIFT 6
+#define INA238_ADC_CONFIG_VSHCT_MASK GENMASK(8, 6)
+#define INA238_ADC_CONFIG_VTCT_SHIFT 3
+#define INA238_ADC_CONFIG_VTCT_MASK GENMASK(5, 3)
+#define INA238_ADC_CONFIG_AVG_SHIFT 0
+#define INA238_ADC_CONFIG_AVG_MASK GENMASK(2, 0)
+
#define INA238_REGISTERS 0x20
#define INA238_RSHUNT_DEFAULT 2500 /* uOhm */
@@ -101,6 +113,21 @@ static const struct regmap_config ina238_regmap_config = {
.val_bits = 16,
};
+/* Lookup table for conversion times in usec for INA238 family */
+static const u16 ina238_conv_time[] = {
+ 50, 84, 150, 280, 540, 1052, 2074, 4120,
+};
+
+/* Lookup table for conversion times in usec for SQ52206 */
+static const u16 sq52206_conv_time[] = {
+ 66, 118, 310, 566, 1070, 2090, 4140, 8230,
+};
+
+/* Lookup table for number of samples used in averaging mode */
+static const int ina238_avg_samples[] = {
+ 1, 4, 16, 64, 128, 256, 512, 1024,
+};
+
enum ina238_ids { ina228, ina237, ina238, ina700, ina780, sq52206 };
struct ina238_config {
@@ -112,6 +139,7 @@ struct ina238_config {
u32 power_calculate_factor; /* fixed parameter for power calculation, from datasheet */
u32 bus_voltage_lsb; /* bus voltage LSB, in nV */
int current_lsb; /* current LSB, in uA */
+ const u16 *conv_time; /* conversion time lookup table */
};
struct ina238_data {
@@ -124,6 +152,7 @@ struct ina238_data {
int current_lsb; /* current LSB, in uA */
int power_lsb; /* power LSB, in uW */
int energy_lsb; /* energy LSB, in uJ */
+ u16 adc_config; /* cached ADC_CONFIG register value */
};
static const struct ina238_config ina238_config[] = {
@@ -135,6 +164,7 @@ static const struct ina238_config ina238_config[] = {
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 16,
+ .conv_time = ina238_conv_time,
},
[ina237] = {
.has_20bit_voltage_current = false,
@@ -144,6 +174,7 @@ static const struct ina238_config ina238_config[] = {
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
+ .conv_time = ina238_conv_time,
},
[ina238] = {
.has_20bit_voltage_current = false,
@@ -153,6 +184,7 @@ static const struct ina238_config ina238_config[] = {
.config_default = INA238_CONFIG_DEFAULT,
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
+ .conv_time = ina238_conv_time,
},
[ina700] = {
.has_20bit_voltage_current = false,
@@ -163,6 +195,7 @@ static const struct ina238_config ina238_config[] = {
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
.current_lsb = 480,
+ .conv_time = ina238_conv_time,
},
[ina780] = {
.has_20bit_voltage_current = false,
@@ -173,6 +206,7 @@ static const struct ina238_config ina238_config[] = {
.bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB,
.temp_resolution = 12,
.current_lsb = 2400,
+ .conv_time = ina238_conv_time,
},
[sq52206] = {
.has_20bit_voltage_current = false,
@@ -182,6 +216,7 @@ static const struct ina238_config ina238_config[] = {
.config_default = SQ52206_CONFIG_DEFAULT,
.bus_voltage_lsb = SQ52206_BUS_VOLTAGE_LSB,
.temp_resolution = 16,
+ .conv_time = sq52206_conv_time,
},
};
@@ -261,6 +296,97 @@ static int ina228_read_voltage(struct ina238_data *data, int channel, long *val)
return 0;
}
+/* Converting ADC_CONFIG register value to update_interval in usec */
+static inline u32 ina238_reg_to_interval_us(struct ina238_data *data)
+{
+ const u16 *ct = data->config->conv_time;
+ u32 vbusct = ct[(data->adc_config & INA238_ADC_CONFIG_VBUSCT_MASK) >>
+ INA238_ADC_CONFIG_VBUSCT_SHIFT];
+ u32 vshct = ct[(data->adc_config & INA238_ADC_CONFIG_VSHCT_MASK) >>
+ INA238_ADC_CONFIG_VSHCT_SHIFT];
+ u32 vtct = ct[(data->adc_config & INA238_ADC_CONFIG_VTCT_MASK) >>
+ INA238_ADC_CONFIG_VTCT_SHIFT];
+
+ return vbusct + vshct + vtct;
+}
+
+static inline u32 ina238_samples(struct ina238_data *data)
+{
+ return ina238_avg_samples[(data->adc_config & INA238_ADC_CONFIG_AVG_MASK) >>
+ INA238_ADC_CONFIG_AVG_SHIFT];
+}
+
+/* Converting update_interval in msec to a single conversion time in usec */
+static inline u32 ina238_interval_ms_to_conv_time(long interval, u32 samples)
+{
+ u64 interval_us;
+
+ interval = clamp_val(interval, 0, LONG_MAX / 1000);
+ interval_us = (u64)interval * 1000;
+
+ /*
+ * update_interval reports the ADC cycle time including averaging.
+ * The target per-field conversion time is interval_us / (samples * 3).
+ */
+ return DIV_ROUND_CLOSEST_ULL(interval_us, samples * 3);
+}
+
+static int ina238_read_chip(struct device *dev, u32 attr, long *val)
+{
+ struct ina238_data *data = dev_get_drvdata(dev);
+
+ switch (attr) {
+ case hwmon_chip_samples:
+ *val = ina238_samples(data);
+ return 0;
+ case hwmon_chip_update_interval:
+ /* Return in msec */
+ *val = DIV_ROUND_CLOSEST(ina238_reg_to_interval_us(data) *
+ ina238_samples(data), 1000);
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ina238_write_chip(struct device *dev, u32 attr, long val)
+{
+ struct ina238_data *data = dev_get_drvdata(dev);
+ u16 adc_config;
+ int idx, ret;
+
+ switch (attr) {
+ case hwmon_chip_samples:
+ idx = find_closest(val, ina238_avg_samples,
+ ARRAY_SIZE(ina238_avg_samples));
+ adc_config = (data->adc_config & ~INA238_ADC_CONFIG_AVG_MASK) |
+ (idx << INA238_ADC_CONFIG_AVG_SHIFT);
+ ret = regmap_write(data->regmap, INA238_ADC_CONFIG, adc_config);
+ if (ret)
+ return ret;
+ data->adc_config = adc_config;
+ return 0;
+ case hwmon_chip_update_interval:
+ val = ina238_interval_ms_to_conv_time(val, ina238_samples(data));
+ idx = find_closest(val, data->config->conv_time,
+ ARRAY_SIZE(ina238_conv_time));
+ adc_config = (data->adc_config &
+ ~(INA238_ADC_CONFIG_VBUSCT_MASK |
+ INA238_ADC_CONFIG_VSHCT_MASK |
+ INA238_ADC_CONFIG_VTCT_MASK)) |
+ ((u16)idx << INA238_ADC_CONFIG_VBUSCT_SHIFT) |
+ ((u16)idx << INA238_ADC_CONFIG_VSHCT_SHIFT) |
+ ((u16)idx << INA238_ADC_CONFIG_VTCT_SHIFT);
+ ret = regmap_write(data->regmap, INA238_ADC_CONFIG, adc_config);
+ if (ret)
+ return ret;
+ data->adc_config = adc_config;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
static int ina238_read_in(struct device *dev, u32 attr, int channel,
long *val)
{
@@ -587,6 +713,8 @@ static int ina238_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
switch (type) {
+ case hwmon_chip:
+ return ina238_read_chip(dev, attr, val);
case hwmon_in:
return ina238_read_in(dev, attr, channel, val);
case hwmon_curr:
@@ -607,6 +735,8 @@ static int ina238_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
switch (type) {
+ case hwmon_chip:
+ return ina238_write_chip(dev, attr, val);
case hwmon_in:
return ina238_write_in(dev, attr, channel, val);
case hwmon_curr:
@@ -629,6 +759,14 @@ static umode_t ina238_is_visible(const void *drvdata,
bool has_energy = data->config->has_energy;
switch (type) {
+ case hwmon_chip:
+ switch (attr) {
+ case hwmon_chip_samples:
+ case hwmon_chip_update_interval:
+ return 0644;
+ default:
+ return 0;
+ }
case hwmon_in:
switch (attr) {
case hwmon_in_input:
@@ -692,6 +830,8 @@ static umode_t ina238_is_visible(const void *drvdata,
HWMON_I_MIN | HWMON_I_MIN_ALARM)
static const struct hwmon_channel_info * const ina238_info[] = {
+ HWMON_CHANNEL_INFO(chip,
+ HWMON_C_SAMPLES | HWMON_C_UPDATE_INTERVAL),
HWMON_CHANNEL_INFO(in,
/* 0: shunt voltage */
INA238_HWMON_IN_CONFIG,
@@ -798,8 +938,8 @@ static int ina238_probe(struct i2c_client *client)
}
/* Setup ADC_CONFIG register */
- ret = regmap_write(data->regmap, INA238_ADC_CONFIG,
- INA238_ADC_CONFIG_DEFAULT);
+ data->adc_config = INA238_ADC_CONFIG_DEFAULT;
+ ret = regmap_write(data->regmap, INA238_ADC_CONFIG, data->adc_config);
if (ret < 0) {
dev_err(dev, "error configuring the device: %d\n", ret);
return -ENODEV;
--
2.54.0
^ permalink raw reply related
* [PATCH v2 3/3] hwmon: ina238: add update_interval_us attribute
From: Ferdinand Schwenk via B4 Relay @ 2026-06-08 13:03 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc, richard.leitner,
ferdinand.schwenk
In-Reply-To: <20260608-hwmon-ina238-update-interval-us-v2-v2-0-2d939fbb2ea1@advastore.com>
From: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
The INA238 family supports eight conversion time steps from 50 us to
4120 us (SQ52206: 66 us to 8230 us). At the millisecond granularity of
update_interval, the four shortest steps (50, 84, 150, 280 us) all
round to the same value and cannot be individually selected.
Add support for the generic update_interval_us attribute, which reports
and programs the same ADC cycle time as update_interval but in
microseconds, giving userspace full access to all conversion time steps.
Both attributes reflect the total cycle time including the active
averaging count: the reported value is the raw conversion time
multiplied by the number of averaged samples, and writes apply the
inverse mapping.
---
Documentation/hwmon/ina238.rst | 4 +++
drivers/hwmon/ina238.c | 68 ++++++++++++++++++++++++++----------------
2 files changed, 46 insertions(+), 26 deletions(-)
diff --git a/Documentation/hwmon/ina238.rst b/Documentation/hwmon/ina238.rst
index 43950d1ec551..a75b79e17d9d 100644
--- a/Documentation/hwmon/ina238.rst
+++ b/Documentation/hwmon/ina238.rst
@@ -106,4 +106,8 @@ energy1_input Energy measurement (uJ)
temp1_input Die temperature measurement (mC)
temp1_max Maximum die temperature threshold (mC)
temp1_max_alarm Maximum die temperature alarm
+
+samples ADC averaging count (1, 4, 16, 64, 128, 256, 512, 1024)
+update_interval Total ADC conversion cycle time including averaging (ms)
+update_interval_us Total ADC conversion cycle time including averaging (us)
======================= =======================================================
diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c
index 667f78d43a6a..068a9fb422f2 100644
--- a/drivers/hwmon/ina238.c
+++ b/drivers/hwmon/ina238.c
@@ -316,19 +316,37 @@ static inline u32 ina238_samples(struct ina238_data *data)
INA238_ADC_CONFIG_AVG_SHIFT];
}
-/* Converting update_interval in msec to a single conversion time in usec */
-static inline u32 ina238_interval_ms_to_conv_time(long interval, u32 samples)
+/* Converting update_interval(_us) to a per-field conversion time in usec.
+ * interval_us is the total ADC cycle time including averaging in microseconds.
+ * All three conversion fields (VBUSCT, VSHCT, VTCT) are set equal, so the
+ * per-field time is interval_us / (samples * 3).
+ */
+static inline u32 ina238_interval_us_to_conv_time(long interval_us, u32 samples)
{
- u64 interval_us;
+ interval_us = clamp_val(interval_us, 0, LONG_MAX);
+ return DIV_ROUND_CLOSEST_ULL((u64)interval_us, samples * 3);
+}
- interval = clamp_val(interval, 0, LONG_MAX / 1000);
- interval_us = (u64)interval * 1000;
+/* Write a per-field conversion time (in usec) to the ADC_CONFIG register */
+static int ina238_write_conv_time(struct ina238_data *data, u32 conv_time_us)
+{
+ u16 adc_config;
+ int idx, ret;
- /*
- * update_interval reports the ADC cycle time including averaging.
- * The target per-field conversion time is interval_us / (samples * 3).
- */
- return DIV_ROUND_CLOSEST_ULL(interval_us, samples * 3);
+ idx = find_closest(conv_time_us, data->config->conv_time,
+ ARRAY_SIZE(ina238_conv_time));
+ adc_config = (data->adc_config &
+ ~(INA238_ADC_CONFIG_VBUSCT_MASK |
+ INA238_ADC_CONFIG_VSHCT_MASK |
+ INA238_ADC_CONFIG_VTCT_MASK)) |
+ ((u16)idx << INA238_ADC_CONFIG_VBUSCT_SHIFT) |
+ ((u16)idx << INA238_ADC_CONFIG_VSHCT_SHIFT) |
+ ((u16)idx << INA238_ADC_CONFIG_VTCT_SHIFT);
+ ret = regmap_write(data->regmap, INA238_ADC_CONFIG, adc_config);
+ if (ret)
+ return ret;
+ data->adc_config = adc_config;
+ return 0;
}
static int ina238_read_chip(struct device *dev, u32 attr, long *val)
@@ -344,6 +362,10 @@ static int ina238_read_chip(struct device *dev, u32 attr, long *val)
*val = DIV_ROUND_CLOSEST(ina238_reg_to_interval_us(data) *
ina238_samples(data), 1000);
return 0;
+ case hwmon_chip_update_interval_us:
+ /* Return in usec */
+ *val = ina238_reg_to_interval_us(data) * ina238_samples(data);
+ return 0;
default:
return -EOPNOTSUPP;
}
@@ -367,21 +389,13 @@ static int ina238_write_chip(struct device *dev, u32 attr, long val)
data->adc_config = adc_config;
return 0;
case hwmon_chip_update_interval:
- val = ina238_interval_ms_to_conv_time(val, ina238_samples(data));
- idx = find_closest(val, data->config->conv_time,
- ARRAY_SIZE(ina238_conv_time));
- adc_config = (data->adc_config &
- ~(INA238_ADC_CONFIG_VBUSCT_MASK |
- INA238_ADC_CONFIG_VSHCT_MASK |
- INA238_ADC_CONFIG_VTCT_MASK)) |
- ((u16)idx << INA238_ADC_CONFIG_VBUSCT_SHIFT) |
- ((u16)idx << INA238_ADC_CONFIG_VSHCT_SHIFT) |
- ((u16)idx << INA238_ADC_CONFIG_VTCT_SHIFT);
- ret = regmap_write(data->regmap, INA238_ADC_CONFIG, adc_config);
- if (ret)
- return ret;
- data->adc_config = adc_config;
- return 0;
+ /* Convert ms to us before passing to the shared helper */
+ val = clamp_val(val, 0, LONG_MAX / 1000) * 1000;
+ return ina238_write_conv_time(data,
+ ina238_interval_us_to_conv_time(val, ina238_samples(data)));
+ case hwmon_chip_update_interval_us:
+ return ina238_write_conv_time(data,
+ ina238_interval_us_to_conv_time(val, ina238_samples(data)));
default:
return -EOPNOTSUPP;
}
@@ -763,6 +777,7 @@ static umode_t ina238_is_visible(const void *drvdata,
switch (attr) {
case hwmon_chip_samples:
case hwmon_chip_update_interval:
+ case hwmon_chip_update_interval_us:
return 0644;
default:
return 0;
@@ -831,7 +846,8 @@ static umode_t ina238_is_visible(const void *drvdata,
static const struct hwmon_channel_info * const ina238_info[] = {
HWMON_CHANNEL_INFO(chip,
- HWMON_C_SAMPLES | HWMON_C_UPDATE_INTERVAL),
+ HWMON_C_SAMPLES | HWMON_C_UPDATE_INTERVAL |
+ HWMON_C_UPDATE_INTERVAL_US),
HWMON_CHANNEL_INFO(in,
/* 0: shunt voltage */
INA238_HWMON_IN_CONFIG,
--
2.54.0
^ permalink raw reply related
* [PATCH v2 0/3] hwmon: ina238: add samples and update_interval_us support
From: Ferdinand Schwenk via B4 Relay @ 2026-06-08 13:03 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc, richard.leitner,
ferdinand.schwenk
The INA238 family exposes ADC averaging and conversion timing controls
through ADC_CONFIG. Add support for the samples and update_interval
chip attributes and introduce a generic update_interval_us companion
attribute for devices that need sub-millisecond resolution.
The shortest INA238 conversion time steps are below 1 ms, so several
valid hardware settings collapse to the same update_interval value when
reported only in milliseconds. Keep update_interval in milliseconds as
required by the existing hwmon ABI and provide update_interval_us to
report and program the same total conversion cycle time with microsecond
resolution.
Patch 1 adds samples and update_interval support for the INA238 family.
Patch 2 adds the generic hwmon update_interval_us attribute and
documents it.
Patch 3 wires the new attribute up in the INA238 driver.
Link: https://lore.kernel.org/all/20260522-hwmon-ina238-add-samples-update-interval-v1-0-e1acfceb447e@advastore.com/
---
v2:
- keep update_interval in milliseconds to preserve the existing ABI
- add the generic update_interval_us hwmon chip attribute and documentation
- implement update_interval_us for ina238
- report and program intervals using the active averaging count
---
Ferdinand Schwenk (3):
hwmon: ina238: add support for samples and update_interval
hwmon: Add update_interval_us chip attribute
hwmon: ina238: add update_interval_us attribute
Documentation/ABI/testing/sysfs-class-hwmon | 14 +++
Documentation/hwmon/ina238.rst | 4 +
Documentation/hwmon/sysfs-interface.rst | 4 +
drivers/hwmon/hwmon.c | 1 +
drivers/hwmon/ina238.c | 160 +++++++++++++++++++++++++++-
include/linux/hwmon.h | 2 +
6 files changed, 183 insertions(+), 2 deletions(-)
---
base-commit: 028ef9c96e96197026887c0f092424679298aae8
change-id: 20260608-hwmon-ina238-update-interval-us-v2-13448de0a083
Best regards,
--
Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
^ permalink raw reply
* [PATCH v2 2/3] hwmon: Add update_interval_us chip attribute
From: Ferdinand Schwenk via B4 Relay @ 2026-06-08 13:03 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc, richard.leitner,
ferdinand.schwenk
In-Reply-To: <20260608-hwmon-ina238-update-interval-us-v2-v2-0-2d939fbb2ea1@advastore.com>
From: Ferdinand Schwenk <ferdinand.schwenk@advastore.com>
Some hardware monitoring chips support update intervals below one
millisecond. The existing update_interval attribute uses millisecond
granularity, which causes sub-millisecond steps to round to the same
value and become inaccessible from userspace.
Introduce update_interval_us, a companion chip-level attribute that
expresses the same update interval in microseconds. Drivers
implementing this attribute should also implement update_interval for
compatibility with millisecond-based userspace interfaces.
---
Documentation/ABI/testing/sysfs-class-hwmon | 14 ++++++++++++++
Documentation/hwmon/sysfs-interface.rst | 4 ++++
drivers/hwmon/hwmon.c | 1 +
include/linux/hwmon.h | 2 ++
4 files changed, 21 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-class-hwmon b/Documentation/ABI/testing/sysfs-class-hwmon
index cfd0d0bab483..b185bdfc7186 100644
--- a/Documentation/ABI/testing/sysfs-class-hwmon
+++ b/Documentation/ABI/testing/sysfs-class-hwmon
@@ -27,6 +27,20 @@ Description:
Some devices have a variable update rate or interval.
This attribute can be used to change it to the desired value.
+What: /sys/class/hwmon/hwmonX/update_interval_us
+Description:
+ The interval at which the chip will update readings,
+ expressed in microseconds.
+ Unit: microsecond
+
+ RW
+
+ Some devices have a variable update rate or interval and
+ require finer-than-millisecond control.
+ This attribute can be used to change it to the desired value.
+ Drivers implementing this attribute should also implement
+ update_interval for millisecond-based userspace interfaces.
+
What: /sys/class/hwmon/hwmonX/inY_min
Description:
Voltage min value.
diff --git a/Documentation/hwmon/sysfs-interface.rst b/Documentation/hwmon/sysfs-interface.rst
index f76e9f8cc1ad..94e1bbce172a 100644
--- a/Documentation/hwmon/sysfs-interface.rst
+++ b/Documentation/hwmon/sysfs-interface.rst
@@ -106,6 +106,10 @@ Global attributes
`update_interval`
The interval at which the chip will update readings.
+`update_interval_us`
+ The interval at which the chip will update readings,
+ expressed in microseconds for finer resolution.
+
********
Voltages
diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c
index 9695dca62a7e..c481bb535e35 100644
--- a/drivers/hwmon/hwmon.c
+++ b/drivers/hwmon/hwmon.c
@@ -572,6 +572,7 @@ static const char * const hwmon_chip_attrs[] = {
[hwmon_chip_curr_reset_history] = "curr_reset_history",
[hwmon_chip_power_reset_history] = "power_reset_history",
[hwmon_chip_update_interval] = "update_interval",
+ [hwmon_chip_update_interval_us] = "update_interval_us",
[hwmon_chip_alarms] = "alarms",
[hwmon_chip_samples] = "samples",
[hwmon_chip_curr_samples] = "curr_samples",
diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h
index 301a83afbd66..5a5882fee299 100644
--- a/include/linux/hwmon.h
+++ b/include/linux/hwmon.h
@@ -39,6 +39,7 @@ enum hwmon_chip_attributes {
hwmon_chip_power_reset_history,
hwmon_chip_register_tz,
hwmon_chip_update_interval,
+ hwmon_chip_update_interval_us,
hwmon_chip_alarms,
hwmon_chip_samples,
hwmon_chip_curr_samples,
@@ -55,6 +56,7 @@ enum hwmon_chip_attributes {
#define HWMON_C_POWER_RESET_HISTORY BIT(hwmon_chip_power_reset_history)
#define HWMON_C_REGISTER_TZ BIT(hwmon_chip_register_tz)
#define HWMON_C_UPDATE_INTERVAL BIT(hwmon_chip_update_interval)
+#define HWMON_C_UPDATE_INTERVAL_US BIT(hwmon_chip_update_interval_us)
#define HWMON_C_ALARMS BIT(hwmon_chip_alarms)
#define HWMON_C_SAMPLES BIT(hwmon_chip_samples)
#define HWMON_C_CURR_SAMPLES BIT(hwmon_chip_curr_samples)
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v3 1/4] mm/zswap: Make shrink_worker writeback cursor per-memcg
From: Hao Jia @ 2026-06-08 12:50 UTC (permalink / raw)
To: Nhat Pham
Cc: Yosry Ahmed, akpm, tj, hannes, shakeel.butt, mhocko, mkoutny,
chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
linux-kernel, linux-doc, Hao Jia
In-Reply-To: <CAKEwX=MQ3xXBAY-2H8vA+XSX5GHNBubJ2GCYAXGD+Hra++ZM7A@mail.gmail.com>
On 2026/6/5 01:23, Nhat Pham wrote:
> On Thu, Jun 4, 2026 at 6:06 AM Hao Jia <jiahao.kernel@gmail.com> wrote:
>>
>>
>>
>> On 2026/6/4 13:34, Yosry Ahmed wrote:
>>>>>> For instance, suppose a parent memcg has two children, memcg1 and memcg2,
>>>>>> each with 200MB of zswap (100MB inactive). Triggering proactive writeback on
>>>>>> the parent memcg will exhaust memcg1's inactive zswap pages. After that,
>>>>>> even though memcg2 still has plenty of inactive zswap pages, it will
>>>>>> continue to write back memcg1's active zswap pages. Writing back active
>>>>>> zswap pages causes the user-space agent to prematurely abort the writeback
>>>>>> because it detects that certain memcg metrics have exceeded predefined
>>>>>> thresholds.
>>>>>
>>>>> This will only happen if the reclaim size is smaller than the batch
>>>>> size, right? Otherwise the kernel should reclaim more or less equally
>>>>> from both memcgs?
>>>>>
>>>>
>>>> I gave it some thought. Not using a cursor could lead to unfairness
>>>> issues with certain writeback sizes:
>>>>
>>>> - If the writeback size is an odd multiple of WB_BATCH (e.g.,
>>>> triggering a writeback of 3 * WB_BATCH), with 2 child cgroups, the
>>>> writeback ratio might end up being 2:1.
>>>> - If a memcg has 5 child cgroups and a writeback of 2 * WB_BATCH is
>>>> triggered, it might repeatedly write back from only the first 2 child
>>>> cgroups.
>>>>
>>>> Although setting a smaller WB_BATCH might mitigate this unfairness, it
>>>> could hurt writeback efficiency. Let's just use per-memcg cursors to
>>>> completely fix these corner cases.
>>>
>>> Exactly, the batch size should be small enough that any unfairness is
>>> not a problem. I would honestly just do batching without a per-memcg
>>> cursor, unless we have numbers to prove that the efficiency is
>>> affected when we use a small batch size. Let's only introduce
>>> complexity when needed please.
>
> I'm impartial towards the complexity of per-memcg cursor. I don't
> think it's that big of a deal, but only if it's warranted.
>
> Hao, if you're convinced that doing small batch is not efficient,
> could you run some experiments to show the improvement bigger batchign
> and fairness? Maybe implement a small batch, no-memcg cursor first.
> Then implement a patch on top of it to add per-memcg cursor, and show
> how much performance win we can get from that patch on top of the
> patch series?
>
Thanks for the suggestion!
I ran some tests and found that neither the per-memcg cursor nor
different batch sizes have a significant impact on proactive writeback
performance. However, exactly as we suspected, without the per-memcg
cursor, the writeback distribution among child memcgs is highly unfair.
Test Setup:
zswap config: 18G capacity, LZ4 compression.
cgroup hierarchy: 1 parent test memcg with 10 child memcgs.
Allocation: Allocated 1600MB of anonymous pages in each child memcg.
To ensure compressibility, the first half of each page was filled with
random data and the second half with zeros.
Force to zswap: Ran echo "1600M" > memory.reclaim on each child memcg
to squeeze all their memory into zswap.
Trigger writeback: Ran echo "<size> zswap_writeback_only" >
memory.reclaim on the parent cgroup 200 times, with a 2-second interval
between each run.
Metric: Monitored the zswpwb_proactive metric in memory.stat to
observe the writeback volume.
**Note**: The size here refers to the uncompressed memory size. Also,
since the second-chance algorithm would cause many writebacks to fall
short of the target size, I **bypassed** it during these tests to avoid
interference.
Without cursor (size: 1M, batch: 32)
child wb_pages wb_MB share%
child0 6368 24.88 12.50
child1 6368 24.88 12.50
child2 6368 24.88 12.50
child3 6368 24.88 12.50
child4 6368 24.88 12.50
child5 6368 24.88 12.50
child6 6368 24.88 12.50
child7 6368 24.88 12.50
child8 0 0.00 0.00
child9 0 0.00 0.00
Without cursor (size: 1M, batch: 128)
child wb_pages wb_MB share%
child0 25472 99.50 50.00
child1 25472 99.50 50.00
child2 0 0.00 0.00
child3 0 0.00 0.00
child4 0 0.00 0.00
child5 0 0.00 0.00
child6 0 0.00 0.00
child7 0 0.00 0.00
child8 0 0.00 0.00
child9 0 0.00 0.00
Without cursor (size: 6M, batch: 128)
child wb_pages wb_MB share%
child0 51200 200.00 16.67
child1 51200 200.00 16.67
child2 25600 100.00 8.33
child3 25600 100.00 8.33
child4 25600 100.00 8.33
child5 25600 100.00 8.33
child6 25600 100.00 8.33
child7 25600 100.00 8.33
child8 25600 100.00 8.33
child9 25600 100.00 8.33
With cursor (size: 1M, batch: 32)
child wb_pages wb_MB share%
child0 5120 20.00 10.00
child1 5120 20.00 10.00
child2 5120 20.00 10.00
child3 5120 20.00 10.00
child4 5120 20.00 10.00
child5 5120 20.00 10.00
child6 5120 20.00 10.00
child7 5120 20.00 10.00
child8 5120 20.00 10.00
child9 5120 20.00 10.00
With cursor (size: 1M, batch: 128)
child wb_pages wb_MB share%
child0 5120 20.00 10.00
child1 5120 20.00 10.00
child2 5120 20.00 10.00
child3 5120 20.00 10.00
child4 5120 20.00 10.00
child5 5120 20.00 10.00
child6 5120 20.00 10.00
child7 5120 20.00 10.00
child8 5120 20.00 10.00
child9 5120 20.00 10.00
Thakns,
Hao
^ permalink raw reply
* [PATCH net-next v08 4/5] hinic3: Add ethtool rss ops
From: Fan Gong @ 2026-06-08 12:36 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1780907605.git.wudi234@huawei.com>
Implement following ethtool callback function:
.get_rxnfc
.set_rxnfc
.get_channels
.set_channels
.get_rxfh_indir_size
.get_rxfh_key_size
.get_rxfh
.set_rxfh
These callbacks allow users to utilize ethtool for detailed
RSS parameters configuration and monitoring.
Co-developed-by: Wu Di <wudi234@huawei.com>
Signed-off-by: Wu Di <wudi234@huawei.com>
Co-developed-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
.../ethernet/huawei/hinic3/hinic3_ethtool.c | 9 +
.../huawei/hinic3/hinic3_mgmt_interface.h | 2 +
| 539 +++++++++++++++++-
| 19 +
4 files changed, 567 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
index 0f01e52830fb..d63807c4c7f5 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
@@ -16,6 +16,7 @@
#include "hinic3_hw_comm.h"
#include "hinic3_nic_dev.h"
#include "hinic3_nic_cfg.h"
+#include "hinic3_rss.h"
#define HINIC3_MGMT_VERSION_MAX_LEN 32
/* Coalesce time properties in microseconds */
@@ -1245,6 +1246,14 @@ static const struct ethtool_ops hinic3_ethtool_ops = {
.get_pause_stats = hinic3_get_pause_stats,
.get_coalesce = hinic3_get_coalesce,
.set_coalesce = hinic3_set_coalesce,
+ .get_rxnfc = hinic3_get_rxnfc,
+ .set_rxnfc = hinic3_set_rxnfc,
+ .get_channels = hinic3_get_channels,
+ .set_channels = hinic3_set_channels,
+ .get_rxfh_indir_size = hinic3_get_rxfh_indir_size,
+ .get_rxfh_key_size = hinic3_get_rxfh_key_size,
+ .get_rxfh = hinic3_get_rxfh,
+ .set_rxfh = hinic3_set_rxfh,
};
void hinic3_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h b/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
index 76c691f82703..3c1263ff99ff 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
@@ -282,6 +282,7 @@ enum l2nic_cmd {
L2NIC_CMD_SET_VLAN_FILTER_EN = 26,
L2NIC_CMD_SET_RX_VLAN_OFFLOAD = 27,
L2NIC_CMD_CFG_RSS = 60,
+ L2NIC_CMD_GET_RSS_CTX_TBL = 62,
L2NIC_CMD_CFG_RSS_HASH_KEY = 63,
L2NIC_CMD_CFG_RSS_HASH_ENGINE = 64,
L2NIC_CMD_SET_RSS_CTX_TBL = 65,
@@ -301,6 +302,7 @@ enum l2nic_ucode_cmd {
L2NIC_UCODE_CMD_MODIFY_QUEUE_CTX = 0,
L2NIC_UCODE_CMD_CLEAN_QUEUE_CTX = 1,
L2NIC_UCODE_CMD_SET_RSS_INDIR_TBL = 4,
+ L2NIC_UCODE_CMD_GET_RSS_INDIR_TBL = 6,
};
/* hilink mac group command */
--git a/drivers/net/ethernet/huawei/hinic3/hinic3_rss.c b/drivers/net/ethernet/huawei/hinic3/hinic3_rss.c
index 25db74d8c7dd..4852c99acc29 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rss.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rss.c
@@ -155,7 +155,7 @@ static int hinic3_set_rss_type(struct hinic3_hwdev *hwdev,
L2NIC_CMD_SET_RSS_CTX_TBL, &msg_params);
if (ctx_tbl.msg_head.status == MGMT_STATUS_CMD_UNSUPPORTED) {
- return MGMT_STATUS_CMD_UNSUPPORTED;
+ return -EOPNOTSUPP;
} else if (err || ctx_tbl.msg_head.status) {
dev_err(hwdev->dev, "mgmt Failed to set rss context offload, err: %d, status: 0x%x\n",
err, ctx_tbl.msg_head.status);
@@ -165,6 +165,41 @@ static int hinic3_set_rss_type(struct hinic3_hwdev *hwdev,
return 0;
}
+static int hinic3_get_rss_type(struct hinic3_hwdev *hwdev,
+ struct hinic3_rss_type *rss_type)
+{
+ struct l2nic_cmd_rss_ctx_tbl ctx_tbl = {};
+ struct mgmt_msg_params msg_params = {};
+ int err;
+
+ ctx_tbl.func_id = hinic3_global_func_id(hwdev);
+
+ mgmt_msg_params_init_default(&msg_params, &ctx_tbl, sizeof(ctx_tbl));
+
+ err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
+ L2NIC_CMD_GET_RSS_CTX_TBL,
+ &msg_params);
+ if (ctx_tbl.msg_head.status == MGMT_STATUS_CMD_UNSUPPORTED) {
+ return -EOPNOTSUPP;
+ } else if (err || ctx_tbl.msg_head.status) {
+ dev_err(hwdev->dev, "Failed to get hash type, err: %d, status: 0x%x\n",
+ err, ctx_tbl.msg_head.status);
+ return -EINVAL;
+ }
+
+ rss_type->ipv4 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, IPV4);
+ rss_type->ipv6 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, IPV6);
+ rss_type->ipv6_ext = L2NIC_RSS_TYPE_GET(ctx_tbl.context, IPV6_EXT);
+ rss_type->tcp_ipv4 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, TCP_IPV4);
+ rss_type->tcp_ipv6 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, TCP_IPV6);
+ rss_type->tcp_ipv6_ext = L2NIC_RSS_TYPE_GET(ctx_tbl.context,
+ TCP_IPV6_EXT);
+ rss_type->udp_ipv4 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, UDP_IPV4);
+ rss_type->udp_ipv6 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, UDP_IPV6);
+
+ return 0;
+}
+
static int hinic3_rss_cfg_hash_type(struct hinic3_hwdev *hwdev, u8 opcode,
enum hinic3_rss_hash_type *type)
{
@@ -264,7 +299,8 @@ static int hinic3_set_hw_rss_parameters(struct net_device *netdev, u8 rss_en)
if (err)
return err;
- hinic3_fillout_indir_tbl(netdev, nic_dev->rss_indir);
+ if (!netif_is_rxfh_configured(netdev))
+ hinic3_fillout_indir_tbl(netdev, nic_dev->rss_indir);
err = hinic3_config_rss_hw_resource(netdev, nic_dev->rss_indir);
if (err)
@@ -334,3 +370,502 @@ void hinic3_try_to_enable_rss(struct net_device *netdev)
clear_bit(HINIC3_RSS_ENABLE, &nic_dev->flags);
nic_dev->q_params.num_qps = nic_dev->max_qps;
}
+
+static int hinic3_set_l4_rss_hash_ops(const struct ethtool_rxnfc *cmd,
+ struct hinic3_rss_type *rss_type)
+{
+ u8 rss_l4_en;
+
+ switch (cmd->data & (RXH_L4_B_0_1 | RXH_L4_B_2_3)) {
+ case 0:
+ rss_l4_en = 0;
+ break;
+ case (RXH_L4_B_0_1 | RXH_L4_B_2_3):
+ rss_l4_en = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (cmd->flow_type) {
+ case TCP_V4_FLOW:
+ rss_type->tcp_ipv4 = rss_l4_en;
+ break;
+ case TCP_V6_FLOW:
+ rss_type->tcp_ipv6 = rss_l4_en;
+ break;
+ case UDP_V4_FLOW:
+ rss_type->udp_ipv4 = rss_l4_en;
+ break;
+ case UDP_V6_FLOW:
+ rss_type->udp_ipv6 = rss_l4_en;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hinic3_update_rss_hash_opts(struct net_device *netdev,
+ struct ethtool_rxnfc *cmd,
+ struct hinic3_rss_type *rss_type)
+{
+ int err;
+
+ switch (cmd->flow_type) {
+ case TCP_V4_FLOW:
+ case TCP_V6_FLOW:
+ case UDP_V4_FLOW:
+ case UDP_V6_FLOW:
+ err = hinic3_set_l4_rss_hash_ops(cmd, rss_type);
+ if (err)
+ return err;
+
+ break;
+ case IPV4_FLOW:
+ rss_type->ipv4 = 1;
+ break;
+ case IPV6_FLOW:
+ rss_type->ipv6 = 1;
+ break;
+ default:
+ netdev_err(netdev, "Unsupported flow type\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hinic3_set_rss_hash_opts(struct net_device *netdev,
+ struct ethtool_rxnfc *cmd)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_rss_type rss_type;
+ int err;
+
+ if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
+ cmd->data = 0;
+ netdev_err(netdev, "RSS is disable, not support to set flow-hash\n");
+ return -EOPNOTSUPP;
+ }
+
+ /* RSS only supports hashing of IP addresses and L4 ports */
+ if (cmd->data & ~(RXH_IP_SRC | RXH_IP_DST |
+ RXH_L4_B_0_1 | RXH_L4_B_2_3))
+ return -EINVAL;
+
+ /* Both IP addresses must be part of the hash tuple */
+ if (!(cmd->data & RXH_IP_SRC) || !(cmd->data & RXH_IP_DST))
+ return -EINVAL;
+
+ /* L4 hash bits are not valid for pure L3 flow types */
+ if ((cmd->flow_type == IPV4_FLOW || cmd->flow_type == IPV6_FLOW) &&
+ (cmd->data & (RXH_L4_B_0_1 | RXH_L4_B_2_3)))
+ return -EINVAL;
+
+ err = hinic3_get_rss_type(nic_dev->hwdev, &rss_type);
+ if (err) {
+ netdev_err(netdev, "Failed to get rss type\n");
+ return err;
+ }
+
+ err = hinic3_update_rss_hash_opts(netdev, cmd, &rss_type);
+ if (err)
+ return err;
+
+ err = hinic3_set_rss_type(nic_dev->hwdev, rss_type);
+ if (err) {
+ netdev_err(netdev, "Failed to set rss type\n");
+ return err;
+ }
+
+ nic_dev->rss_type = rss_type;
+
+ return 0;
+}
+
+static void convert_rss_l3_type(u8 rss_opt, struct ethtool_rxnfc *cmd)
+{
+ if (!rss_opt)
+ cmd->data &= ~(RXH_IP_SRC | RXH_IP_DST);
+}
+
+static void convert_rss_l4_type(u8 rss_opt, struct ethtool_rxnfc *cmd)
+{
+ if (rss_opt)
+ cmd->data |= RXH_L4_B_0_1 | RXH_L4_B_2_3;
+}
+
+static int hinic3_convert_rss_type(struct net_device *netdev,
+ struct hinic3_rss_type *rss_type,
+ struct ethtool_rxnfc *cmd)
+{
+ cmd->data = RXH_IP_SRC | RXH_IP_DST;
+ switch (cmd->flow_type) {
+ case TCP_V4_FLOW:
+ convert_rss_l4_type(rss_type->tcp_ipv4, cmd);
+ break;
+ case TCP_V6_FLOW:
+ convert_rss_l4_type(rss_type->tcp_ipv6, cmd);
+ break;
+ case UDP_V4_FLOW:
+ convert_rss_l4_type(rss_type->udp_ipv4, cmd);
+ break;
+ case UDP_V6_FLOW:
+ convert_rss_l4_type(rss_type->udp_ipv6, cmd);
+ break;
+ case IPV4_FLOW:
+ convert_rss_l3_type(rss_type->ipv4, cmd);
+ break;
+ case IPV6_FLOW:
+ convert_rss_l3_type(rss_type->ipv6, cmd);
+ break;
+ default:
+ netdev_err(netdev, "Unsupported flow type\n");
+ cmd->data = 0;
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hinic3_get_rss_hash_opts(struct net_device *netdev,
+ struct ethtool_rxnfc *cmd)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_rss_type rss_type;
+ int err;
+
+ cmd->data = 0;
+
+ if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags))
+ return 0;
+
+ err = hinic3_get_rss_type(nic_dev->hwdev, &rss_type);
+ if (err) {
+ netdev_err(netdev, "Failed to get rss type\n");
+ return err;
+ }
+
+ return hinic3_convert_rss_type(netdev, &rss_type, cmd);
+}
+
+int hinic3_get_rxnfc(struct net_device *netdev,
+ struct ethtool_rxnfc *cmd, u32 *rule_locs)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ int err = 0;
+
+ switch (cmd->cmd) {
+ case ETHTOOL_GRXRINGS:
+ cmd->data = nic_dev->q_params.num_qps;
+ break;
+ case ETHTOOL_GRXFH:
+ err = hinic3_get_rss_hash_opts(netdev, cmd);
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ break;
+ }
+
+ return err;
+}
+
+int hinic3_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd)
+{
+ int err;
+
+ switch (cmd->cmd) {
+ case ETHTOOL_SRXFH:
+ err = hinic3_set_rss_hash_opts(netdev, cmd);
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ break;
+ }
+
+ return err;
+}
+
+static u16 hinic3_max_channels(struct net_device *netdev)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ u8 tcs = netdev_get_num_tc(netdev);
+
+ return tcs ? nic_dev->max_qps / tcs : nic_dev->max_qps;
+}
+
+static u16 hinic3_curr_channels(struct net_device *netdev)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+
+ if (netif_running(netdev))
+ return nic_dev->q_params.num_qps ?
+ nic_dev->q_params.num_qps : 1;
+ else
+ return min_t(u16, hinic3_max_channels(netdev),
+ nic_dev->q_params.num_qps);
+}
+
+void hinic3_get_channels(struct net_device *netdev,
+ struct ethtool_channels *channels)
+{
+ channels->max_rx = 0;
+ channels->max_tx = 0;
+ channels->max_other = 0;
+ /* report maximum channels */
+ channels->max_combined = hinic3_max_channels(netdev);
+ channels->rx_count = 0;
+ channels->tx_count = 0;
+ channels->other_count = 0;
+ /* report flow director queues as maximum channels */
+ channels->combined_count = hinic3_curr_channels(netdev);
+}
+
+static int
+hinic3_validate_channel_parameter(struct net_device *netdev,
+ const struct ethtool_channels *channels)
+{
+ u16 max_channel = hinic3_max_channels(netdev);
+ unsigned int count = channels->combined_count;
+
+ if (!count) {
+ netdev_err(netdev, "Unsupported combined_count=0\n");
+ return -EINVAL;
+ }
+
+ if (channels->tx_count || channels->rx_count || channels->other_count) {
+ netdev_err(netdev, "Setting rx/tx/other count not supported\n");
+ return -EINVAL;
+ }
+
+ if (count > max_channel) {
+ netdev_err(netdev, "Combined count %u exceed limit %u\n", count,
+ max_channel);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hinic3_rss_update_num_qps_and_reprogram(struct net_device *netdev)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ int err;
+
+ if (!netif_is_rxfh_configured(netdev))
+ hinic3_rss_set_indir_tbl(nic_dev->hwdev, nic_dev->rss_indir);
+
+ if (!netif_running(netdev))
+ return 0;
+
+ err = hinic3_set_hw_rss_parameters(netdev, 1);
+ if (err)
+ netdev_err(netdev,
+ "Failed to update RSS parameters after changing channels\n");
+
+ return err;
+}
+
+int hinic3_set_channels(struct net_device *netdev,
+ struct ethtool_channels *channels)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ unsigned int count = channels->combined_count;
+ struct hinic3_dyna_txrxq_params q_params;
+ int err;
+
+ err = hinic3_validate_channel_parameter(netdev, channels);
+ if (err)
+ return err;
+
+ if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
+ netdev_err(netdev, "This function doesn't support RSS, only support 1 queue pair\n");
+ return -EOPNOTSUPP;
+ }
+
+ netdev_dbg(netdev, "Set max combined queue number from %u to %u\n",
+ nic_dev->q_params.num_qps, count);
+
+ if (netif_running(netdev)) {
+ q_params = nic_dev->q_params;
+ q_params.num_qps = (u16)count;
+ q_params.txqs_res = NULL;
+ q_params.rxqs_res = NULL;
+ q_params.irq_cfg = NULL;
+
+ err = hinic3_change_channel_settings(netdev, &q_params);
+ if (err) {
+ netdev_err(netdev, "Failed to change channel settings\n");
+ return err;
+ }
+ } else {
+ nic_dev->q_params.num_qps = (u16)count;
+ }
+
+ err = hinic3_rss_update_num_qps_and_reprogram(netdev);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+u32 hinic3_get_rxfh_indir_size(struct net_device *netdev)
+{
+ return L2NIC_RSS_INDIR_SIZE;
+}
+
+static int hinic3_set_rss_rxfh(struct net_device *netdev,
+ const u32 *indir, u8 *key)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ u16 temp_indir[L2NIC_RSS_INDIR_SIZE];
+ int err;
+ u32 i;
+
+ if (indir) {
+ for (i = 0; i < L2NIC_RSS_INDIR_SIZE; i++)
+ temp_indir[i] = (u16)indir[i];
+
+ err = hinic3_rss_set_indir_tbl(nic_dev->hwdev, temp_indir);
+ if (err) {
+ netdev_err(netdev, "Failed to set rss indir table\n");
+ return err;
+ }
+
+ memcpy(nic_dev->rss_indir, temp_indir, L2NIC_RSS_INDIR_SIZE);
+ }
+
+ if (key) {
+ err = hinic3_rss_set_hash_key(nic_dev->hwdev, key);
+ if (err) {
+ netdev_err(netdev, "Failed to set rss key\n");
+ return err;
+ }
+
+ memcpy(nic_dev->rss_hkey, key, L2NIC_RSS_KEY_SIZE);
+ }
+
+ return 0;
+}
+
+u32 hinic3_get_rxfh_key_size(struct net_device *netdev)
+{
+ return L2NIC_RSS_KEY_SIZE;
+}
+
+static int hinic3_rss_get_indir_tbl(struct hinic3_hwdev *hwdev,
+ u32 *indir_table)
+{
+ struct hinic3_cmd_buf_pair pair;
+ __le16 *indir_tbl = NULL;
+ int err, i;
+
+ err = hinic3_cmd_buf_pair_init(hwdev, &pair);
+ if (err) {
+ dev_err(hwdev->dev, "Failed to allocate cmd_buf.\n");
+ return err;
+ }
+
+ memset(pair.in->buf, 0, pair.in->size);
+
+ err = hinic3_cmdq_detail_resp(hwdev, MGMT_MOD_L2NIC,
+ L2NIC_UCODE_CMD_GET_RSS_INDIR_TBL,
+ pair.in, pair.out, NULL);
+ if (err) {
+ dev_err(hwdev->dev, "Failed to get rss indir table\n");
+ goto err_get_indir_tbl;
+ }
+
+ indir_tbl = (__le16 *)pair.out->buf;
+ for (i = 0; i < L2NIC_RSS_INDIR_SIZE; i++)
+ indir_table[i] = le16_to_cpu(*(indir_tbl + i));
+
+err_get_indir_tbl:
+ hinic3_cmd_buf_pair_uninit(hwdev, &pair);
+
+ return err;
+}
+
+int hinic3_get_rxfh(struct net_device *netdev,
+ struct ethtool_rxfh_param *rxfh)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ int err = 0;
+
+ if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
+ netdev_err(netdev, "Rss is disabled\n");
+ return -EOPNOTSUPP;
+ }
+
+ rxfh->hfunc =
+ nic_dev->rss_hash_type == HINIC3_RSS_HASH_ENGINE_TYPE_XOR ?
+ ETH_RSS_HASH_XOR : ETH_RSS_HASH_TOP;
+
+ if (rxfh->indir) {
+ err = hinic3_rss_get_indir_tbl(nic_dev->hwdev, rxfh->indir);
+ if (err)
+ return err;
+ }
+
+ if (rxfh->key)
+ memcpy(rxfh->key, nic_dev->rss_hkey, L2NIC_RSS_KEY_SIZE);
+
+ return err;
+}
+
+static int hinic3_update_hash_func_type(struct net_device *netdev, u8 hfunc)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ enum hinic3_rss_hash_type new_rss_hash_type;
+ int err;
+
+ switch (hfunc) {
+ case ETH_RSS_HASH_NO_CHANGE:
+ return 0;
+ case ETH_RSS_HASH_XOR:
+ new_rss_hash_type = HINIC3_RSS_HASH_ENGINE_TYPE_XOR;
+ break;
+ case ETH_RSS_HASH_TOP:
+ new_rss_hash_type = HINIC3_RSS_HASH_ENGINE_TYPE_TOEP;
+ break;
+ default:
+ netdev_err(netdev, "Unsupported hash func %u\n", hfunc);
+ return -EOPNOTSUPP;
+ }
+
+ if (new_rss_hash_type == nic_dev->rss_hash_type)
+ return 0;
+
+ err = hinic3_rss_set_hash_type(nic_dev->hwdev, nic_dev->rss_hash_type);
+ if (err) {
+ netdev_err(netdev, "Failed to set RSS hash type to HW\n");
+ return err;
+ }
+
+ nic_dev->rss_hash_type = new_rss_hash_type;
+
+ return 0;
+}
+
+int hinic3_set_rxfh(struct net_device *netdev,
+ struct ethtool_rxfh_param *rxfh,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ int err;
+
+ if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
+ netdev_err(netdev, "Not support to set rss parameters when rss is disable\n");
+ return -EOPNOTSUPP;
+ }
+
+ err = hinic3_update_hash_func_type(netdev, rxfh->hfunc);
+ if (err)
+ return err;
+
+ err = hinic3_set_rss_rxfh(netdev, rxfh->indir, rxfh->key);
+
+ return err;
+}
--git a/drivers/net/ethernet/huawei/hinic3/hinic3_rss.h b/drivers/net/ethernet/huawei/hinic3/hinic3_rss.h
index 78d82c2aca06..9f1b77780cd4 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rss.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rss.h
@@ -5,10 +5,29 @@
#define _HINIC3_RSS_H_
#include <linux/netdevice.h>
+#include <linux/ethtool.h>
int hinic3_rss_init(struct net_device *netdev);
void hinic3_rss_uninit(struct net_device *netdev);
void hinic3_try_to_enable_rss(struct net_device *netdev);
void hinic3_clear_rss_config(struct net_device *netdev);
+int hinic3_get_rxnfc(struct net_device *netdev,
+ struct ethtool_rxnfc *cmd, u32 *rule_locs);
+int hinic3_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd);
+
+void hinic3_get_channels(struct net_device *netdev,
+ struct ethtool_channels *channels);
+int hinic3_set_channels(struct net_device *netdev,
+ struct ethtool_channels *channels);
+
+u32 hinic3_get_rxfh_indir_size(struct net_device *netdev);
+u32 hinic3_get_rxfh_key_size(struct net_device *netdev);
+
+int hinic3_get_rxfh(struct net_device *netdev,
+ struct ethtool_rxfh_param *rxfh);
+int hinic3_set_rxfh(struct net_device *netdev,
+ struct ethtool_rxfh_param *rxfh,
+ struct netlink_ext_ack *extack);
+
#endif
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v08 5/5] hinic3: Remove unneeded coalesce parameters
From: Fan Gong @ 2026-06-08 12:36 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1780907605.git.wudi234@huawei.com>
Remove unneeded coalesce parameters in irq handling.
Co-developed-by: Wu Di <wudi234@huawei.com>
Signed-off-by: Wu Di <wudi234@huawei.com>
Co-developed-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
drivers/net/ethernet/huawei/hinic3/hinic3_irq.c | 6 +-----
drivers/net/ethernet/huawei/hinic3/hinic3_rx.h | 3 ---
2 files changed, 1 insertion(+), 8 deletions(-)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
index b7cd5f2f53a7..6a8695e0a69a 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
@@ -149,13 +149,9 @@ static int hinic3_set_interrupt_moder(struct net_device *netdev, u16 q_id,
nic_dev->intr_coalesce[q_id].resend_timer_cfg;
err = hinic3_set_interrupt_cfg(nic_dev->hwdev, info);
- if (err) {
+ if (err)
netdev_err(netdev,
"Failed to modify moderation for Queue: %u\n", q_id);
- } else {
- nic_dev->rxqs[q_id].last_coalesc_timer_cfg = coalesc_timer_cfg;
- nic_dev->rxqs[q_id].last_pending_limit = pending_limit;
- }
mutex_unlock(&nic_dev->change_res_mutex);
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
index c11d080408a7..2ab691ed11a9 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
@@ -111,9 +111,6 @@ struct hinic3_rxq {
dma_addr_t cqe_start_paddr;
struct dim dim;
-
- u8 last_coalesc_timer_cfg;
- u8 last_pending_limit;
} ____cacheline_aligned;
struct hinic3_dyna_rxq_res {
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v08 3/5] hinic3: Add ethtool coalesce ops
From: Fan Gong @ 2026-06-08 12:36 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1780907605.git.wudi234@huawei.com>
Implement following ethtool callback function:
.get_coalesce
.set_coalesce
These callbacks allow users to utilize ethtool for detailed
RX coalesce configuration and monitoring.
Co-developed-by: Wu Di <wudi234@huawei.com>
Signed-off-by: Wu Di <wudi234@huawei.com>
Co-developed-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
.../ethernet/huawei/hinic3/hinic3_ethtool.c | 249 +++++++++++++++++-
.../net/ethernet/huawei/hinic3/hinic3_irq.c | 3 +
.../net/ethernet/huawei/hinic3/hinic3_main.c | 1 +
.../ethernet/huawei/hinic3/hinic3_nic_dev.h | 2 +
4 files changed, 253 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
index 570f32d87e2e..0f01e52830fb 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
@@ -18,6 +18,11 @@
#include "hinic3_nic_cfg.h"
#define HINIC3_MGMT_VERSION_MAX_LEN 32
+/* Coalesce time properties in microseconds */
+#define COALESCE_PENDING_LIMIT_UNIT 8
+#define COALESCE_TIMER_CFG_UNIT 5
+#define COALESCE_MAX_PENDING_LIMIT (255 * COALESCE_PENDING_LIMIT_UNIT)
+#define COALESCE_MAX_TIMER_CFG (255 * COALESCE_TIMER_CFG_UNIT)
static void hinic3_get_drvinfo(struct net_device *netdev,
struct ethtool_drvinfo *info)
@@ -982,9 +987,247 @@ static void hinic3_get_pause_stats(struct net_device *netdev,
kfree(ps);
}
+static int hinic3_set_queue_coalesce(struct net_device *netdev, u16 q_id,
+ struct hinic3_intr_coal_info *coal,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_intr_coal_info *intr_coal;
+ struct hinic3_interrupt_info info = {};
+ int err;
+
+ if (nic_dev->adaptive_rx_coal) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Static coalesce not allowed in adaptive RX mode");
+ return -EINVAL;
+ }
+
+ if (!test_bit(HINIC3_INTF_UP, &nic_dev->flags) ||
+ q_id >= nic_dev->q_params.num_qps)
+ return 0;
+
+ spin_lock(&nic_dev->coal_lock);
+
+ intr_coal = &nic_dev->intr_coalesce[q_id];
+
+ intr_coal->coalesce_timer_cfg = coal->coalesce_timer_cfg;
+ intr_coal->pending_limit = coal->pending_limit;
+ intr_coal->rx_pending_limit_low = coal->rx_pending_limit_low;
+ intr_coal->rx_pending_limit_high = coal->rx_pending_limit_high;
+ spin_unlock(&nic_dev->coal_lock);
+
+ info.msix_index = nic_dev->q_params.irq_cfg[q_id].msix_entry_idx;
+ info.interrupt_coalesc_set = 1;
+ info.coalesc_timer_cfg = intr_coal->coalesce_timer_cfg;
+ info.pending_limit = intr_coal->pending_limit;
+ info.resend_timer_cfg = intr_coal->resend_timer_cfg;
+ err = hinic3_set_interrupt_cfg(nic_dev->hwdev, info);
+ if (err) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "Failed to set queue%u coalesce",
+ q_id);
+ return err;
+ }
+
+ return 0;
+}
+
+static int is_coalesce_exceed_limit(const struct ethtool_coalesce *coal,
+ struct netlink_ext_ack *extack)
+{
+ const struct {
+ const char *name;
+ u32 value;
+ u32 limit;
+ } coalesce_limits[] = {
+ {"rx_coalesce_usecs",
+ coal->rx_coalesce_usecs,
+ COALESCE_MAX_TIMER_CFG},
+ {"rx_max_coalesced_frames",
+ coal->rx_max_coalesced_frames,
+ COALESCE_MAX_PENDING_LIMIT},
+ {"rx_max_coalesced_frames_low",
+ coal->rx_max_coalesced_frames_low,
+ COALESCE_MAX_PENDING_LIMIT},
+ {"rx_max_coalesced_frames_high",
+ coal->rx_max_coalesced_frames_high,
+ COALESCE_MAX_PENDING_LIMIT},
+ };
+
+ for (int i = 0; i < ARRAY_SIZE(coalesce_limits); i++) {
+ if (coalesce_limits[i].value > coalesce_limits[i].limit) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "%s out of range %d-%d",
+ coalesce_limits[i].name,
+ 0,
+ coalesce_limits[i].limit);
+ return -ERANGE;
+ }
+ }
+ return 0;
+}
+
+static int is_coalesce_legal(const struct ethtool_coalesce *coal,
+ struct netlink_ext_ack *extack)
+{
+ int err;
+
+ err = is_coalesce_exceed_limit(coal, extack);
+ if (err)
+ return err;
+
+ if (coal->rx_max_coalesced_frames_low >
+ coal->rx_max_coalesced_frames_high) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "invalid coalesce frame high %u, low %u",
+ coal->rx_max_coalesced_frames_high,
+ coal->rx_max_coalesced_frames_low);
+ return -ERANGE;
+ }
+
+ return 0;
+}
+
+static void check_coalesce_align(struct net_device *netdev,
+ u32 item, u32 unit, const char *str)
+{
+ if (item % unit)
+ netdev_warn(netdev, "%s in %d units, change to %u\n",
+ str, unit, item - item % unit);
+}
+
+#define CHECK_COALESCE_ALIGN(member, unit) \
+ check_coalesce_align(netdev, member, unit, #member)
+
+static void check_coalesce_changed(struct net_device *netdev,
+ u32 item, u32 unit, u32 ori_val,
+ const char *obj_str, const char *str)
+{
+ if ((item / unit) != ori_val)
+ netdev_dbg(netdev, "Change %s from %d to %u %s\n",
+ str, ori_val * unit, item - item % unit, obj_str);
+}
+
+#define CHECK_COALESCE_CHANGED(member, unit, ori_val, obj_str) \
+ check_coalesce_changed(netdev, member, unit, ori_val, obj_str, #member)
+
+static int hinic3_set_hw_coal_param(struct net_device *netdev,
+ struct hinic3_intr_coal_info *intr_coal,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ int err;
+ u16 i;
+
+ for (i = 0; i < nic_dev->max_qps; i++) {
+ err = hinic3_set_queue_coalesce(netdev, i, intr_coal, extack);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int hinic3_get_coalesce(struct net_device *netdev,
+ struct ethtool_coalesce *coal,
+ struct kernel_ethtool_coalesce *kernel_coal,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_intr_coal_info *interrupt_info;
+
+ memset(coal, 0, sizeof(*coal));
+
+ interrupt_info = &nic_dev->intr_coalesce[0];
+
+ coal->use_adaptive_rx_coalesce = nic_dev->adaptive_rx_coal;
+
+ if (nic_dev->adaptive_rx_coal) {
+ coal->rx_max_coalesced_frames_low =
+ interrupt_info->rx_pending_limit_low *
+ COALESCE_PENDING_LIMIT_UNIT;
+ coal->rx_max_coalesced_frames_high =
+ interrupt_info->rx_pending_limit_high *
+ COALESCE_PENDING_LIMIT_UNIT;
+ } else {
+ /* TX/RX uses the same interrupt.
+ * So we only declare RX ethtool_coalesce parameters.
+ */
+ coal->rx_coalesce_usecs = interrupt_info->coalesce_timer_cfg *
+ COALESCE_TIMER_CFG_UNIT;
+ coal->rx_max_coalesced_frames = interrupt_info->pending_limit *
+ COALESCE_PENDING_LIMIT_UNIT;
+ }
+
+ return 0;
+}
+
+static int hinic3_set_coalesce(struct net_device *netdev,
+ struct ethtool_coalesce *coal,
+ struct kernel_ethtool_coalesce *kernel_coal,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_intr_coal_info *ori_intr_coal;
+ struct hinic3_intr_coal_info intr_coal = {};
+ const char *obj_str = "for netdev";
+ int err;
+
+ err = is_coalesce_legal(coal, extack);
+ if (err)
+ return err;
+
+ CHECK_COALESCE_ALIGN(coal->rx_coalesce_usecs, COALESCE_TIMER_CFG_UNIT);
+ CHECK_COALESCE_ALIGN(coal->rx_max_coalesced_frames,
+ COALESCE_PENDING_LIMIT_UNIT);
+ CHECK_COALESCE_ALIGN(coal->rx_max_coalesced_frames_high,
+ COALESCE_PENDING_LIMIT_UNIT);
+ CHECK_COALESCE_ALIGN(coal->rx_max_coalesced_frames_low,
+ COALESCE_PENDING_LIMIT_UNIT);
+
+ ori_intr_coal = &nic_dev->intr_coalesce[0];
+
+ CHECK_COALESCE_CHANGED(coal->rx_coalesce_usecs, COALESCE_TIMER_CFG_UNIT,
+ ori_intr_coal->coalesce_timer_cfg, obj_str);
+ CHECK_COALESCE_CHANGED(coal->rx_max_coalesced_frames,
+ COALESCE_PENDING_LIMIT_UNIT,
+ ori_intr_coal->pending_limit, obj_str);
+ CHECK_COALESCE_CHANGED(coal->rx_max_coalesced_frames_high,
+ COALESCE_PENDING_LIMIT_UNIT,
+ ori_intr_coal->rx_pending_limit_high, obj_str);
+ CHECK_COALESCE_CHANGED(coal->rx_max_coalesced_frames_low,
+ COALESCE_PENDING_LIMIT_UNIT,
+ ori_intr_coal->rx_pending_limit_low, obj_str);
+
+ intr_coal.coalesce_timer_cfg =
+ (u8)(coal->rx_coalesce_usecs / COALESCE_TIMER_CFG_UNIT);
+ intr_coal.pending_limit = (u8)(coal->rx_max_coalesced_frames /
+ COALESCE_PENDING_LIMIT_UNIT);
+
+ nic_dev->adaptive_rx_coal = coal->use_adaptive_rx_coalesce;
+
+ intr_coal.rx_pending_limit_high =
+ (u8)(coal->rx_max_coalesced_frames_high /
+ COALESCE_PENDING_LIMIT_UNIT);
+
+ intr_coal.rx_pending_limit_low =
+ (u8)(coal->rx_max_coalesced_frames_low /
+ COALESCE_PENDING_LIMIT_UNIT);
+
+ /* coalesce timer or pending set to zero will disable coalesce */
+ if (!nic_dev->adaptive_rx_coal &&
+ (!intr_coal.coalesce_timer_cfg || !intr_coal.pending_limit))
+ NL_SET_ERR_MSG_MOD(extack, "Coalesce will be disabled");
+
+ return hinic3_set_hw_coal_param(netdev, &intr_coal, extack);
+}
+
static const struct ethtool_ops hinic3_ethtool_ops = {
- .supported_coalesce_params = ETHTOOL_COALESCE_USECS |
- ETHTOOL_COALESCE_PKT_RATE_RX_USECS,
+ .supported_coalesce_params = ETHTOOL_COALESCE_RX_USECS |
+ ETHTOOL_COALESCE_RX_MAX_FRAMES |
+ ETHTOOL_COALESCE_USE_ADAPTIVE_RX |
+ ETHTOOL_COALESCE_RX_MAX_FRAMES_LOW |
+ ETHTOOL_COALESCE_RX_MAX_FRAMES_HIGH,
.get_link_ksettings = hinic3_get_link_ksettings,
.get_drvinfo = hinic3_get_drvinfo,
.get_msglevel = hinic3_get_msglevel,
@@ -1000,6 +1243,8 @@ static const struct ethtool_ops hinic3_ethtool_ops = {
.get_eth_ctrl_stats = hinic3_get_eth_ctrl_stats,
.get_rmon_stats = hinic3_get_rmon_stats,
.get_pause_stats = hinic3_get_pause_stats,
+ .get_coalesce = hinic3_get_coalesce,
+ .set_coalesce = hinic3_set_coalesce,
};
void hinic3_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
index bc4d879f9be4..b7cd5f2f53a7 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
@@ -173,9 +173,12 @@ static void hinic3_update_queue_coal(struct net_device *netdev, u16 q_id,
q_coal = &nic_dev->intr_coalesce[q_id];
coalesc_timer_cfg = (u8)coal_timer;
+
+ spin_lock(&nic_dev->coal_lock);
pending_limit = clamp_t(u8, coal_pkts >> HINIC3_COAL_PKT_SHIFT,
q_coal->rx_pending_limit_low,
q_coal->rx_pending_limit_high);
+ spin_unlock(&nic_dev->coal_lock);
hinic3_set_interrupt_moder(nic_dev->netdev, q_id,
coalesc_timer_cfg, pending_limit);
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_main.c b/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
index c87624a5e5dc..b4821c8042b1 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
@@ -180,6 +180,7 @@ static int hinic3_sw_init(struct net_device *netdev)
mutex_init(&nic_dev->port_state_mutex);
mutex_init(&nic_dev->change_res_mutex);
+ spin_lock_init(&nic_dev->coal_lock);
nic_dev->q_params.sq_depth = HINIC3_SQ_DEPTH;
nic_dev->q_params.rq_depth = HINIC3_RQ_DEPTH;
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h
index 005b2c01a988..b6e3b188fa78 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h
@@ -134,6 +134,8 @@ struct hinic3_nic_dev {
struct mutex port_state_mutex;
/* mutex to serialize channel/resource changes */
struct mutex change_res_mutex;
+ /* lock for set queue coalesce */
+ spinlock_t coal_lock;
struct list_head uc_filter_list;
struct list_head mc_filter_list;
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v08 2/5] hinic3: Add ethtool statistic ops
From: Fan Gong @ 2026-06-08 12:36 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1780907605.git.wudi234@huawei.com>
Add PF/VF statistics functions in TX and RX processing.
Implement following ethtool callback function:
.get_sset_count
.get_ethtool_stats
.get_strings
.get_eth_phy_stats
.get_eth_mac_stats
.get_eth_ctrl_stats
.get_rmon_stats
.get_pause_stats
These callbacks allow users to utilize ethtool for detailed
TX and RX netdev stats monitoring.
Add mgmt_msg_params_init_in_out() to support management
commands that require separate input and output buffers. This is
needed for retrieving the expanded PF/VF MAC statistics from HW,
which no longer fit in the input buffer used by the existing path.
Remove unused stats "restore_drop_sge" in struct hinic3_rxq_stats.
Co-developed-by: Wu Di <wudi234@huawei.com>
Signed-off-by: Wu Di <wudi234@huawei.com>
Co-developed-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
.../ethernet/huawei/hinic3/hinic3_ethtool.c | 483 ++++++++++++++++++
.../ethernet/huawei/hinic3/hinic3_hw_intf.h | 11 +
.../huawei/hinic3/hinic3_mgmt_interface.h | 37 ++
.../ethernet/huawei/hinic3/hinic3_nic_cfg.c | 64 +++
.../ethernet/huawei/hinic3/hinic3_nic_cfg.h | 109 ++++
.../net/ethernet/huawei/hinic3/hinic3_rx.c | 70 ++-
.../net/ethernet/huawei/hinic3/hinic3_rx.h | 15 +-
.../net/ethernet/huawei/hinic3/hinic3_tx.c | 62 ++-
.../net/ethernet/huawei/hinic3/hinic3_tx.h | 2 +
9 files changed, 845 insertions(+), 8 deletions(-)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
index c72db9da7701..570f32d87e2e 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
@@ -507,6 +507,481 @@ static int hinic3_set_ringparam(struct net_device *netdev,
return 0;
}
+struct hinic3_stats {
+ char name[ETH_GSTRING_LEN];
+ u32 size;
+ int offset;
+};
+
+#define HINIC3_RXQ_STAT(_stat_item) { \
+ .name = "rxq%d_"#_stat_item, \
+ .size = sizeof_field(struct hinic3_rxq_stats, _stat_item), \
+ .offset = offsetof(struct hinic3_rxq_stats, _stat_item) \
+}
+
+#define HINIC3_TXQ_STAT(_stat_item) { \
+ .name = "txq%d_"#_stat_item, \
+ .size = sizeof_field(struct hinic3_txq_stats, _stat_item), \
+ .offset = offsetof(struct hinic3_txq_stats, _stat_item) \
+}
+
+static struct hinic3_stats hinic3_rx_queue_stats[] = {
+ HINIC3_RXQ_STAT(csum_errors),
+ HINIC3_RXQ_STAT(other_errors),
+ HINIC3_RXQ_STAT(rx_buf_empty),
+ HINIC3_RXQ_STAT(alloc_skb_err),
+ HINIC3_RXQ_STAT(alloc_rx_buf_err),
+};
+
+static struct hinic3_stats hinic3_tx_queue_stats[] = {
+ HINIC3_TXQ_STAT(busy),
+ HINIC3_TXQ_STAT(skb_pad_err),
+ HINIC3_TXQ_STAT(frag_len_overflow),
+ HINIC3_TXQ_STAT(offload_cow_skb_err),
+ HINIC3_TXQ_STAT(map_frag_err),
+ HINIC3_TXQ_STAT(unknown_tunnel_pkt),
+ HINIC3_TXQ_STAT(frag_size_err),
+};
+
+#define HINIC3_FUNC_STAT(_stat_item) { \
+ .name = #_stat_item, \
+ .size = sizeof_field(struct l2nic_vport_stats, _stat_item), \
+ .offset = offsetof(struct l2nic_vport_stats, _stat_item) \
+}
+
+static struct hinic3_stats hinic3_function_stats[] = {
+ HINIC3_FUNC_STAT(tx_unicast_pkts_vport),
+ HINIC3_FUNC_STAT(tx_unicast_bytes_vport),
+ HINIC3_FUNC_STAT(tx_multicast_pkts_vport),
+ HINIC3_FUNC_STAT(tx_multicast_bytes_vport),
+ HINIC3_FUNC_STAT(tx_broadcast_pkts_vport),
+ HINIC3_FUNC_STAT(tx_broadcast_bytes_vport),
+
+ HINIC3_FUNC_STAT(rx_unicast_pkts_vport),
+ HINIC3_FUNC_STAT(rx_unicast_bytes_vport),
+ HINIC3_FUNC_STAT(rx_multicast_pkts_vport),
+ HINIC3_FUNC_STAT(rx_multicast_bytes_vport),
+ HINIC3_FUNC_STAT(rx_broadcast_pkts_vport),
+ HINIC3_FUNC_STAT(rx_broadcast_bytes_vport),
+
+ HINIC3_FUNC_STAT(tx_discard_vport),
+ HINIC3_FUNC_STAT(rx_discard_vport),
+ HINIC3_FUNC_STAT(tx_err_vport),
+ HINIC3_FUNC_STAT(rx_err_vport),
+};
+
+#define HINIC3_PORT_STAT(_stat_item) { \
+ .name = #_stat_item, \
+ .size = sizeof_field(struct mag_cmd_port_stats, _stat_item), \
+ .offset = offsetof(struct mag_cmd_port_stats, _stat_item) \
+}
+
+static struct hinic3_stats hinic3_port_stats[] = {
+ HINIC3_PORT_STAT(mac_tx_fragment_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_undersize_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_undermin_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_1519_max_bad_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_1519_max_good_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_oversize_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_jabber_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_bad_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_bad_oct_num),
+ HINIC3_PORT_STAT(mac_tx_good_oct_num),
+ HINIC3_PORT_STAT(mac_tx_total_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_uni_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri0_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri1_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri2_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri3_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri4_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri5_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri6_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri7_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_err_all_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_from_app_good_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_from_app_bad_pkt_num),
+
+ HINIC3_PORT_STAT(mac_rx_undermin_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_1519_max_bad_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_1519_max_good_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_bad_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_bad_oct_num),
+ HINIC3_PORT_STAT(mac_rx_good_oct_num),
+ HINIC3_PORT_STAT(mac_rx_total_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_uni_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri0_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri1_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri2_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri3_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri4_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri5_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri6_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri7_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_send_app_good_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_send_app_bad_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_unfilter_pkt_num),
+};
+
+static int hinic3_get_sset_count(struct net_device *netdev, int sset)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ int count, q_num;
+
+ switch (sset) {
+ case ETH_SS_STATS:
+ q_num = nic_dev->q_params.num_qps;
+ count = ARRAY_SIZE(hinic3_function_stats) +
+ (ARRAY_SIZE(hinic3_tx_queue_stats) +
+ ARRAY_SIZE(hinic3_rx_queue_stats)) *
+ q_num;
+
+ if (!HINIC3_IS_VF(nic_dev->hwdev))
+ count += ARRAY_SIZE(hinic3_port_stats);
+
+ return count;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static u64 get_val_of_ptr(u32 size, const void *ptr)
+{
+ u64 ret = size == sizeof(u64) ? *(u64 *)ptr :
+ size == sizeof(u32) ? *(u32 *)ptr :
+ size == sizeof(u16) ? *(u16 *)ptr :
+ *(u8 *)ptr;
+
+ return ret;
+}
+
+static void hinic3_get_drv_queue_stats(struct net_device *netdev, u64 *data)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_txq_stats txq_stats = {};
+ struct hinic3_rxq_stats rxq_stats = {};
+ u16 i = 0, j, qid;
+ char *p;
+
+ for (qid = 0; qid < nic_dev->q_params.num_qps; qid++) {
+ if (!nic_dev->txqs)
+ break;
+
+ hinic3_txq_get_stats(&nic_dev->txqs[qid], &txq_stats);
+ for (j = 0; j < ARRAY_SIZE(hinic3_tx_queue_stats); j++, i++) {
+ p = (char *)&txq_stats +
+ hinic3_tx_queue_stats[j].offset;
+ data[i] = get_val_of_ptr(hinic3_tx_queue_stats[j].size,
+ p);
+ }
+ }
+
+ i = nic_dev->q_params.num_qps * ARRAY_SIZE(hinic3_tx_queue_stats);
+ for (qid = 0; qid < nic_dev->q_params.num_qps; qid++) {
+ if (!nic_dev->rxqs)
+ break;
+
+ hinic3_rxq_get_stats(&nic_dev->rxqs[qid], &rxq_stats);
+ for (j = 0; j < ARRAY_SIZE(hinic3_rx_queue_stats); j++, i++) {
+ p = (char *)&rxq_stats +
+ hinic3_rx_queue_stats[j].offset;
+ data[i] = get_val_of_ptr(hinic3_rx_queue_stats[j].size,
+ p);
+ }
+ }
+}
+
+static u16 hinic3_get_ethtool_port_stats(struct net_device *netdev, u64 *data)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ u16 i = 0, j;
+ char *p;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ goto err_zero_stats;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get port stats from fw\n");
+ goto err_zero_stats;
+ }
+
+ for (j = 0; j < ARRAY_SIZE(hinic3_port_stats); j++, i++) {
+ p = (char *)ps + hinic3_port_stats[j].offset;
+ data[i] = get_val_of_ptr(hinic3_port_stats[j].size, p);
+ }
+
+ kfree(ps);
+
+ return i;
+
+err_zero_stats:
+ memset(&data[i], 0, ARRAY_SIZE(hinic3_port_stats) * sizeof(*data));
+
+ return i + ARRAY_SIZE(hinic3_port_stats);
+}
+
+static void hinic3_get_ethtool_stats(struct net_device *netdev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct l2nic_vport_stats vport_stats = {};
+ u16 i = 0, j;
+ char *p;
+ int err;
+
+ err = hinic3_get_vport_stats(nic_dev->hwdev,
+ hinic3_global_func_id(nic_dev->hwdev),
+ &vport_stats);
+ if (err)
+ netdev_err(netdev, "Failed to get function stats from fw\n");
+
+ for (j = 0; j < ARRAY_SIZE(hinic3_function_stats); j++, i++) {
+ p = (char *)&vport_stats + hinic3_function_stats[j].offset;
+ data[i] = get_val_of_ptr(hinic3_function_stats[j].size, p);
+ }
+
+ if (!HINIC3_IS_VF(nic_dev->hwdev))
+ i += hinic3_get_ethtool_port_stats(netdev, data + i);
+
+ hinic3_get_drv_queue_stats(netdev, data + i);
+}
+
+static u16 hinic3_get_hw_stats_strings(struct net_device *netdev, char *p)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ u16 i, cnt = 0;
+
+ for (i = 0; i < ARRAY_SIZE(hinic3_function_stats); i++) {
+ memcpy(p, hinic3_function_stats[i].name, ETH_GSTRING_LEN);
+ p += ETH_GSTRING_LEN;
+ cnt++;
+ }
+
+ if (!HINIC3_IS_VF(nic_dev->hwdev)) {
+ for (i = 0; i < ARRAY_SIZE(hinic3_port_stats); i++) {
+ memcpy(p, hinic3_port_stats[i].name, ETH_GSTRING_LEN);
+ p += ETH_GSTRING_LEN;
+ cnt++;
+ }
+ }
+
+ return cnt;
+}
+
+static void hinic3_get_qp_stats_strings(struct net_device *netdev, char *p)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ u8 *data = p;
+ u16 i, j;
+
+ for (i = 0; i < nic_dev->q_params.num_qps; i++) {
+ for (j = 0; j < ARRAY_SIZE(hinic3_tx_queue_stats); j++)
+ ethtool_sprintf(&data,
+ hinic3_tx_queue_stats[j].name, i);
+ }
+
+ for (i = 0; i < nic_dev->q_params.num_qps; i++) {
+ for (j = 0; j < ARRAY_SIZE(hinic3_rx_queue_stats); j++)
+ ethtool_sprintf(&data,
+ hinic3_rx_queue_stats[j].name, i);
+ }
+}
+
+static void hinic3_get_strings(struct net_device *netdev,
+ u32 stringset, u8 *data)
+{
+ char *p = (char *)data;
+ u16 offset;
+
+ switch (stringset) {
+ case ETH_SS_STATS:
+ offset = hinic3_get_hw_stats_strings(netdev, p);
+ hinic3_get_qp_stats_strings(netdev,
+ p + offset * ETH_GSTRING_LEN);
+
+ return;
+ default:
+ netdev_err(netdev, "Invalid string set %u.\n", stringset);
+ return;
+ }
+}
+
+static void hinic3_get_eth_phy_stats(struct net_device *netdev,
+ struct ethtool_eth_phy_stats *phy_stats)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ return;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get eth phy stats from fw\n");
+ return;
+ }
+
+ phy_stats->SymbolErrorDuringCarrier = ps->mac_rx_sym_err_pkt_num;
+
+ kfree(ps);
+}
+
+static void hinic3_get_eth_mac_stats(struct net_device *netdev,
+ struct ethtool_eth_mac_stats *mac_stats)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ return;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get eth mac stats from fw\n");
+ return;
+ }
+
+ mac_stats->FramesTransmittedOK = ps->mac_tx_good_pkt_num;
+ mac_stats->FramesReceivedOK = ps->mac_rx_good_pkt_num;
+ mac_stats->FrameCheckSequenceErrors = ps->mac_rx_fcs_err_pkt_num;
+ mac_stats->OctetsTransmittedOK = ps->mac_tx_total_oct_num;
+ mac_stats->OctetsReceivedOK = ps->mac_rx_total_oct_num;
+ mac_stats->MulticastFramesXmittedOK = ps->mac_tx_multi_pkt_num;
+ mac_stats->BroadcastFramesXmittedOK = ps->mac_tx_broad_pkt_num;
+ mac_stats->MulticastFramesReceivedOK = ps->mac_rx_multi_pkt_num;
+ mac_stats->BroadcastFramesReceivedOK = ps->mac_rx_broad_pkt_num;
+
+ kfree(ps);
+}
+
+static void hinic3_get_eth_ctrl_stats(struct net_device *netdev,
+ struct ethtool_eth_ctrl_stats *ctrl_stats)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ return;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get eth ctrl stats from fw\n");
+ return;
+ }
+
+ ctrl_stats->MACControlFramesTransmitted = ps->mac_tx_control_pkt_num;
+ ctrl_stats->MACControlFramesReceived = ps->mac_rx_control_pkt_num;
+
+ kfree(ps);
+}
+
+static const struct ethtool_rmon_hist_range hinic3_rmon_ranges[] = {
+ { 0, 64 },
+ { 65, 127 },
+ { 128, 255 },
+ { 256, 511 },
+ { 512, 1023 },
+ { 1024, 1518 },
+ { 1519, 2047 },
+ { 2048, 4095 },
+ { 4096, 8191 },
+ { 8192, 9216 },
+ { 9217, 12287 },
+ {}
+};
+
+static void hinic3_get_rmon_stats(struct net_device *netdev,
+ struct ethtool_rmon_stats *rmon_stats,
+ const struct ethtool_rmon_hist_range **ranges)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ return;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get eth rmon stats from fw\n");
+ return;
+ }
+
+ rmon_stats->undersize_pkts = ps->mac_rx_undersize_pkt_num;
+ rmon_stats->oversize_pkts = ps->mac_rx_oversize_pkt_num;
+ rmon_stats->fragments = ps->mac_rx_fragment_pkt_num;
+ rmon_stats->jabbers = ps->mac_rx_jabber_pkt_num;
+
+ rmon_stats->hist[0] = ps->mac_rx_64_oct_pkt_num;
+ rmon_stats->hist[1] = ps->mac_rx_65_127_oct_pkt_num;
+ rmon_stats->hist[2] = ps->mac_rx_128_255_oct_pkt_num;
+ rmon_stats->hist[3] = ps->mac_rx_256_511_oct_pkt_num;
+ rmon_stats->hist[4] = ps->mac_rx_512_1023_oct_pkt_num;
+ rmon_stats->hist[5] = ps->mac_rx_1024_1518_oct_pkt_num;
+ rmon_stats->hist[6] = ps->mac_rx_1519_2047_oct_pkt_num;
+ rmon_stats->hist[7] = ps->mac_rx_2048_4095_oct_pkt_num;
+ rmon_stats->hist[8] = ps->mac_rx_4096_8191_oct_pkt_num;
+ rmon_stats->hist[9] = ps->mac_rx_8192_9216_oct_pkt_num;
+ rmon_stats->hist[10] = ps->mac_rx_9217_12287_oct_pkt_num;
+
+ rmon_stats->hist_tx[0] = ps->mac_tx_64_oct_pkt_num;
+ rmon_stats->hist_tx[1] = ps->mac_tx_65_127_oct_pkt_num;
+ rmon_stats->hist_tx[2] = ps->mac_tx_128_255_oct_pkt_num;
+ rmon_stats->hist_tx[3] = ps->mac_tx_256_511_oct_pkt_num;
+ rmon_stats->hist_tx[4] = ps->mac_tx_512_1023_oct_pkt_num;
+ rmon_stats->hist_tx[5] = ps->mac_tx_1024_1518_oct_pkt_num;
+ rmon_stats->hist_tx[6] = ps->mac_tx_1519_2047_oct_pkt_num;
+ rmon_stats->hist_tx[7] = ps->mac_tx_2048_4095_oct_pkt_num;
+ rmon_stats->hist_tx[8] = ps->mac_tx_4096_8191_oct_pkt_num;
+ rmon_stats->hist_tx[9] = ps->mac_tx_8192_9216_oct_pkt_num;
+ rmon_stats->hist_tx[10] = ps->mac_tx_9217_12287_oct_pkt_num;
+
+ *ranges = hinic3_rmon_ranges;
+
+ kfree(ps);
+}
+
+static void hinic3_get_pause_stats(struct net_device *netdev,
+ struct ethtool_pause_stats *pause_stats)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ return;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get eth pause stats from fw\n");
+ return;
+ }
+
+ pause_stats->tx_pause_frames = ps->mac_tx_pause_num;
+ pause_stats->rx_pause_frames = ps->mac_rx_pause_num;
+
+ kfree(ps);
+}
+
static const struct ethtool_ops hinic3_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_PKT_RATE_RX_USECS,
@@ -517,6 +992,14 @@ static const struct ethtool_ops hinic3_ethtool_ops = {
.get_link = ethtool_op_get_link,
.get_ringparam = hinic3_get_ringparam,
.set_ringparam = hinic3_set_ringparam,
+ .get_sset_count = hinic3_get_sset_count,
+ .get_ethtool_stats = hinic3_get_ethtool_stats,
+ .get_strings = hinic3_get_strings,
+ .get_eth_phy_stats = hinic3_get_eth_phy_stats,
+ .get_eth_mac_stats = hinic3_get_eth_mac_stats,
+ .get_eth_ctrl_stats = hinic3_get_eth_ctrl_stats,
+ .get_rmon_stats = hinic3_get_rmon_stats,
+ .get_pause_stats = hinic3_get_pause_stats,
};
void hinic3_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_hw_intf.h b/drivers/net/ethernet/huawei/hinic3/hinic3_hw_intf.h
index cfc9daa3034f..6b0f486ba590 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_hw_intf.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_hw_intf.h
@@ -53,6 +53,17 @@ static inline void mgmt_msg_params_init_default(struct mgmt_msg_params *msg_para
msg_params->timeout_ms = 0;
}
+static inline void
+mgmt_msg_params_init_in_out(struct mgmt_msg_params *msg_params, void *in_buf,
+ void *out_buf, u32 in_buf_size, u32 out_buf_size)
+{
+ msg_params->buf_in = in_buf;
+ msg_params->buf_out = out_buf;
+ msg_params->in_size = in_buf_size;
+ msg_params->expected_out_size = out_buf_size;
+ msg_params->timeout_ms = 0;
+}
+
enum cfg_cmd {
CFG_CMD_GET_DEV_CAP = 0,
};
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h b/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
index c5bca3c4af96..76c691f82703 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
@@ -143,6 +143,41 @@ struct l2nic_cmd_set_dcb_state {
u8 rsvd[7];
};
+struct l2nic_port_stats_info {
+ struct mgmt_msg_head msg_head;
+ u16 func_id;
+ u16 rsvd1;
+};
+
+struct l2nic_vport_stats {
+ u64 tx_unicast_pkts_vport;
+ u64 tx_unicast_bytes_vport;
+ u64 tx_multicast_pkts_vport;
+ u64 tx_multicast_bytes_vport;
+ u64 tx_broadcast_pkts_vport;
+ u64 tx_broadcast_bytes_vport;
+
+ u64 rx_unicast_pkts_vport;
+ u64 rx_unicast_bytes_vport;
+ u64 rx_multicast_pkts_vport;
+ u64 rx_multicast_bytes_vport;
+ u64 rx_broadcast_pkts_vport;
+ u64 rx_broadcast_bytes_vport;
+
+ u64 tx_discard_vport;
+ u64 rx_discard_vport;
+ u64 tx_err_vport;
+ u64 rx_err_vport;
+};
+
+struct l2nic_cmd_vport_stats {
+ struct mgmt_msg_head msg_head;
+ u32 stats_size;
+ u32 rsvd1;
+ struct l2nic_vport_stats stats;
+ u64 rsvd2[6];
+};
+
struct l2nic_cmd_lro_config {
struct mgmt_msg_head msg_head;
u16 func_id;
@@ -234,6 +269,7 @@ enum l2nic_cmd {
L2NIC_CMD_SET_VPORT_ENABLE = 6,
L2NIC_CMD_SET_RX_MODE = 7,
L2NIC_CMD_SET_SQ_CI_ATTR = 8,
+ L2NIC_CMD_GET_VPORT_STAT = 9,
L2NIC_CMD_CLEAR_QP_RESOURCE = 11,
L2NIC_CMD_CFG_RX_LRO = 13,
L2NIC_CMD_CFG_LRO_TIMER = 14,
@@ -272,6 +308,7 @@ enum mag_cmd {
MAG_CMD_SET_PORT_ENABLE = 6,
MAG_CMD_GET_LINK_STATUS = 7,
+ MAG_CMD_GET_PORT_STAT = 151,
MAG_CMD_GET_PORT_INFO = 153,
};
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.c b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.c
index de5a7984d2cb..1b14dc824ce1 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.c
@@ -639,6 +639,42 @@ int hinic3_get_link_status(struct hinic3_hwdev *hwdev, bool *link_status_up)
return 0;
}
+int hinic3_get_phy_port_stats(struct hinic3_hwdev *hwdev,
+ struct mag_cmd_port_stats *stats)
+{
+ struct mag_cmd_port_stats_info stats_info = {};
+ struct mag_cmd_get_port_stat *ps;
+ struct mgmt_msg_params msg_params = {};
+ int err;
+
+ ps = kzalloc_obj(*ps);
+ if (!ps)
+ return -ENOMEM;
+
+ stats_info.port_id = hinic3_physical_port_id(hwdev);
+
+ mgmt_msg_params_init_in_out(&msg_params, &stats_info, ps,
+ sizeof(stats_info), sizeof(*ps));
+
+ err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_HILINK,
+ MAG_CMD_GET_PORT_STAT, &msg_params);
+
+ if (err || ps->head.status) {
+ dev_err(hwdev->dev,
+ "Failed to get port statistics, err: %d, status: 0x%x\n",
+ err, ps->head.status);
+ err = -EFAULT;
+ goto out;
+ }
+
+ memcpy(stats, &ps->counter, sizeof(*stats));
+
+out:
+ kfree(ps);
+
+ return err;
+}
+
int hinic3_get_port_info(struct hinic3_hwdev *hwdev,
struct hinic3_nic_port_info *port_info)
{
@@ -738,3 +774,31 @@ int hinic3_get_pause_info(struct hinic3_nic_dev *nic_dev,
return hinic3_cfg_hw_pause(nic_dev->hwdev, MGMT_MSG_CMD_OP_GET,
nic_pause);
}
+
+int hinic3_get_vport_stats(struct hinic3_hwdev *hwdev, u16 func_id,
+ struct l2nic_vport_stats *stats)
+{
+ struct l2nic_cmd_vport_stats vport_stats = {};
+ struct l2nic_port_stats_info stats_info = {};
+ struct mgmt_msg_params msg_params = {};
+ int err;
+
+ stats_info.func_id = func_id;
+
+ mgmt_msg_params_init_in_out(&msg_params, &stats_info, &vport_stats,
+ sizeof(stats_info), sizeof(vport_stats));
+
+ err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
+ L2NIC_CMD_GET_VPORT_STAT, &msg_params);
+
+ if (err || vport_stats.msg_head.status) {
+ dev_err(hwdev->dev,
+ "Failed to get function statistics, err: %d, status: 0x%x\n",
+ err, vport_stats.msg_head.status);
+ return -EFAULT;
+ }
+
+ memcpy(stats, &vport_stats.stats, sizeof(*stats));
+
+ return 0;
+}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.h b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.h
index 5d52202a8d4e..80573c121539 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.h
@@ -129,6 +129,110 @@ struct mag_cmd_get_xsfp_present {
u8 rsvd[2];
};
+struct mag_cmd_port_stats {
+ u64 mac_tx_fragment_pkt_num;
+ u64 mac_tx_undersize_pkt_num;
+ u64 mac_tx_undermin_pkt_num;
+ u64 mac_tx_64_oct_pkt_num;
+ u64 mac_tx_65_127_oct_pkt_num;
+ u64 mac_tx_128_255_oct_pkt_num;
+ u64 mac_tx_256_511_oct_pkt_num;
+ u64 mac_tx_512_1023_oct_pkt_num;
+ u64 mac_tx_1024_1518_oct_pkt_num;
+ u64 mac_tx_1519_2047_oct_pkt_num;
+ u64 mac_tx_2048_4095_oct_pkt_num;
+ u64 mac_tx_4096_8191_oct_pkt_num;
+ u64 mac_tx_8192_9216_oct_pkt_num;
+ u64 mac_tx_9217_12287_oct_pkt_num;
+ u64 mac_tx_12288_16383_oct_pkt_num;
+ u64 mac_tx_1519_max_bad_pkt_num;
+ u64 mac_tx_1519_max_good_pkt_num;
+ u64 mac_tx_oversize_pkt_num;
+ u64 mac_tx_jabber_pkt_num;
+ u64 mac_tx_bad_pkt_num;
+ u64 mac_tx_bad_oct_num;
+ u64 mac_tx_good_pkt_num;
+ u64 mac_tx_good_oct_num;
+ u64 mac_tx_total_pkt_num;
+ u64 mac_tx_total_oct_num;
+ u64 mac_tx_uni_pkt_num;
+ u64 mac_tx_multi_pkt_num;
+ u64 mac_tx_broad_pkt_num;
+ u64 mac_tx_pause_num;
+ u64 mac_tx_pfc_pkt_num;
+ u64 mac_tx_pfc_pri0_pkt_num;
+ u64 mac_tx_pfc_pri1_pkt_num;
+ u64 mac_tx_pfc_pri2_pkt_num;
+ u64 mac_tx_pfc_pri3_pkt_num;
+ u64 mac_tx_pfc_pri4_pkt_num;
+ u64 mac_tx_pfc_pri5_pkt_num;
+ u64 mac_tx_pfc_pri6_pkt_num;
+ u64 mac_tx_pfc_pri7_pkt_num;
+ u64 mac_tx_control_pkt_num;
+ u64 mac_tx_err_all_pkt_num;
+ u64 mac_tx_from_app_good_pkt_num;
+ u64 mac_tx_from_app_bad_pkt_num;
+
+ u64 mac_rx_fragment_pkt_num;
+ u64 mac_rx_undersize_pkt_num;
+ u64 mac_rx_undermin_pkt_num;
+ u64 mac_rx_64_oct_pkt_num;
+ u64 mac_rx_65_127_oct_pkt_num;
+ u64 mac_rx_128_255_oct_pkt_num;
+ u64 mac_rx_256_511_oct_pkt_num;
+ u64 mac_rx_512_1023_oct_pkt_num;
+ u64 mac_rx_1024_1518_oct_pkt_num;
+ u64 mac_rx_1519_2047_oct_pkt_num;
+ u64 mac_rx_2048_4095_oct_pkt_num;
+ u64 mac_rx_4096_8191_oct_pkt_num;
+ u64 mac_rx_8192_9216_oct_pkt_num;
+ u64 mac_rx_9217_12287_oct_pkt_num;
+ u64 mac_rx_12288_16383_oct_pkt_num;
+ u64 mac_rx_1519_max_bad_pkt_num;
+ u64 mac_rx_1519_max_good_pkt_num;
+ u64 mac_rx_oversize_pkt_num;
+ u64 mac_rx_jabber_pkt_num;
+ u64 mac_rx_bad_pkt_num;
+ u64 mac_rx_bad_oct_num;
+ u64 mac_rx_good_pkt_num;
+ u64 mac_rx_good_oct_num;
+ u64 mac_rx_total_pkt_num;
+ u64 mac_rx_total_oct_num;
+ u64 mac_rx_uni_pkt_num;
+ u64 mac_rx_multi_pkt_num;
+ u64 mac_rx_broad_pkt_num;
+ u64 mac_rx_pause_num;
+ u64 mac_rx_pfc_pkt_num;
+ u64 mac_rx_pfc_pri0_pkt_num;
+ u64 mac_rx_pfc_pri1_pkt_num;
+ u64 mac_rx_pfc_pri2_pkt_num;
+ u64 mac_rx_pfc_pri3_pkt_num;
+ u64 mac_rx_pfc_pri4_pkt_num;
+ u64 mac_rx_pfc_pri5_pkt_num;
+ u64 mac_rx_pfc_pri6_pkt_num;
+ u64 mac_rx_pfc_pri7_pkt_num;
+ u64 mac_rx_control_pkt_num;
+ u64 mac_rx_sym_err_pkt_num;
+ u64 mac_rx_fcs_err_pkt_num;
+ u64 mac_rx_send_app_good_pkt_num;
+ u64 mac_rx_send_app_bad_pkt_num;
+ u64 mac_rx_unfilter_pkt_num;
+};
+
+struct mag_cmd_port_stats_info {
+ struct mgmt_msg_head head;
+
+ u8 port_id;
+ u8 rsvd0[3];
+};
+
+struct mag_cmd_get_port_stat {
+ struct mgmt_msg_head head;
+
+ struct mag_cmd_port_stats counter;
+ u64 rsvd1[15];
+};
+
enum link_err_type {
LINK_ERR_MODULE_UNRECOGENIZED,
LINK_ERR_NUM,
@@ -209,6 +313,11 @@ int hinic3_get_port_info(struct hinic3_hwdev *hwdev,
struct hinic3_nic_port_info *port_info);
int hinic3_set_vport_enable(struct hinic3_hwdev *hwdev, u16 func_id,
bool enable);
+int hinic3_get_phy_port_stats(struct hinic3_hwdev *hwdev,
+ struct mag_cmd_port_stats *stats);
+int hinic3_get_vport_stats(struct hinic3_hwdev *hwdev, u16 func_id,
+ struct l2nic_vport_stats *stats);
+
int hinic3_add_vlan(struct hinic3_hwdev *hwdev, u16 vlan_id, u16 func_id);
int hinic3_del_vlan(struct hinic3_hwdev *hwdev, u16 vlan_id, u16 func_id);
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
index b5b601469517..a332f814e625 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
@@ -29,7 +29,7 @@
#define HINIC3_LRO_PKT_HDR_LEN_IPV4 66
#define HINIC3_LRO_PKT_HDR_LEN_IPV6 86
#define HINIC3_LRO_PKT_HDR_LEN(cqe) \
- (RQ_CQE_OFFOLAD_TYPE_GET((cqe)->offload_type, IP_TYPE) == \
+ (RQ_CQE_OFFOLAD_TYPE_GET(le32_to_cpu((cqe)->offload_type), IP_TYPE) == \
HINIC3_RX_IPV6_PKT ? HINIC3_LRO_PKT_HDR_LEN_IPV6 : \
HINIC3_LRO_PKT_HDR_LEN_IPV4)
@@ -46,7 +46,6 @@ static void hinic3_rxq_clean_stats(struct hinic3_rxq_stats *rxq_stats)
rxq_stats->alloc_skb_err = 0;
rxq_stats->alloc_rx_buf_err = 0;
- rxq_stats->restore_drop_sge = 0;
u64_stats_update_end(&rxq_stats->syncp);
}
@@ -155,8 +154,14 @@ static u32 hinic3_rx_fill_buffers(struct hinic3_rxq *rxq)
err = rx_alloc_mapped_page(rxq->page_pool, rx_info,
rxq->buf_len);
- if (unlikely(err))
+ if (unlikely(err)) {
+ preempt_disable();
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.alloc_rx_buf_err++;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
+ preempt_enable();
break;
+ }
dma_addr = page_pool_get_dma_addr(rx_info->page) +
rx_info->page_offset;
@@ -170,6 +175,12 @@ static u32 hinic3_rx_fill_buffers(struct hinic3_rxq *rxq)
rxq->next_to_update << HINIC3_NORMAL_RQ_WQE);
rxq->delta -= i;
rxq->next_to_alloc = rxq->next_to_update;
+ } else if (free_wqebbs == rxq->q_depth - 1) {
+ preempt_disable();
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.rx_buf_empty++;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
+ preempt_enable();
}
return i;
@@ -330,11 +341,23 @@ static void hinic3_rx_csum(struct hinic3_rxq *rxq, u32 offload_type,
struct net_device *netdev = rxq->netdev;
bool l2_tunnel;
+ if (unlikely(csum_err == HINIC3_RX_CSUM_IPSU_OTHER_ERR)) {
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.other_errors++;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
+ }
+
if (!(netdev->features & NETIF_F_RXCSUM))
return;
if (unlikely(csum_err)) {
/* pkt type is recognized by HW, and csum is wrong */
+ if (!(csum_err & (HINIC3_RX_CSUM_HW_CHECK_NONE |
+ HINIC3_RX_CSUM_IPSU_OTHER_ERR))) {
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.csum_errors++;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
+ }
skb->ip_summed = CHECKSUM_NONE;
return;
}
@@ -387,8 +410,12 @@ static int recv_one_pkt(struct hinic3_rxq *rxq, struct hinic3_rq_cqe *rx_cqe,
u16 num_lro;
skb = hinic3_fetch_rx_buffer(rxq, pkt_len);
- if (unlikely(!skb))
+ if (unlikely(!skb)) {
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.alloc_skb_err++;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
return -ENOMEM;
+ }
/* place header in linear portion of buffer */
if (skb_is_nonlinear(skb))
@@ -550,11 +577,29 @@ int hinic3_configure_rxqs(struct net_device *netdev, u16 num_rq,
return 0;
}
+void hinic3_rxq_get_stats(struct hinic3_rxq *rxq,
+ struct hinic3_rxq_stats *stats)
+{
+ struct hinic3_rxq_stats *rxq_stats = &rxq->rxq_stats;
+ unsigned int start;
+
+ do {
+ start = u64_stats_fetch_begin(&rxq_stats->syncp);
+ stats->csum_errors = rxq_stats->csum_errors;
+ stats->other_errors = rxq_stats->other_errors;
+ stats->rx_buf_empty = rxq_stats->rx_buf_empty;
+ stats->alloc_skb_err = rxq_stats->alloc_skb_err;
+ stats->alloc_rx_buf_err = rxq_stats->alloc_rx_buf_err;
+ } while (u64_stats_fetch_retry(&rxq_stats->syncp, start));
+}
+
int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(rxq->netdev);
+ u32 ip_type, offload_type, pkt_hdr_len;
u32 sw_ci, status, pkt_len, vlan_len;
struct hinic3_rq_cqe *rx_cqe;
+ u64 rx_bytes = 0;
u32 num_wqe = 0;
int nr_pkts = 0;
u16 num_lro;
@@ -574,10 +619,20 @@ int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget)
if (recv_one_pkt(rxq, rx_cqe, pkt_len, vlan_len, status))
break;
+ rx_bytes += pkt_len;
nr_pkts++;
num_lro = RQ_CQE_STATUS_GET(status, NUM_LRO);
- if (num_lro)
+ if (num_lro) {
+ offload_type = le32_to_cpu(rx_cqe->offload_type);
+ ip_type = RQ_CQE_OFFOLAD_TYPE_GET(offload_type,
+ IP_TYPE);
+ pkt_hdr_len = ip_type == HINIC3_RX_IPV6_PKT ?
+ HINIC3_LRO_PKT_HDR_LEN_IPV6 :
+ HINIC3_LRO_PKT_HDR_LEN_IPV4;
+
+ rx_bytes += (num_lro - 1) * pkt_hdr_len;
num_wqe += hinic3_get_sge_num(rxq, pkt_len);
+ }
rx_cqe->status = 0;
@@ -588,5 +643,10 @@ int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget)
if (rxq->delta >= HINIC3_RX_BUFFER_WRITE)
hinic3_rx_fill_buffers(rxq);
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.packets += (u64)nr_pkts;
+ rxq->rxq_stats.bytes += rx_bytes;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
+
return nr_pkts;
}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
index 06d1b3299e7c..c11d080408a7 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
@@ -8,6 +8,17 @@
#include <linux/dim.h>
#include <linux/netdevice.h>
+/* rx cqe checksum err */
+#define HINIC3_RX_CSUM_IP_CSUM_ERR BIT(0)
+#define HINIC3_RX_CSUM_TCP_CSUM_ERR BIT(1)
+#define HINIC3_RX_CSUM_UDP_CSUM_ERR BIT(2)
+#define HINIC3_RX_CSUM_IGMP_CSUM_ERR BIT(3)
+#define HINIC3_RX_CSUM_ICMPV4_CSUM_ERR BIT(4)
+#define HINIC3_RX_CSUM_ICMPV6_CSUM_ERR BIT(5)
+#define HINIC3_RX_CSUM_SCTP_CRC_ERR BIT(6)
+#define HINIC3_RX_CSUM_HW_CHECK_NONE BIT(7)
+#define HINIC3_RX_CSUM_IPSU_OTHER_ERR BIT(8)
+
#define RQ_CQE_OFFOLAD_TYPE_PKT_TYPE_MASK GENMASK(4, 0)
#define RQ_CQE_OFFOLAD_TYPE_IP_TYPE_MASK GENMASK(6, 5)
#define RQ_CQE_OFFOLAD_TYPE_TUNNEL_PKT_FORMAT_MASK GENMASK(11, 8)
@@ -39,7 +50,6 @@ struct hinic3_rxq_stats {
u64 rx_buf_empty;
u64 alloc_skb_err;
u64 alloc_rx_buf_err;
- u64 restore_drop_sge;
struct u64_stats_sync syncp;
};
@@ -123,6 +133,9 @@ void hinic3_free_rxqs_res(struct net_device *netdev, u16 num_rq,
u32 rq_depth, struct hinic3_dyna_rxq_res *rxqs_res);
int hinic3_configure_rxqs(struct net_device *netdev, u16 num_rq,
u32 rq_depth, struct hinic3_dyna_rxq_res *rxqs_res);
+
+void hinic3_rxq_get_stats(struct hinic3_rxq *rxq,
+ struct hinic3_rxq_stats *stats);
int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget);
#endif
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
index 9306bf0020ca..019ea4d03c19 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
@@ -97,8 +97,12 @@ static int hinic3_tx_map_skb(struct net_device *netdev, struct sk_buff *skb,
dma_info[0].dma = dma_map_single(&pdev->dev, skb->data,
skb_headlen(skb), DMA_TO_DEVICE);
- if (dma_mapping_error(&pdev->dev, dma_info[0].dma))
+ if (dma_mapping_error(&pdev->dev, dma_info[0].dma)) {
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.map_frag_err++;
+ u64_stats_update_end(&txq->txq_stats.syncp);
return -EFAULT;
+ }
dma_info[0].len = skb_headlen(skb);
@@ -117,6 +121,9 @@ static int hinic3_tx_map_skb(struct net_device *netdev, struct sk_buff *skb,
skb_frag_size(frag),
DMA_TO_DEVICE);
if (dma_mapping_error(&pdev->dev, dma_info[idx].dma)) {
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.map_frag_err++;
+ u64_stats_update_end(&txq->txq_stats.syncp);
err = -EFAULT;
goto err_unmap_page;
}
@@ -260,6 +267,9 @@ static int hinic3_tx_csum(struct hinic3_txq *txq, struct hinic3_sq_task *task,
if (l4_proto != IPPROTO_UDP ||
((struct udphdr *)skb_transport_header(skb))->dest !=
VXLAN_OFFLOAD_PORT_LE) {
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.unknown_tunnel_pkt++;
+ u64_stats_update_end(&txq->txq_stats.syncp);
/* Unsupported tunnel packet, disable csum offload */
skb_checksum_help(skb);
return 0;
@@ -433,6 +443,27 @@ static u32 hinic3_tx_offload(struct sk_buff *skb, struct hinic3_sq_task *task,
return offload;
}
+static void hinic3_get_pkt_stats(struct hinic3_txq *txq, struct sk_buff *skb)
+{
+ u32 hdr_len, tx_bytes;
+ unsigned short pkts;
+
+ if (skb_is_gso(skb)) {
+ hdr_len = (skb_shinfo(skb)->gso_segs - 1) *
+ skb_tcp_all_headers(skb);
+ tx_bytes = skb->len + hdr_len;
+ pkts = skb_shinfo(skb)->gso_segs;
+ } else {
+ tx_bytes = skb->len > ETH_ZLEN ? skb->len : ETH_ZLEN;
+ pkts = 1;
+ }
+
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.bytes += tx_bytes;
+ txq->txq_stats.packets += pkts;
+ u64_stats_update_end(&txq->txq_stats.syncp);
+}
+
static u16 hinic3_get_and_update_sq_owner(struct hinic3_io_queue *sq,
u16 curr_pi, u16 wqebb_cnt)
{
@@ -539,8 +570,12 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb,
int err;
if (unlikely(skb->len < MIN_SKB_LEN)) {
- if (skb_pad(skb, MIN_SKB_LEN - skb->len))
+ if (skb_pad(skb, MIN_SKB_LEN - skb->len)) {
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.skb_pad_err++;
+ u64_stats_update_end(&txq->txq_stats.syncp);
goto err_out;
+ }
skb->len = MIN_SKB_LEN;
}
@@ -595,6 +630,7 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb,
txq->tx_stop_thrs,
txq->tx_start_thrs);
+ hinic3_get_pkt_stats(txq, skb);
hinic3_prepare_sq_ctrl(&wqe_combo, queue_info, num_sge, owner);
hinic3_write_db(txq->sq, 0, DB_CFLAG_DP_SQ,
hinic3_get_sq_local_pi(txq->sq));
@@ -604,6 +640,10 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb,
err_drop_pkt:
dev_kfree_skb_any(skb);
err_out:
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.dropped++;
+ u64_stats_update_end(&txq->txq_stats.syncp);
+
return NETDEV_TX_OK;
}
@@ -754,6 +794,24 @@ int hinic3_configure_txqs(struct net_device *netdev, u16 num_sq,
return 0;
}
+void hinic3_txq_get_stats(struct hinic3_txq *txq,
+ struct hinic3_txq_stats *stats)
+{
+ struct hinic3_txq_stats *txq_stats = &txq->txq_stats;
+ unsigned int start;
+
+ do {
+ start = u64_stats_fetch_begin(&txq_stats->syncp);
+ stats->busy = txq_stats->busy;
+ stats->skb_pad_err = txq_stats->skb_pad_err;
+ stats->frag_len_overflow = txq_stats->frag_len_overflow;
+ stats->offload_cow_skb_err = txq_stats->offload_cow_skb_err;
+ stats->map_frag_err = txq_stats->map_frag_err;
+ stats->unknown_tunnel_pkt = txq_stats->unknown_tunnel_pkt;
+ stats->frag_size_err = txq_stats->frag_size_err;
+ } while (u64_stats_fetch_retry(&txq_stats->syncp, start));
+}
+
bool hinic3_tx_poll(struct hinic3_txq *txq, int budget)
{
struct net_device *netdev = txq->netdev;
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.h b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.h
index 00194f2a1bcc..0a21c423618f 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.h
@@ -157,6 +157,8 @@ int hinic3_configure_txqs(struct net_device *netdev, u16 num_sq,
u32 sq_depth, struct hinic3_dyna_txq_res *txqs_res);
netdev_tx_t hinic3_xmit_frame(struct sk_buff *skb, struct net_device *netdev);
+void hinic3_txq_get_stats(struct hinic3_txq *txq,
+ struct hinic3_txq_stats *stats);
bool hinic3_tx_poll(struct hinic3_txq *txq, int budget);
void hinic3_flush_txqs(struct net_device *netdev);
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v08 1/5] hinic3: Add ethtool queue ops
From: Fan Gong @ 2026-06-08 12:36 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1780907605.git.wudi234@huawei.com>
Implement following ethtool callback function:
.get_ringparam
.set_ringparam
These callbacks allow users to utilize ethtool for detailed
queue depth configuration and monitoring.
Co-developed-by: Wu Di <wudi234@huawei.com>
Signed-off-by: Wu Di <wudi234@huawei.com>
Co-developed-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
.../ethernet/huawei/hinic3/hinic3_ethtool.c | 100 +++++++++++++++++
.../net/ethernet/huawei/hinic3/hinic3_irq.c | 5 +-
.../net/ethernet/huawei/hinic3/hinic3_main.c | 6 +
.../huawei/hinic3/hinic3_netdev_ops.c | 104 ++++++++++++++++--
.../ethernet/huawei/hinic3/hinic3_nic_dev.h | 9 ++
.../ethernet/huawei/hinic3/hinic3_nic_io.c | 4 +-
.../ethernet/huawei/hinic3/hinic3_nic_io.h | 8 +-
.../net/ethernet/huawei/hinic3/hinic3_rx.c | 2 +-
8 files changed, 224 insertions(+), 14 deletions(-)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
index 90fc16288de9..c72db9da7701 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
@@ -9,6 +9,7 @@
#include <linux/errno.h>
#include <linux/etherdevice.h>
#include <linux/netdevice.h>
+#include <linux/netlink.h>
#include <linux/ethtool.h>
#include "hinic3_lld.h"
@@ -409,6 +410,103 @@ hinic3_get_link_ksettings(struct net_device *netdev,
return 0;
}
+static void hinic3_get_ringparam(struct net_device *netdev,
+ struct ethtool_ringparam *ring,
+ struct kernel_ethtool_ringparam *kernel_ring,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+
+ ring->rx_max_pending = HINIC3_MAX_RX_QUEUE_DEPTH;
+ ring->tx_max_pending = HINIC3_MAX_TX_QUEUE_DEPTH;
+ ring->rx_pending = nic_dev->q_params.rq_depth;
+ ring->rx_pending = nic_dev->q_params.sq_depth;
+}
+
+static void hinic3_update_qp_depth(struct net_device *netdev,
+ u32 sq_depth, u32 rq_depth)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ u16 i;
+
+ nic_dev->q_params.sq_depth = sq_depth;
+ nic_dev->q_params.rq_depth = rq_depth;
+ for (i = 0; i < nic_dev->max_qps; i++) {
+ nic_dev->txqs[i].q_depth = sq_depth;
+ nic_dev->txqs[i].q_mask = sq_depth - 1;
+ nic_dev->rxqs[i].q_depth = rq_depth;
+ nic_dev->rxqs[i].q_mask = rq_depth - 1;
+ }
+}
+
+static int hinic3_check_ringparam_valid(struct net_device *netdev,
+ const struct ethtool_ringparam *ring,
+ struct netlink_ext_ack *extack)
+{
+ if (ring->tx_pending < HINIC3_MIN_QUEUE_DEPTH ||
+ ring->rx_pending < HINIC3_MIN_QUEUE_DEPTH) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "Queue depth out of range tx[%d-%d] rx[%d-%d]",
+ HINIC3_MIN_QUEUE_DEPTH,
+ HINIC3_MAX_TX_QUEUE_DEPTH,
+ HINIC3_MIN_QUEUE_DEPTH,
+ HINIC3_MAX_RX_QUEUE_DEPTH);
+
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hinic3_set_ringparam(struct net_device *netdev,
+ struct ethtool_ringparam *ring,
+ struct kernel_ethtool_ringparam *kernel_ring,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_dyna_txrxq_params q_params = {};
+ u32 new_sq_depth, new_rq_depth;
+ int err;
+
+ err = hinic3_check_ringparam_valid(netdev, ring, extack);
+ if (err)
+ return err;
+
+ new_sq_depth = 1U << ilog2(ring->tx_pending);
+ new_rq_depth = 1U << ilog2(ring->rx_pending);
+ if (new_sq_depth == nic_dev->q_params.sq_depth &&
+ new_rq_depth == nic_dev->q_params.rq_depth)
+ return 0;
+
+ if (new_sq_depth != ring->tx_pending ||
+ new_rq_depth != ring->rx_pending)
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "Requested Tx/Rx ring depth %u/%u trimmed to %u/%u",
+ ring->tx_pending, ring->rx_pending,
+ new_sq_depth, new_rq_depth);
+
+ netdev_info(netdev, "Change Tx/Rx ring depth from %u/%u to %u/%u\n",
+ nic_dev->q_params.sq_depth, nic_dev->q_params.rq_depth,
+ new_sq_depth, new_rq_depth);
+
+ if (!netif_running(netdev)) {
+ hinic3_update_qp_depth(netdev, new_sq_depth, new_rq_depth);
+ } else {
+ q_params = nic_dev->q_params;
+ q_params.sq_depth = new_sq_depth;
+ q_params.rq_depth = new_rq_depth;
+
+ err = hinic3_change_channel_settings(netdev, &q_params);
+ if (err) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Failed to change channel settings");
+ return err;
+ }
+ }
+
+ return 0;
+}
+
static const struct ethtool_ops hinic3_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_PKT_RATE_RX_USECS,
@@ -417,6 +515,8 @@ static const struct ethtool_ops hinic3_ethtool_ops = {
.get_msglevel = hinic3_get_msglevel,
.set_msglevel = hinic3_set_msglevel,
.get_link = ethtool_op_get_link,
+ .get_ringparam = hinic3_get_ringparam,
+ .set_ringparam = hinic3_set_ringparam,
};
void hinic3_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
index e7d6c2033b45..bc4d879f9be4 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
@@ -137,7 +137,8 @@ static int hinic3_set_interrupt_moder(struct net_device *netdev, u16 q_id,
struct hinic3_interrupt_info info = {};
int err;
- if (q_id >= nic_dev->q_params.num_qps)
+ if (q_id >= nic_dev->q_params.num_qps ||
+ !mutex_trylock(&nic_dev->change_res_mutex))
return 0;
info.interrupt_coalesc_set = 1;
@@ -156,6 +157,8 @@ static int hinic3_set_interrupt_moder(struct net_device *netdev, u16 q_id,
nic_dev->rxqs[q_id].last_pending_limit = pending_limit;
}
+ mutex_unlock(&nic_dev->change_res_mutex);
+
return err;
}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_main.c b/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
index 0a888fe4c975..c87624a5e5dc 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
@@ -179,6 +179,7 @@ static int hinic3_sw_init(struct net_device *netdev)
int err;
mutex_init(&nic_dev->port_state_mutex);
+ mutex_init(&nic_dev->change_res_mutex);
nic_dev->q_params.sq_depth = HINIC3_SQ_DEPTH;
nic_dev->q_params.rq_depth = HINIC3_RQ_DEPTH;
@@ -315,6 +316,9 @@ static void hinic3_link_status_change(struct net_device *netdev,
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ if (!mutex_trylock(&nic_dev->change_res_mutex))
+ return;
+
if (link_status_up) {
if (netif_carrier_ok(netdev))
return;
@@ -330,6 +334,8 @@ static void hinic3_link_status_change(struct net_device *netdev,
netif_carrier_off(netdev);
netdev_dbg(netdev, "Link is down\n");
}
+
+ mutex_unlock(&nic_dev->change_res_mutex);
}
static void hinic3_port_module_event_handler(struct net_device *netdev,
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c b/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
index da73811641a9..047214cfc753 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
@@ -288,7 +288,8 @@ static void hinic3_free_channel_resources(struct net_device *netdev,
hinic3_free_qps(nic_dev, qp_params);
}
-static int hinic3_open_channel(struct net_device *netdev)
+static int hinic3_prepare_channel(struct net_device *netdev,
+ struct hinic3_dyna_txrxq_params *qp_params)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
int err;
@@ -299,16 +300,28 @@ static int hinic3_open_channel(struct net_device *netdev)
return err;
}
- err = hinic3_configure_txrxqs(netdev, &nic_dev->q_params);
+ err = hinic3_configure_txrxqs(netdev, qp_params);
if (err) {
netdev_err(netdev, "Failed to configure txrxqs\n");
goto err_free_qp_ctxts;
}
+ return 0;
+
+err_free_qp_ctxts:
+ hinic3_free_qp_ctxts(nic_dev);
+
+ return err;
+}
+
+static int hinic3_open_channel(struct net_device *netdev)
+{
+ int err;
+
err = hinic3_qps_irq_init(netdev);
if (err) {
netdev_err(netdev, "Failed to init txrxq irq\n");
- goto err_free_qp_ctxts;
+ return err;
}
err = hinic3_configure(netdev);
@@ -321,8 +334,6 @@ static int hinic3_open_channel(struct net_device *netdev)
err_uninit_qps_irq:
hinic3_qps_irq_uninit(netdev);
-err_free_qp_ctxts:
- hinic3_free_qp_ctxts(nic_dev);
return err;
}
@@ -428,6 +439,74 @@ static void hinic3_vport_down(struct net_device *netdev)
}
}
+int
+hinic3_change_channel_settings(struct net_device *netdev,
+ struct hinic3_dyna_txrxq_params *trxq_params)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_dyna_txrxq_params cur_trxq_params = {};
+ struct hinic3_dyna_qp_params new_qp_params = {};
+ struct hinic3_dyna_qp_params cur_qp_params = {};
+ int err;
+
+ cur_trxq_params = nic_dev->q_params;
+
+ hinic3_config_num_qps(netdev, trxq_params);
+
+ err = hinic3_alloc_channel_resources(netdev, &new_qp_params,
+ trxq_params);
+ if (err) {
+ netdev_err(netdev, "Failed to alloc channel resources\n");
+ return err;
+ }
+
+ mutex_lock(&nic_dev->change_res_mutex);
+ hinic3_vport_down(netdev);
+ hinic3_close_channel(netdev);
+ hinic3_get_cur_qps(nic_dev, &cur_qp_params);
+
+ hinic3_init_qps(nic_dev, &new_qp_params);
+
+ err = hinic3_prepare_channel(netdev, trxq_params);
+ if (err)
+ goto err_uninit_qps;
+
+ if (nic_dev->num_qp_irq > trxq_params->num_qps)
+ hinic3_qp_irq_change(netdev, trxq_params->num_qps);
+
+ nic_dev->q_params = *trxq_params;
+
+ err = hinic3_open_channel(netdev);
+ if (err)
+ goto err_qp_irq_reset;
+
+ err = hinic3_vport_up(netdev);
+ if (err)
+ goto err_close_channel;
+
+ hinic3_free_channel_resources(netdev, &cur_qp_params, &cur_trxq_params);
+
+ mutex_unlock(&nic_dev->change_res_mutex);
+
+ return 0;
+
+err_close_channel:
+ hinic3_close_channel(netdev);
+err_qp_irq_reset:
+ nic_dev->q_params = cur_trxq_params;
+
+ if (trxq_params->num_qps > cur_trxq_params.num_qps)
+ hinic3_qp_irq_change(netdev, cur_trxq_params.num_qps);
+ hinic3_free_qp_ctxts(nic_dev);
+err_uninit_qps:
+ hinic3_get_cur_qps(nic_dev, &new_qp_params);
+ hinic3_free_channel_resources(netdev, &new_qp_params, trxq_params);
+ hinic3_free_channel_resources(netdev, &cur_qp_params, &cur_trxq_params);
+ mutex_unlock(&nic_dev->change_res_mutex);
+
+ return err;
+}
+
static int hinic3_open(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
@@ -458,6 +537,10 @@ static int hinic3_open(struct net_device *netdev)
hinic3_init_qps(nic_dev, &qp_params);
+ err = hinic3_prepare_channel(netdev, &nic_dev->q_params);
+ if (err)
+ goto err_uninit_qps;
+
err = hinic3_open_channel(netdev);
if (err)
goto err_uninit_qps;
@@ -473,7 +556,7 @@ static int hinic3_open(struct net_device *netdev)
err_close_channel:
hinic3_close_channel(netdev);
err_uninit_qps:
- hinic3_uninit_qps(nic_dev, &qp_params);
+ hinic3_get_cur_qps(nic_dev, &qp_params);
hinic3_free_channel_resources(netdev, &qp_params, &nic_dev->q_params);
err_destroy_num_qps:
hinic3_destroy_num_qps(netdev);
@@ -493,10 +576,15 @@ static int hinic3_close(struct net_device *netdev)
return 0;
}
+ mutex_lock(&nic_dev->change_res_mutex);
hinic3_vport_down(netdev);
hinic3_close_channel(netdev);
- hinic3_uninit_qps(nic_dev, &qp_params);
- hinic3_free_channel_resources(netdev, &qp_params, &nic_dev->q_params);
+ hinic3_get_cur_qps(nic_dev, &qp_params);
+ hinic3_free_channel_resources(netdev, &qp_params,
+ &nic_dev->q_params);
+ hinic3_free_nicio_res(nic_dev);
+ hinic3_destroy_num_qps(netdev);
+ mutex_unlock(&nic_dev->change_res_mutex);
return 0;
}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h
index 9502293ff710..005b2c01a988 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h
@@ -10,6 +10,9 @@
#include "hinic3_hw_cfg.h"
#include "hinic3_hwdev.h"
#include "hinic3_mgmt_interface.h"
+#include "hinic3_nic_io.h"
+#include "hinic3_tx.h"
+#include "hinic3_rx.h"
#define HINIC3_VLAN_BITMAP_BYTE_SIZE(nic_dev) (sizeof(*(nic_dev)->vlan_bitmap))
#define HINIC3_VLAN_BITMAP_SIZE(nic_dev) \
@@ -129,6 +132,8 @@ struct hinic3_nic_dev {
struct work_struct rx_mode_work;
/* lock for enable/disable port */
struct mutex port_state_mutex;
+ /* mutex to serialize channel/resource changes */
+ struct mutex change_res_mutex;
struct list_head uc_filter_list;
struct list_head mc_filter_list;
@@ -143,6 +148,10 @@ struct hinic3_nic_dev {
void hinic3_set_netdev_ops(struct net_device *netdev);
int hinic3_set_hw_features(struct net_device *netdev);
+int
+hinic3_change_channel_settings(struct net_device *netdev,
+ struct hinic3_dyna_txrxq_params *trxq_params);
+
int hinic3_qps_irq_init(struct net_device *netdev);
void hinic3_qps_irq_uninit(struct net_device *netdev);
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.c b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.c
index 87e736adba02..0e7a0ccfba98 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.c
@@ -484,8 +484,8 @@ void hinic3_init_qps(struct hinic3_nic_dev *nic_dev,
}
}
-void hinic3_uninit_qps(struct hinic3_nic_dev *nic_dev,
- struct hinic3_dyna_qp_params *qp_params)
+void hinic3_get_cur_qps(struct hinic3_nic_dev *nic_dev,
+ struct hinic3_dyna_qp_params *qp_params)
{
struct hinic3_nic_io *nic_io = nic_dev->nic_io;
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h
index 12eefabcf1db..571b34d63950 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h
@@ -14,6 +14,10 @@ struct hinic3_nic_dev;
#define HINIC3_RQ_WQEBB_SHIFT 3
#define HINIC3_SQ_WQEBB_SIZE BIT(HINIC3_SQ_WQEBB_SHIFT)
+#define HINIC3_MAX_TX_QUEUE_DEPTH 65536
+#define HINIC3_MAX_RX_QUEUE_DEPTH 16384
+#define HINIC3_MIN_QUEUE_DEPTH 128
+
/* ******************** RQ_CTRL ******************** */
enum hinic3_rq_wqe_type {
HINIC3_NORMAL_RQ_WQE = 1,
@@ -136,8 +140,8 @@ void hinic3_free_qps(struct hinic3_nic_dev *nic_dev,
struct hinic3_dyna_qp_params *qp_params);
void hinic3_init_qps(struct hinic3_nic_dev *nic_dev,
struct hinic3_dyna_qp_params *qp_params);
-void hinic3_uninit_qps(struct hinic3_nic_dev *nic_dev,
- struct hinic3_dyna_qp_params *qp_params);
+void hinic3_get_cur_qps(struct hinic3_nic_dev *nic_dev,
+ struct hinic3_dyna_qp_params *qp_params);
int hinic3_init_qp_ctxts(struct hinic3_nic_dev *nic_dev);
void hinic3_free_qp_ctxts(struct hinic3_nic_dev *nic_dev);
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
index 309ab5901379..b5b601469517 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
@@ -541,7 +541,7 @@ int hinic3_configure_rxqs(struct net_device *netdev, u16 num_rq,
rq_associate_cqes(rxq);
pkts = hinic3_rx_fill_buffers(rxq);
- if (!pkts) {
+ if (pkts < rxq->q_depth - 1) {
netdev_err(netdev, "Failed to fill Rx buffer\n");
return -ENOMEM;
}
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v08 0/5] net: hinic3: PF initialization
From: Fan Gong @ 2026-06-08 12:36 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
This is [3/3] part of hinic3 Ethernet driver second submission.
With this patch hinic3 becomes a complete Ethernet driver with
pf and vf.
Add 20 ethtool ops for information of queue, rss, coalesce and eth data.
Add MTU size validation
Config netdev watchdog timeout.
Remove unneed coalesce parameters.
Changes:
PATCH 03 V01: https://lore.kernel.org/netdev/cover.1773387649.git.zhuyikai1@h-partners.com/
* Add rmon/pause/phy/mac/ctrl stats (Ioana Ciornei)
PATCH 03 V02: https://lore.kernel.org/netdev/cover.1774684571.git.zhuyikai1@h-partners.com/
* Modify "return -EINVAL" intension problem (AI review)
* Use le16_to_cpu for rss_indir pair.out->buf (AI review)
* Use u32 instead of int in coalesce_limits to avoid overflow (AI review)
* Remove redundant u64_stats_update_begin/end when reading stats without
concurrent reader (AI review)
* Modify nic_dev->stats.syncp logic (AI review)
* Complete rxq/txq stats stats fileds in hinic3_rx/txq_get_stats (AI review)
* Remove statistics values in rtnl_link_stats64 from ethtool statistics
values (AI review)
* Add channel_cfg_lock & channel_res_lock to protect resources access (AI review)
* Remove OutOfRangeLengthField, FrameToolong and InRangeLengthErrors (Ioana Ciornei)
* Remove redundant mtu commit (Maxime Chevialler)
PATCH 03 V03: https://lore.kernel.org/netdev/cover.1774940117.git.zhuyikai1@h-partners.com/
* Change unnedd to unneeded (AI review)
* Remove packets,bytes,errors and dropped in hinic3_rx/tx_queue_stats (AI review)
* Remove duplicated entried in hinic3_port_stats[] (AI review)
* change stats_info.head.status to ps->head.status (AI review)
PATCH 03 V04: https://lore.kernel.org/netdev/cover.1775618797.git.zhuyikai1@h-partners.com/
* Remove restore_drop_sge in hinic3_rx_queue_stats (AI review)
* Remove hinic3_nic_stats (AI review)
* Use old_q_param to store old config and use it in error handling (Mohsin Bashir)
* Add netdev_info to inform the user that depth is trimmed (Mohsin Bashir)
* Remove const in hinic3_get_qp_stats_strings parameters (Mohsin Bashir)
* Change EOPNOTSUPP to ERANGE in is_coalesce_exceed_limit (Mohsin Bashir)
* Update nic_dev->rss_type after hinic3_set_rss_type (Mohsin Bashir)
* Modify MGMT_STATUS_CMD_UNSUPPORTED to EOPNOTSUPP for complying with the
error code specifications (Mohsin Bashir)
PATCH 03 V05: https://lore.kernel.org/netdev/cover.1775711066.git.zhuyikai1@h-partners.com/
* Clear HINIC3_CHANGE_RES_INVALID bit in error handling (AI review)
* Use low >= high to avoid low=high in is_coalesce_legal (AI review)
* As tx and rx share interrupts, we only use ETHTOOL_COALESCE_RX_USECS for
user setting to avoid user misunderstanding. So we do not add
ETHTOOL_COALESCE_TX_USECS. (Mohsin Bashir & AI review)
PATCH 03 V06: https://lore.kernel.org/netdev/cover.1779867397.git.zhuyikai1@h-partners.com/
* Remove redundant rx_jumbo_pending and rx_mini_pending judgement (Jakub Kicinski)
* Remove redundant max tx_pending judgement when .get_ringparam already got the
max value (Jakub Kicinski)
* Use extack instead of netdev_err/netdev_info/netdev_warning (Jakub Kicinski)
* Remove HINIC3_CHANNEL_RES_VALID and only use HINIC3_CHANGE_RES_INVALID
bit (Jakub Kicinski)
* Deference freed pointers in hinic3_change_channel_settings error
handling (Jakub Kicinski)
* Modify hinic3_open_channel (Jakub Kicinski)
PATCH 03 V07: https://lore.kernel.org/netdev/cover.1779940072.git.zhuyikai1@h-partners.com/
* Remove the trailing '\n' in NL_SET_ERR_MSG* (Jakub Kicinski)
PATCH 03 V08:
* Update patch commit to be more accurate for change information (AI review)
* Modify <net/devlink.h> to <linux/netlink.h> (AI review)
* Use sq/rq_depth instead of rx/txqs[0].q_depth (AI review)
* hinic3_change_channel_settings() runs under change_res_mutex
to protect dynamic channel updates (AI review)
* hinic3_close() serializes close and channel reconfiguration paths (AI review)
* Remove useless u64_stats_init in hinic3_get_drv_queue_stats() (AI review)
* hinic3_get_drv_queue_stats() fills rxq stats at correct idx
when txq is null (AI review)
* Remove unnecessary semicolons after the closing bracket of
static inline functions (AI review)
* Disable preemption in hinic3_rx_fill_buffers() (AI review)
* Add spinlock to protect per-queue coalesce parameters from
concurrent access (AI review)
* Reject static coalesce config when adaptive RX coalesce is
enabled (AI review)
* Validate interface state and queue ID before modifying coalesce
parameters (AI review)
* Differentiate get_coalesce output for adaptive/static modes and
zero-fill the struct (AI review)
* Return -ERANGE for invalid coalesce frame limits and simplify
error message (AI review)
* Remove unused watchdog timeout definition and assignment (AI review)
* Return -EOPNOTSUPP when RSS type query is unsupported by firmware (AI review)
* Reject L4 hash bits for pure L3 flow types (AI review)
* Split RSS type conversion to correctly handle L3-only flows (AI review)
* Reprogram RSS indir table and HW parameters after channel count
changes (AI review)
* Propagate exact error code from channel parameter validation (AI review)
* Update SW RSS indir copy only after HW programming succeeds (AI review)
* Program RSS hash type to HW before updating SW state to prevent
inconsistency (AI review)
Fan Gong (5):
hinic3: Add ethtool queue ops
hinic3: Add ethtool statistic ops
hinic3: Add ethtool coalesce ops
hinic3: Add ethtool rss ops
hinic3: Remove unneeded coalesce parameters
.../ethernet/huawei/hinic3/hinic3_ethtool.c | 841 +++++++++++++++++-
.../ethernet/huawei/hinic3/hinic3_hw_intf.h | 11 +
.../net/ethernet/huawei/hinic3/hinic3_irq.c | 14 +-
.../net/ethernet/huawei/hinic3/hinic3_main.c | 7 +
.../huawei/hinic3/hinic3_mgmt_interface.h | 39 +
.../huawei/hinic3/hinic3_netdev_ops.c | 104 ++-
.../ethernet/huawei/hinic3/hinic3_nic_cfg.c | 64 ++
.../ethernet/huawei/hinic3/hinic3_nic_cfg.h | 109 +++
.../ethernet/huawei/hinic3/hinic3_nic_dev.h | 11 +
.../ethernet/huawei/hinic3/hinic3_nic_io.c | 4 +-
.../ethernet/huawei/hinic3/hinic3_nic_io.h | 8 +-
.../net/ethernet/huawei/hinic3/hinic3_rss.c | 539 ++++++++++-
.../net/ethernet/huawei/hinic3/hinic3_rss.h | 19 +
.../net/ethernet/huawei/hinic3/hinic3_rx.c | 72 +-
.../net/ethernet/huawei/hinic3/hinic3_rx.h | 18 +-
.../net/ethernet/huawei/hinic3/hinic3_tx.c | 62 +-
.../net/ethernet/huawei/hinic3/hinic3_tx.h | 2 +
17 files changed, 1890 insertions(+), 34 deletions(-)
base-commit: 903db046d5579bef0ea699eae4b279dd6455fc9f
--
2.43.0
^ permalink raw reply
* Re: [PATCH v2] hwmon: (asus-ec-sensors) add ROG MAXIMUS Z790 EXTREME
From: Guenter Roeck @ 2026-06-08 12:33 UTC (permalink / raw)
To: Eugene Shalygin
Cc: Brian Downey, Jonathan Corbet, Shuah Khan,
open list:HARDWARE MONITORING, open list:DOCUMENTATION, open list
In-Reply-To: <20260608060855.40469-1-eugene.shalygin@gmail.com>
On Mon, Jun 08, 2026 at 08:08:41AM +0200, Eugene Shalygin wrote:
> From: Brian Downey <bdowne01@gmail.com>
>
> Add support for ROG MAXIMUS Z790 EXTREME
>
> Signed-off-by: Brian Downey <bdowne01@gmail.com>
> Signed-off-by: Eugene Shalygin <eugene.shalygin@gmail.com>
Applied. Please provide change logs in the future.
Guenter
> ---
> Documentation/hwmon/asus_ec_sensors.rst | 1 +
> drivers/hwmon/asus-ec-sensors.c | 15 +++++++++++++++
> 2 files changed, 16 insertions(+)
>
> diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst
> index 9ad3f0a57f55..60f1a6036538 100644
> --- a/Documentation/hwmon/asus_ec_sensors.rst
> +++ b/Documentation/hwmon/asus_ec_sensors.rst
> @@ -29,6 +29,7 @@ Supported boards:
> * ROG MAXIMUS XI HERO
> * ROG MAXIMUS XI HERO (WI-FI)
> * ROG MAXIMUS Z690 FORMULA
> + * ROG MAXIMUS Z790 EXTREME
> * ROG STRIX B550-E GAMING
> * ROG STRIX B550-I GAMING
> * ROG STRIX B650E-I GAMING WIFI
> diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c
> index 070bb368f2b7..0e78750de34a 100644
> --- a/drivers/hwmon/asus-ec-sensors.c
> +++ b/drivers/hwmon/asus-ec-sensors.c
> @@ -399,6 +399,12 @@ static const struct ec_sensor_info sensors_family_intel_700[] = {
> [ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x33),
> [ec_sensor_fan_cpu_opt] =
> EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0),
> + [ec_sensor_fan_water_flow] =
> + EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbc),
> + [ec_sensor_temp_water_in] =
> + EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x00),
> + [ec_sensor_temp_water_out] =
> + EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x01),
> };
>
> /* Shortcuts for common combinations */
> @@ -509,6 +515,13 @@ static const struct ec_board_info board_info_maximus_z690_formula = {
> .family = family_intel_600_series,
> };
>
> +static const struct ec_board_info board_info_maximus_z790_extreme = {
> + .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
> + SENSOR_SET_TEMP_WATER | SENSOR_FAN_WATER_FLOW,
> + .mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX,
> + .family = family_intel_700_series,
> +};
> +
> static const struct ec_board_info board_info_prime_x470_pro = {
> .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB |
> SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM |
> @@ -857,6 +870,8 @@ static const struct dmi_system_id dmi_table[] = {
> &board_info_maximus_x_hero),
> DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z690 FORMULA",
> &board_info_maximus_z690_formula),
> + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z790 EXTREME",
> + &board_info_maximus_z790_extreme),
> DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING",
> &board_info_strix_b550_e_gaming),
> DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING",
^ permalink raw reply
* Re: [PATCH v3 1/2] docs/zh_CN: update DAMON usage Chinese translation
From: Dongliang Mu @ 2026-06-08 12:29 UTC (permalink / raw)
To: Doehyun Baek, Alex Shi, Yanteng Si, Hu Haowen
Cc: Jonathan Corbet, Shuah Khan, SeongJae Park, linux-doc,
linux-kernel, damon
In-Reply-To: <20260608065745.1587885-2-doehyunbaek@gmail.com>
On 6/8/26 2:57 PM, Doehyun Baek wrote:
> Update the translation of .../admin-guide/mm/damon/usage.rst into Chinese.
>
> Update the translation through commit d9cfe515d36e
> ("Docs/admin-guide/mm/damon/usage: document goal_tuner sysfs file")
>
> Signed-off-by: Doehyun Baek <doehyunbaek@gmail.com>
From my experience, you need to double check your patched translation
with the original English documentation. This will make sure your
translation matches the latest documenation.
Dongliang Mu
> ---
> .../zh_CN/admin-guide/mm/damon/usage.rst | 92 +++++++++++++------
> 1 file changed, 65 insertions(+), 27 deletions(-)
>
> diff --git a/Documentation/translations/zh_CN/admin-guide/mm/damon/usage.rst b/Documentation/translations/zh_CN/admin-guide/mm/damon/usage.rst
> index 9d7cb51be493..4a31210b948e 100644
> --- a/Documentation/translations/zh_CN/admin-guide/mm/damon/usage.rst
> +++ b/Documentation/translations/zh_CN/admin-guide/mm/damon/usage.rst
> @@ -15,6 +15,10 @@
>
> DAMON 为不同的用户提供了下面这些接口。
>
> +- *专用DAMON模块。*
> + :ref:`这 <damon_modules_special_purpose>` 是为构建、发布或管理带有专用DAMON用法的内
Please change to the reference in the Chinese translation. And double
check all the references.
> + 核的用户准备的。使用它,用户可以在构建、启动或运行时以简单的方式为给定目的使用DAMON的主要
> + 功能。
> - *DAMON用户空间工具。*
> `这 <https://github.com/damonitor/damo>`_ 为有这特权的人, 如系统管理员,希望有一个刚好
> 可以工作的人性化界面。
> @@ -55,30 +59,37 @@ DAMON sysfs接口的文件层次结构如下图所示。在下图中,父子关
>
|.. parsed-literal:: :ref:`/sys/kernel/mm/damon <sysfs_root>`/admin │
:ref:`kdamonds <sysfs_kdamonds>`/nr_kdamonds │ │ :ref:`0
<sysfs_kdamond>`/state,pid,refresh_ms │ │ │ :ref:`contexts
<sysfs_contexts>`/nr_contexts │ │ │ │ :ref:`0
<sysfs_context>`/avail_operations,operations,addr_unit │ │ │ │ │
:ref:`monitoring_attrs <sysfs_monitoring_attrs>`/ │ │ │ │ │ │
intervals/sample_us,aggr_us,update_us │ │ │ │ │ │ │
intervals_goal/access_bp,aggrs,min_sample_us,max_sample_us │ │ │ │ │ │
nr_regions/min,max │ │ │ │ │ :ref:`targets <sysfs_targets>`/nr_targets │
│ │ │ │ │ :ref:`0 <sysfs_target>`/pid_target,obsolete_target │ │ │ │ │ │
│ :ref:`regions <sysfs_regions>`/nr_regions │ │ │ │ │ │ │ │ :ref:`0
<sysfs_region>`/start,end │ │ │ │ │ │ │ │ ... │ │ │ │ │ │ ... │ │ │ │ │
:ref:`schemes <sysfs_schemes>`/nr_schemes │ │ │ │ │ │ :ref:`0
<sysfs_scheme>`/action,target_nid,apply_interval_us │ │ │ │ │ │ │
:ref:`access_pattern <sysfs_access_pattern>`/ │ │ │ │ │ │ │ │ sz/min,max
│ │ │ │ │ │ │ │ nr_accesses/min,max │ │ │ │ │ │ │ │ age/min,max │ │ │ │
│ │ │ :ref:`quotas
<sysfs_quotas>`/ms,bytes,reset_interval_ms,effective_bytes,goal_tuner │
│ │ │ │ │ │ │ weights/sz_permil,nr_accesses_permil,age_permil │ │ │ │ │
│ │ │ :ref:`goals <sysfs_schemes_quota_goals>`/nr_goals │ │ │ │ │ │ │ │
│ 0/target_metric,target_value,current_value,nid,path │ │ │ │ │ │ │
:ref:`watermarks <sysfs_watermarks>`/metric,interval_us,high,mid,low │ │
│ │ │ │ │ :ref:`{core_,ops_,}filters <sysfs_filters>`/nr_filters │ │ │ │
│ │ │ │
0/type,matching,allow,memcg_path,addr_start,addr_end,target_idx,min,max
│ │ │ │ │ │ │ :ref:`dests <damon_sysfs_dests>`/nr_dests │ │ │ │ │ │ │ │
0/id,weight │ │ │ │ │ │ │ :ref:`stats
<sysfs_schemes_stats>`/nr_tried,sz_tried,nr_applied,sz_applied,sz_ops_filter_passed,qt_exceeds,nr_snapshots,max_nr_snapshots
│ │ │ │ │ │ │ :ref:`tried_regions
<sysfs_schemes_tried_regions>`/total_bytes │ │ │ │ │ │ │ │
0/start,end,nr_accesses,age,sz_filter_passed │ │ │ │ │ │ │ │ ... │ │ │ │
│ │ ... │ │ │ │ ... │ │ ... |
This part changes a lot. Please take a look the original English
documentation.
> /sys/kernel/mm/damon/admin
> │ kdamonds/nr_kdamonds
> - │ │ 0/state,pid
> + │ │ 0/state,pid,refresh_ms
> │ │ │ contexts/nr_contexts
> - │ │ │ │ 0/operations
> + │ │ │ │ 0/avail_operations,operations,addr_unit
> │ │ │ │ │ monitoring_attrs/
> │ │ │ │ │ │ intervals/sample_us,aggr_us,update_us
> + │ │ │ │ │ │ │ intervals_goal/access_bp,aggrs,min_sample_us,max_sample_us
> │ │ │ │ │ │ nr_regions/min,max
> │ │ │ │ │ targets/nr_targets
> - │ │ │ │ │ │ 0/pid_target
> + │ │ │ │ │ │ 0/pid_target,obsolete_target
> │ │ │ │ │ │ │ regions/nr_regions
> │ │ │ │ │ │ │ │ 0/start,end
> │ │ │ │ │ │ │ │ ...
> │ │ │ │ │ │ ...
> │ │ │ │ │ schemes/nr_schemes
> - │ │ │ │ │ │ 0/action
> + │ │ │ │ │ │ 0/action,target_nid,apply_interval_us
> │ │ │ │ │ │ │ access_pattern/
> │ │ │ │ │ │ │ │ sz/min,max
> │ │ │ │ │ │ │ │ nr_accesses/min,max
> │ │ │ │ │ │ │ │ age/min,max
> - │ │ │ │ │ │ │ quotas/ms,bytes,reset_interval_ms
> + │ │ │ │ │ │ │ quotas/ms,bytes,reset_interval_ms,effective_bytes,goal_tuner
> │ │ │ │ │ │ │ │ weights/sz_permil,nr_accesses_permil,age_permil
> + │ │ │ │ │ │ │ │ goals/nr_goals
> + │ │ │ │ │ │ │ │ │ 0/target_metric,target_value,current_value,nid,path
> │ │ │ │ │ │ │ watermarks/metric,interval_us,high,mid,low
> - │ │ │ │ │ │ │ stats/nr_tried,sz_tried,nr_applied,sz_applied,qt_exceeds
> - │ │ │ │ │ │ │ tried_regions/
> - │ │ │ │ │ │ │ │ 0/start,end,nr_accesses,age
> + │ │ │ │ │ │ │ {core_,ops_,}filters/nr_filters
> + │ │ │ │ │ │ │ │ 0/type,matching,allow,memcg_path,addr_start,addr_end,target_idx,min,max
> + │ │ │ │ │ │ │ dests/nr_dests
> + │ │ │ │ │ │ │ │ 0/id,weight
> + │ │ │ │ │ │ │ stats/nr_tried,sz_tried,nr_applied,sz_applied,sz_ops_filter_passed,qt_exceeds,nr_snapshots,max_nr_snapshots
> + │ │ │ │ │ │ │ tried_regions/total_bytes
> + │ │ │ │ │ │ │ │ 0/start,end,nr_accesses,age,sz_filter_passed
> │ │ │ │ │ │ │ │ ...
> │ │ │ │ │ │ ...
> │ │ │ │ ...
> @@ -104,7 +115,8 @@ kdamonds/
> kdamonds/<N>/
> -------------
>
> -在每个kdamond目录中,存在两个文件(``state`` 和 ``pid`` )和一个目录( ``contexts`` )。
> +在每个kdamond目录中,存在三个文件(``state``、``pid`` 和 ``refresh_ms``)和一个目录
> +(``contexts``)。
>
> 读取 ``state`` 时,如果kdamond当前正在运行,则返回 ``on`` ,如果没有运行则返回 ``off`` 。
> 写入 ``on`` 或 ``off`` 使kdamond处于状态。向 ``state`` 文件写 ``update_schemes_stats`` ,
> @@ -117,6 +129,10 @@ kdamonds/<N>/
>
> 如果状态为 ``on``,读取 ``pid`` 显示kdamond线程的pid。
>
> +用户可以要求内核通过 ``refresh_ms`` 文件周期性地更新显示自动调优参数和DAMOS统计信息的文件。
> +向该文件写入希望的更新时间间隔(毫秒)。如果间隔为零,则禁用周期性更新。读取该文件会显示当前
> +设置的时间间隔。
> +
> ``contexts`` 目录包含控制这个kdamond要执行的监测上下文的文件。
>
> kdamonds/<N>/contexts/
> @@ -129,15 +145,19 @@ kdamonds/<N>/contexts/
> contexts/<N>/
> -------------
>
> -在每个上下文目录中,存在一个文件(``operations``)和三个目录(``monitoring_attrs``,
> -``targets``, 和 ``schemes``)。
> +在每个上下文目录中,存在三个文件(``avail_operations``、``operations`` 和
> +``addr_unit``)和三个目录(``monitoring_attrs``、``targets`` 和 ``schemes``)。
>
> -DAMON支持多种类型的监测操作,包括对虚拟地址空间和物理地址空间的监测。你可以通过向文件
> -中写入以下关键词之一,并从文件中读取,来设置和获取DAMON将为上下文使用何种类型的监测操作。
> +DAMON支持多种类型的监测操作,包括对虚拟地址空间和物理地址空间的监测。你可以通过读取
> +``avail_operations`` 文件获取可用的监测操作集列表。你可以通过向 ``operations`` 文件中写入
> +``avail_operations`` 文件列出的关键词之一,并从文件中读取,来设置和获取DAMON将为上下文使用
> +何种类型的监测操作。
>
> - vaddr: 监测特定进程的虚拟地址空间
> - paddr: 监视系统的物理地址空间
>
> +``addr_unit`` 文件用于设置和获取操作集的 :ref:`地址单位 <damon_design_addr_unit>` 参数。
> +
> contexts/<N>/monitoring_attrs/
> ------------------------------
>
> @@ -161,11 +181,15 @@ contexts/<N>/targets/
> targets/<N>/
> ------------
>
> -在每个目标目录中,存在一个文件(``pid_target``)和一个目录(``regions``)。
> +在每个目标目录中,存在两个文件(``pid_target`` 和 ``obsolete_target``)和一个目录
> +(``regions``)。
>
> 如果你把 ``vaddr`` 写到 ``contexts/<N>/operations`` 中,每个目标应该是一个进程。你
> 可以通过将进程的pid写到 ``pid_target`` 文件中来指定DAMON的进程。
>
> +用户可以向 ``obsolete_target`` 文件写入非零值并提交它(向 ``state`` 文件写入 ``commit``),
> +从目标数组中间选择性地删除目标。
This is the original English text.
Users can selectively remove targets in the middle of the targets array by
writing non-zero value to ``obsolete_target``file and committing it (writing
``commit``to ``state``file). DAMON will remove the matching targets from its
internal targets array. Users are responsible to construct target
directories
again, so that those correctly represent the changed internal targets array.
You missed many translations.
> +
> targets/<N>/regions
> -------------------
>
> @@ -203,8 +227,9 @@ contexts/<N>/schemes/
> schemes/<N>/
> ------------
>
> -在每个方案目录中,存在五个目录(``access_pattern``、``quotas``、``watermarks``、
> -``stats`` 和 ``tried_regions``)和一个文件(``action``)。
> +在每个方案目录中,存在九个目录(``access_pattern``、``quotas``、``watermarks``、
> +``core_filters``、``ops_filters``、``filters``、``dests``、``stats`` 和
> +``tried_regions``)和三个文件(``action``、``target_nid`` 和 ``apply_interval_us``)。
Here I found a typo in the English documenation. The directory should be
nine other than 8.
The correct order is to first submit a patch to fix this typo. Then this
translation is correct. And this patch should be merged after the typo
fix. @Jon, am I right?
>
> ``action`` 文件用于设置和获取你想应用于具有特定访问模式的内存区域的动作。可以写入文件
> 和从文件中读取的关键词及其含义如下。
> @@ -218,6 +243,9 @@ schemes/<N>/
> - ``lru_deprio``: 对区域的LRU列表进行降低优先处理。
> - ``stat``: 什么都不做,只计算统计数据
>
> +``target_nid`` 文件用于设置迁移目标节点,仅当 ``action`` 为 ``migrate_hot`` 或
> +``migrate_cold`` 时有意义。``apply_interval_us`` 文件用于以微秒为单位设置和获取方案的应用间隔。
> +
"``apply_interval_us`` 文件用于以微秒为单位设置和获取方案的应用间隔。" is in a separate paragraph.
> schemes/<N>/access_pattern/
> ---------------------------
>
> @@ -239,14 +267,20 @@ schemes/<N>/quotas/
> 当预计超过配额限制时,DAMON会根据 ``目标访问模式`` 的大小、访问频率和年龄,对找到的内存区域
> 进行优先排序。为了进行个性化的优先排序,用户可以为这三个属性设置权重。
>
> -在 ``quotas`` 目录下,存在三个文件(``ms``, ``bytes``, ``reset_interval_ms``)和一个
> -目录(``weights``),其中有三个文件(``sz_permil``, ``nr_accesses_permil``, 和
> -``age_permil``)。
> +在 ``quotas`` 目录下,存在五个文件(``ms``、``bytes``、``reset_interval_ms``、
> +``effective_bytes`` 和 ``goal_tuner``)和两个目录(``weights`` 和 ``goals``)。
>
> 你可以设置以毫秒为单位的 ``时间配额`` ,以字节为单位的 ``大小配额`` ,以及以毫秒为单位的 ``重
> 置间隔`` ,分别向这三个文件写入数值。你还可以通过向 ``weights`` 目录下的三个文件写入数值来设
> 置大小、访问频率和年龄的优先权,单位为千分之一。
>
> +你可以通过向 ``goal_tuner`` 文件写入算法名称,设置要使用的基于目标的有效配额自动调优算法。
> +读取该文件会返回当前选定的调优器算法。读取 ``effective_bytes`` 会返回当前有效大小配额。
> +
> +``goals`` 目录用于设置自动配额调优目标。每个目标目录包含 ``target_metric``、
> +``target_value``、``current_value``、``nid`` 和 ``path`` 文件。用户可以读写这些文件来设置
> +和获取配额自动调优目标的参数。
You can set the goal-based effective quota auto-tuning algorithm to use, by
writing the algorithm name to ``goal_tuner`` file. Reading the file returns
the currently selected tuner algorithm. Refer to the design documentation of
:ref:`automatic quota tuning goals <damon_design_damos_quotas_auto_tuning>` for
the background design of the feature and the name of the selectable algorithms.
Refer to :ref:`goals directory <sysfs_schemes_quota_goals>` for the goals
setup.
The above is the original English text. It seems your translation does
not match it.
> +
> schemes/<N>/watermarks/
> -----------------------
>
> @@ -271,16 +305,21 @@ schemes/<N>/stats/
> DAMON统计每个方案被尝试应用的区域的总数量和字节数,每个方案被成功应用的区域的两个数字,以及
> 超过配额限制的总数量。这些统计数据可用于在线分析或调整方案。
>
> -可以通过读取 ``stats`` 目录下的文件(``nr_tried``, ``sz_tried``, ``nr_applied``,
> -``sz_applied``, 和 ``qt_exceeds``))分别检索这些统计数据。这些文件不是实时更新的,所以
> -你应该要求DAMON sysfs接口通过在相关的 ``kdamonds/<N>/state`` 文件中写入一个特殊的关键字
> -``update_schemes_stats`` 来更新统计信息的文件内容。
> +可以通过读取 ``stats`` 目录下的文件(``nr_tried``、``sz_tried``、``nr_applied``、
> +``sz_applied``、``sz_ops_filter_passed``、``qt_exceeds``、``nr_snapshots`` 和
> +``max_nr_snapshots``)分别检索这些
> +统计数据。这些文件默认不是实时更新的。你应该要求DAMON sysfs接口通过 ``refresh_ms`` 周期性地
> +更新这些文件,或者通过在相关的 ``kdamonds/<N>/state`` 文件中写入一个特殊的关键字
> +``update_schemes_stats`` 来执行一次性更新。
>
> schemes/<N>/tried_regions/
> --------------------------
>
> +该目录开始时有一个文件 ``total_bytes``。
> +
> 当一个特殊的关键字 ``update_schemes_tried_regions`` 被写入相关的 ``kdamonds/<N>/state``
> -文件时,DAMON会在这个目录下创建从 ``0`` 开始命名的整数目录。每个目录包含的文件暴露了关于每个
> +文件时,DAMON会更新 ``total_bytes`` 文件,使读取该文件返回方案尝试区域的总大小,并在这个目录下
> +创建从 ``0`` 开始命名的整数目录。每个目录包含的文件暴露了关于每个
> 内存区域的详细信息,在下一个 :ref:`聚集区间 <sysfs_monitoring_attrs>`,相应的方案的 ``动作``
> 已经尝试在这个目录下应用。这些信息包括地址范围、``nr_accesses`` 以及区域的 ``年龄`` 。
>
> @@ -290,9 +329,8 @@ schemes/<N>/tried_regions/
> tried_regions/<N>/
> ------------------
>
> -在每个区域目录中,你会发现四个文件(``start``, ``end``, ``nr_accesses``, and ``age``)。
> -读取这些文件将显示相应的基于DAMON的操作方案 ``动作`` 试图应用的区域的开始和结束地址、``nr_accesses``
> -和 ``年龄`` 。
> +在每个区域目录中,你会发现五个文件(``start``、``end``、``nr_accesses``、``age`` 和
> +``sz_filter_passed``)。读取这些文件将显示相应的基于DAMON的操作方案 ``动作`` 试图应用的区域属性。
>
> 用例
> ~~~~
^ permalink raw reply
* Re: [PATCH v6 12/12] Documentation: PCI: Add documentation for Live Update
From: Pranjal Shrivastava @ 2026-06-08 12:01 UTC (permalink / raw)
To: David Matlack
Cc: kexec, linux-doc, linux-kernel, linux-mm, linux-pci,
Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pratyush Yadav, Saeed Mahameed, Samiullah Khawaja, Shuah Khan,
Vipin Sharma, William Tu, Yi Liu
In-Reply-To: <20260522202410.3104264-13-dmatlack@google.com>
On Fri, May 22, 2026 at 08:24:10PM +0000, David Matlack wrote:
> Add documentation files for the PCI subsystem's participation in Live
> Update.
>
> These documentation files are generated from the kernel-doc comments
> in the PCI Live Update source code. They describe the File-Lifecycle
> Bound (FLB) API, the device tracking API, and the specific policies
> applied to preserved devices (such as bus number inheritance and bus
> mastering preservation).
>
> Signed-off-by: David Matlack <dmatlack@google.com>
> ---
> Documentation/PCI/index.rst | 1 +
> Documentation/PCI/liveupdate.rst | 29 +++++++++++++++++++++++++++
> Documentation/core-api/liveupdate.rst | 1 +
> MAINTAINERS | 1 +
> 4 files changed, 32 insertions(+)
The kernel-doc directives seem to be wired up properly, along with
formatting/indentation while keeping MAINTAINERS in sync in the same go.
Reviewed-by: Pranjal Shrivastava <praan@google.com>
Thanks,
Praan
^ permalink raw reply
* Re: [PATCH v6 11/12] PCI: liveupdate: Do not disable bus mastering on preserved devices during kexec
From: Pranjal Shrivastava @ 2026-06-08 11:58 UTC (permalink / raw)
To: David Matlack
Cc: kexec, linux-doc, linux-kernel, linux-mm, linux-pci,
Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pratyush Yadav, Saeed Mahameed, Samiullah Khawaja, Shuah Khan,
Vipin Sharma, William Tu, Yi Liu
In-Reply-To: <20260522202410.3104264-12-dmatlack@google.com>
On Fri, May 22, 2026 at 08:24:09PM +0000, David Matlack wrote:
> Do not disable bus mastering on outgoing preserved devices during
> pci_device_shutdown() for kexec.
>
> Preserved devices must be allowed to perform memory transactions during
> a Live Update to ensure continuous operation. Clearing the bus
> mastering bit would prevent these devices from issuing any memory
> requests while the new kernel boots.
>
> Because bridges upstream of preserved endpoint devices are also
> automatically preserved, this change also avoids clearing bus mastering
> on them. This is critical because clearing bus mastering on an upstream
> bridge prevents the bridge from forwarding memory requests upstream (i.e.
> it would prevent the endpoint device from accessing system RAM and doing
> peer-to-peer transactions with devices not downstream of the bridge).
>
> Signed-off-by: David Matlack <dmatlack@google.com>
> ---
> drivers/pci/liveupdate.c | 11 +++++++++++
> drivers/pci/liveupdate.h | 6 ++++++
> drivers/pci/pci-driver.c | 7 +++++--
> 3 files changed, 22 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
> index d404e64a4e55..a6f2790bc1bf 100644
> --- a/drivers/pci/liveupdate.c
> +++ b/drivers/pci/liveupdate.c
> @@ -132,6 +132,10 @@
> * * The PCI core inherits ARI Forwarding Enable on all bridges with downstream
> * preserved devices to ensure that all preserved devices on the bridge's
> * secondary bus are addressable after the Live Update.
> + *
> + * * The PCI core does not disable bus mastering on outgoing preserved devices
> + * during kexec. This allows preserved devices to issue memory transactions
> + * throughout the Live Update.
> */
>
> #define pr_fmt(fmt) "PCI: liveupdate: " fmt
> @@ -790,6 +794,13 @@ int pci_liveupdate_configure_ari(struct pci_dev *dev)
> return 0;
> }
>
> +bool pci_liveupdate_is_outgoing(struct pci_dev *dev)
> +{
> + guard(rwsem_read)(&pci_liveupdate.rwsem);
> + pci_WARN_ONCE(dev, !dev->liveupdate.frozen, "Preservation status is unstable!\n");
> + return dev->liveupdate.outgoing;
> +}
> +
[...]
> diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
> index f7a5e65a7c75..0b1f8d01d7a5 100644
> --- a/drivers/pci/pci-driver.c
> +++ b/drivers/pci/pci-driver.c
> @@ -546,11 +546,14 @@ static void pci_device_shutdown(struct device *dev)
> /*
> * If this is a kexec reboot, turn off Bus Master bit on the
> * device to tell it to not continue to do DMA. Don't touch
> - * devices in D3cold or unknown states.
> + * devices being preserved for Live Update or in D3cold or
> + * unknown states.
> + *
> * If it is not a kexec reboot, firmware will hit the PCI
> * devices with big hammer and stop their DMA any way.
> */
> - if (kexec_in_progress && (pci_dev->current_state <= PCI_D3hot))
> + if (kexec_in_progress && !pci_liveupdate_is_outgoing(pci_dev) &&
> + pci_dev->current_state <= PCI_D3hot)
> pci_clear_master(pci_dev);
> }
Looks good.
Reviewed-by: Pranjal Shrivastava <praan@google.com>
Thanks,
Praan
^ permalink raw reply
* Re: [PATCH v6 10/12] PCI: liveupdate: Freeze preservation status during shutdown
From: Pranjal Shrivastava @ 2026-06-08 11:47 UTC (permalink / raw)
To: David Matlack
Cc: kexec, linux-doc, linux-kernel, linux-mm, linux-pci,
Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pratyush Yadav, Saeed Mahameed, Samiullah Khawaja, Shuah Khan,
Vipin Sharma, William Tu, Yi Liu
In-Reply-To: <20260522202410.3104264-11-dmatlack@google.com>
On Fri, May 22, 2026 at 08:24:08PM +0000, David Matlack wrote:
> Freeze a device's outgoing preservation status (preserved or not
> preserved) during shutdown. This enables the PCI core and drivers to
> safely make decisions based on the device's preservation status during
> shutdown.
>
> Note that pci_liveupdate_freeze() is triggered by the PCI core rather
> than from drivers participating in Live Update so that all devices can
> have their status frozen (i.e. prevent non-preserved devices from
> getting preserved late).
>
> Signed-off-by: David Matlack <dmatlack@google.com>
Reviewed-by: Pranjal Shrivastava <praan@google.com>
Thanks,
Praan
^ permalink raw reply
* Re: [PATCH] ata: pata_legacy: remove documentation for removed module parameters
From: Damien Le Moal @ 2026-06-08 11:43 UTC (permalink / raw)
To: Ethan Nelson-Moore, linux-doc; +Cc: stable, Jonathan Corbet, Shuah Khan
In-Reply-To: <20260607064053.195166-1-enelsonmoore@gmail.com>
On 2026/06/07 14:40, Ethan Nelson-Moore wrote:
> Commit 3c4d783f6922 ("ata: pata_legacy: remove VLB support") removed
> several module parameters from the pata_legacy driver, but neglected to
> remove their documentation. Remove it.
>
> Fixes: 3c4d783f6922 ("ata: pata_legacy: remove VLB support")
> Cc: stable@vger.kernel.org # 7.0+
> Signed-off-by: Ethan Nelson-Moore <enelsonmoore@gmail.com>
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
--
Damien Le Moal
Western Digital Research
^ permalink raw reply
* Re: [PATCH v6 09/12] PCI: liveupdate: Inherit ARI Forwarding Enable on preserved bridges
From: Pranjal Shrivastava @ 2026-06-08 11:33 UTC (permalink / raw)
To: David Matlack
Cc: kexec, linux-doc, linux-kernel, linux-mm, linux-pci,
Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pratyush Yadav, Saeed Mahameed, Samiullah Khawaja, Shuah Khan,
Vipin Sharma, William Tu, Yi Liu
In-Reply-To: <20260522202410.3104264-10-dmatlack@google.com>
On Fri, May 22, 2026 at 08:24:07PM +0000, David Matlack wrote:
> Inherit the ARI Forwarding Enable on preserved bridges and update
> pci_dev->ari_enabled accordingly during a Live Update. This ensures that
> the preserved devices on the bridge's secondary bus can be identified
> with the same expanded 8-bit function number after a Live Update.
>
> Signed-off-by: David Matlack <dmatlack@google.com>
> ---
> drivers/pci/liveupdate.c | 18 ++++++++++++++++++
> drivers/pci/liveupdate.h | 6 ++++++
> drivers/pci/pci.c | 8 +++++++-
> 3 files changed, 31 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
> index a93b7ef065f2..701276ef6cfb 100644
> --- a/drivers/pci/liveupdate.c
> +++ b/drivers/pci/liveupdate.c
> @@ -128,6 +128,10 @@
> * way after Live Update and ensures that IOMMU groups do not change. Note
> * that a device will use its inherited ACS flags for the lifetime of its
> * struct pci_dev (i.e. even after pci_liveupdate_finish()).
> + *
> + * * The PCI core inherits ARI Forwarding Enable on all bridges with downstream
> + * preserved devices to ensure that all preserved devices on the bridge's
> + * secondary bus are addressable after the Live Update.
> */
>
> #define pr_fmt(fmt) "PCI: liveupdate: " fmt
> @@ -756,6 +760,20 @@ int pci_liveupdate_enable_acs(struct pci_dev *dev)
> return 0;
> }
>
> +int pci_liveupdate_configure_ari(struct pci_dev *dev)
> +{
> + u16 val;
> +
> + guard(rwsem_read)(&pci_liveupdate.rwsem);
> +
> + if (!dev->liveupdate.incoming)
> + return -EINVAL;
> +
> + pcie_capability_read_word(dev, PCI_EXP_DEVCTL2, &val);
Again, I might be thinking out loud here, but since these are
hot-pluggable devices, with some FW / SW running on them, I'm a little
worried while assuming the HW registers can be trusted across a kexec.
Say, if the bridge experiences a reset (e.g. link drop etc) during the
kexec blackout, the PCI_EXP_DEVCTL2 register could revert to its default
state, meaning the ARI bit will be 0.
In that scenario, pci_liveupdate_configure_ari() will read 0, set
bridge->ari_enabled = false, and bypass the pci_configure_ari() logic
as well, permanently leaving ARI disabled on the bridge. Any preserved
downstream devices with function numbers > 7 will instantly become
unaddressable, breaking the Live Update. Even if that's desired it gets
hard to identify why the Liveupdate broke.
Should ari_enabled be serialized with bridge's pci_ser to ensure we
correctly restore / match it in case HW changed it during the kexec?
> + dev->ari_enabled = !!(val & PCI_EXP_DEVCTL2_ARI);
> + return 0;
> +}
> +
[...]
Thanks,
Praan
^ permalink raw reply
* Re: [PATCH v3] docs: pt_BR: update "Purpose of Defconfigs" section in maintainer-soc.rst
From: Daniel Pereira @ 2026-06-08 11:16 UTC (permalink / raw)
To: Jonathan Corbet; +Cc: Amanda Corrêa, linux-doc
In-Reply-To: <87v7byz58v.fsf@trenco.lwn.net>
Hi Amanda,
The grammar for Brazilian Portuguese is okay.
Thanks,
Acked-by: Daniel Pereira <danielmaraboo@gmail.com>
^ permalink raw reply
* Re: [PATCH v6 08/12] PCI: liveupdate: Inherit ACS flags in incoming preserved devices
From: Pranjal Shrivastava @ 2026-06-08 10:49 UTC (permalink / raw)
To: David Matlack
Cc: kexec, linux-doc, linux-kernel, linux-mm, linux-pci,
Adithya Jayachandran, Alexander Graf, Alex Williamson,
Bjorn Helgaas, Chris Li, David Rientjes, Jacob Pan,
Jason Gunthorpe, Jonathan Corbet, Josh Hilke, Leon Romanovsky,
Lukas Wunner, Mike Rapoport, Parav Pandit, Pasha Tatashin,
Pratyush Yadav, Saeed Mahameed, Samiullah Khawaja, Shuah Khan,
Vipin Sharma, William Tu, Yi Liu
In-Reply-To: <aiXWmR-ettxin4LC@google.com>
On Sun, Jun 07, 2026 at 08:37:45PM +0000, Pranjal Shrivastava wrote:
> On Fri, May 22, 2026 at 08:24:06PM +0000, David Matlack wrote:
> > Inherit Access Control Services (ACS) flags on all incoming preserved
> > devices (endpoints and upstream bridges) during a Live Update.
> >
> > Inheriting ACS flags avoids changing routing rules while memory
> > transactions are in flight from preserved devices. This is also strictly
> > necessary to ensure that IOMMU group assignments do not change across
> > a Live Update for preserved devices, as changing ACS configurations can
> > split or merge IOMMU groups.
> >
> > Cache the inherited ACS controls established by the previous kernel in
> > struct pci_dev so that ACS controls do not change after a reset
> > (pci_restore_state() calls pci_enable_acs()).
> >
> > To simplify ACS inheritance, reject preserving any devices that require
> > quirks to enable ACS as those quirks would also have to take Live Update
> > into account.
> >
> > Signed-off-by: David Matlack <dmatlack@google.com>
> > ---
> > drivers/pci/liveupdate.c | 68 ++++++++++++++++++++++++++++++++++
> > drivers/pci/liveupdate.h | 11 ++++++
> > drivers/pci/pci.c | 5 +++
> > drivers/pci/pci.h | 5 +++
> > drivers/pci/quirks.c | 7 ++++
> > include/linux/pci_liveupdate.h | 6 +++
> > 6 files changed, 102 insertions(+)
> >
>
> [...]
>
> >
> > +void pci_liveupdate_init_acs(struct pci_dev *dev)
> > +{
> > + guard(rwsem_read)(&pci_liveupdate.rwsem);
> > +
> > + if (!dev->acs_cap || !dev->liveupdate.incoming)
> > + return;
> > +
> > + pci_read_config_word(dev, dev->acs_cap + PCI_ACS_CTRL, &dev->liveupdate.acs_ctrl);
>
> I might be thinking out loud here, but as an attacker, this motivates me
> to somehow hack the EP FW to mis-report the PCI_ACS_CTRL register across
> a liveupdate to fool the incoming kernel. If the FW feeds a 0, it silently
> strips ACS protections.
Minor note here: I used "0" as an example value, I'm aware that'll
effectively disable ACS and kernel will enforce more security.
My point was that a FW exploit can meddle with the bitfields of the
ACS_CTRL to spoof and mis-report the ACS flags.
Additionally, we might give rise to use-cases that start depending on
this, for e.g. if someone wants to change ACS policies in the
new kernel, the FW may silently update these flags across a kexec.
>
> Should we also serialize ACS state in ser somehow to ensure we aren't
> fooled by something like this?
>
Thanks,
Praan
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox