Netdev List
 help / color / mirror / Atom feed
* [PATCH v5 3/6] firmware: rename fw_sysfs_fallback to firmware_fallback_sysfs()
From: Luis R. Rodriguez @ 2018-05-04 17:43 UTC (permalink / raw)
  To: gregkh
  Cc: akpm, keescook, josh, teg, wagi, hdegoede, andresx7, zohar,
	kubakici, shuah, mfuzzey, dhowells, pali.rohar, tiwai, kvalo,
	arend.vanspriel, zajec5, nbroeking, markivx, broonie,
	dmitry.torokhov, dwmw2, torvalds, Abhay_Salunke, bjorn.andersson,
	jewalt, oneukum, cantabile.desu, ast, hare, jejb, martin.petersen,
	khc, davem, maco, arve, tkjos, linux
In-Reply-To: <20180504174356.13227-1-mcgrof@kernel.org>

From: Andres Rodriguez <andresx7@gmail.com>

This is done since this call is now exposed through kernel-doc,
and since this also paves the way for different future types of
fallback mechanims.

Signed-off-by: Andres Rodriguez <andresx7@gmail.com>
Acked-by: Luis R. Rodriguez <mcgrof@kernel.org>
[mcgrof: small coding style changes]
Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 drivers/base/firmware_loader/fallback.c |  8 ++++----
 drivers/base/firmware_loader/fallback.h | 16 ++++++++--------
 drivers/base/firmware_loader/firmware.h |  2 +-
 drivers/base/firmware_loader/main.c     |  2 +-
 4 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/drivers/base/firmware_loader/fallback.c b/drivers/base/firmware_loader/fallback.c
index 529f7013616f..3db9e0f225ac 100644
--- a/drivers/base/firmware_loader/fallback.c
+++ b/drivers/base/firmware_loader/fallback.c
@@ -662,10 +662,10 @@ static bool fw_run_sysfs_fallback(enum fw_opt opt_flags)
 	return fw_force_sysfs_fallback(opt_flags);
 }
 
-int fw_sysfs_fallback(struct firmware *fw, const char *name,
-		      struct device *device,
-		      enum fw_opt opt_flags,
-		      int ret)
+int firmware_fallback_sysfs(struct firmware *fw, const char *name,
+			    struct device *device,
+			    enum fw_opt opt_flags,
+			    int ret)
 {
 	if (!fw_run_sysfs_fallback(opt_flags))
 		return ret;
diff --git a/drivers/base/firmware_loader/fallback.h b/drivers/base/firmware_loader/fallback.h
index a3b73a09db6c..21063503e4ea 100644
--- a/drivers/base/firmware_loader/fallback.h
+++ b/drivers/base/firmware_loader/fallback.h
@@ -31,10 +31,10 @@ struct firmware_fallback_config {
 };
 
 #ifdef CONFIG_FW_LOADER_USER_HELPER
-int fw_sysfs_fallback(struct firmware *fw, const char *name,
-		      struct device *device,
-		      enum fw_opt opt_flags,
-		      int ret);
+int firmware_fallback_sysfs(struct firmware *fw, const char *name,
+			    struct device *device,
+			    enum fw_opt opt_flags,
+			    int ret);
 void kill_pending_fw_fallback_reqs(bool only_kill_custom);
 
 void fw_fallback_set_cache_timeout(void);
@@ -43,10 +43,10 @@ void fw_fallback_set_default_timeout(void);
 int register_sysfs_loader(void);
 void unregister_sysfs_loader(void);
 #else /* CONFIG_FW_LOADER_USER_HELPER */
-static inline int fw_sysfs_fallback(struct firmware *fw, const char *name,
-				    struct device *device,
-				    enum fw_opt opt_flags,
-				    int ret)
+static inline int firmware_fallback_sysfs(struct firmware *fw, const char *name,
+					  struct device *device,
+					  enum fw_opt opt_flags,
+					  int ret)
 {
 	/* Keep carrying over the same error */
 	return ret;
diff --git a/drivers/base/firmware_loader/firmware.h b/drivers/base/firmware_loader/firmware.h
index 4f433b447367..4c1395f8e7ed 100644
--- a/drivers/base/firmware_loader/firmware.h
+++ b/drivers/base/firmware_loader/firmware.h
@@ -20,7 +20,7 @@
  * @FW_OPT_NOWAIT: Used to describe the firmware request is asynchronous.
  * @FW_OPT_USERHELPER: Enable the fallback mechanism, in case the direct
  *	filesystem lookup fails at finding the firmware.  For details refer to
- *	fw_sysfs_fallback().
+ *	firmware_fallback_sysfs().
  * @FW_OPT_NO_WARN: Quiet, avoid printing warning messages.
  * @FW_OPT_NOCACHE: Disables firmware caching. Firmware caching is used to
  *	cache the firmware upon suspend, so that upon resume races against the
diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c
index 7f2bc7e8e3c0..d951af29017a 100644
--- a/drivers/base/firmware_loader/main.c
+++ b/drivers/base/firmware_loader/main.c
@@ -581,7 +581,7 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
 			dev_warn(device,
 				 "Direct firmware load for %s failed with error %d\n",
 				 name, ret);
-		ret = fw_sysfs_fallback(fw, name, device, opt_flags, ret);
+		ret = firmware_fallback_sysfs(fw, name, device, opt_flags, ret);
 	} else
 		ret = assign_fw(fw, device, opt_flags);
 
-- 
2.17.0

^ permalink raw reply related

* [PATCH v5 4/6] firmware_loader: document firmware_sysfs_fallback()
From: Luis R. Rodriguez @ 2018-05-04 17:43 UTC (permalink / raw)
  To: gregkh
  Cc: akpm, keescook, josh, teg, wagi, hdegoede, andresx7, zohar,
	kubakici, shuah, mfuzzey, dhowells, pali.rohar, tiwai, kvalo,
	arend.vanspriel, zajec5, nbroeking, markivx, broonie,
	dmitry.torokhov, dwmw2, torvalds, Abhay_Salunke, bjorn.andersson,
	jewalt, oneukum, cantabile.desu, ast, hare, jejb, martin.petersen,
	khc, davem, maco, arve, tkjos, linux
In-Reply-To: <20180504174356.13227-1-mcgrof@kernel.org>

This also sets the expecations for future fallback interfaces, even
if they are not exported.

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 drivers/base/firmware_loader/fallback.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/drivers/base/firmware_loader/fallback.c b/drivers/base/firmware_loader/fallback.c
index 3db9e0f225ac..9169e7b9800c 100644
--- a/drivers/base/firmware_loader/fallback.c
+++ b/drivers/base/firmware_loader/fallback.c
@@ -662,6 +662,26 @@ static bool fw_run_sysfs_fallback(enum fw_opt opt_flags)
 	return fw_force_sysfs_fallback(opt_flags);
 }
 
+/**
+ * firmware_fallback_sysfs() - use the fallback mechanism to find firmware
+ * @fw: pointer to firmware image
+ * @name: name of firmware file to look for
+ * @device: device for which firmware is being loaded
+ * @opt_flags: options to control firmware loading behaviour
+ * @ret: return value from direct lookup which triggered the fallback mechanism
+ *
+ * This function is called if direct lookup for the firmware failed, it enables
+ * a fallback mechanism through userspace by exposing a sysfs loading
+ * interface. Userspace is in charge of loading the firmware through the syfs
+ * loading interface. This syfs fallback mechanism may be disabled completely
+ * on a system by setting the proc sysctl value ignore_sysfs_fallback to true.
+ * If this false we check if the internal API caller set the @FW_OPT_NOFALLBACK
+ * flag, if so it would also disable the fallback mechanism. A system may want
+ * to enfoce the sysfs fallback mechanism at all times, it can do this by
+ * setting ignore_sysfs_fallback to false and force_sysfs_fallback to true.
+ * Enabling force_sysfs_fallback is functionally equivalent to build a kernel
+ * with CONFIG_FW_LOADER_USER_HELPER_FALLBACK.
+ **/
 int firmware_fallback_sysfs(struct firmware *fw, const char *name,
 			    struct device *device,
 			    enum fw_opt opt_flags,
-- 
2.17.0

^ permalink raw reply related

* [PATCH v5 5/6] firmware_loader: enhance Kconfig documentation over FW_LOADER
From: Luis R. Rodriguez @ 2018-05-04 17:43 UTC (permalink / raw)
  To: gregkh
  Cc: akpm, keescook, josh, teg, wagi, hdegoede, andresx7, zohar,
	kubakici, shuah, mfuzzey, dhowells, pali.rohar, tiwai, kvalo,
	arend.vanspriel, zajec5, nbroeking, markivx, broonie,
	dmitry.torokhov, dwmw2, torvalds, Abhay_Salunke, bjorn.andersson,
	jewalt, oneukum, cantabile.desu, ast, hare, jejb, martin.petersen,
	khc, davem, maco, arve, tkjos, linux
In-Reply-To: <20180504174356.13227-1-mcgrof@kernel.org>

If you try to read FW_LOADER today it speaks of old riddles and
unless you have been following development closely you will loose
track of what is what. Even the documentation for PREVENT_FIRMWARE_BUILD
is a bit fuzzy and how it fits into this big picture.

Give the FW_LOADER kconfig documentation some love with more up to
date developments and recommendations. While at it, wrap the FW_LOADER
code into its own menu to compartamentalize and make it clearer which
components really are part of the FW_LOADER. This should also make
it easier to later move these kconfig entries into the firmware_loader/
directory later.

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 drivers/base/Kconfig | 160 ++++++++++++++++++++++++++++++++++---------
 1 file changed, 126 insertions(+), 34 deletions(-)

diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index 29b0eb452b3a..bf2d464b0e2c 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -70,39 +70,64 @@ config STANDALONE
 	  If unsure, say Y.
 
 config PREVENT_FIRMWARE_BUILD
-	bool "Prevent firmware from being built"
+	bool "Disable drivers features which enable custom firmware building"
 	default y
 	help
-	  Say yes to avoid building firmware. Firmware is usually shipped
-	  with the driver and only when updating the firmware should a
-	  rebuild be made.
-	  If unsure, say Y here.
+	  Say yes to disable driver features which enable building a custom
+	  driver firmwar at kernel build time. These drivers do not use the
+	  kernel firmware API to load firmware (CONFIG_FW_LOADER), instead they
+	  use their own custom loading mechanism. The required firmware is
+	  usually shipped with the driver, building the driver firmware
+	  should only be needed if you have an updated firmware source.
+
+	  Firmware should not be being built as part of kernel, these days
+	  you should always prevent this and say Y here. There are only two
+	  old drivers which enable building of its firmware at kernel build
+	  time:
+
+	    o CONFIG_WANXL through CONFIG_WANXL_BUILD_FIRMWARE
+	    o CONFIG_SCSI_AIC79XX through CONFIG_AIC79XX_BUILD_FIRMWARE
+
+menu "Firmware loader"
 
 config FW_LOADER
-	tristate "Userspace firmware loading support" if EXPERT
+	tristate "Firmware loading facility" if EXPERT
 	default y
 	---help---
-	  This option is provided for the case where none of the in-tree modules
-	  require userspace firmware loading support, but a module built
-	  out-of-tree does.
+	  This enables the firmware loading facility in the kernel. The kernel
+	  will first look for built-in firmware, if it has any. Next, it will
+	  look for the requested firmware in a series of filesystem paths:
+
+		o firmware_class path module parameter or kernel boot param
+		o /lib/firmware/updates/UTS_RELEASE
+		o /lib/firmware/updates
+		o /lib/firmware/UTS_RELEASE
+		o /lib/firmware
+
+	  Enabling this feature only increases your kernel image by about
+	  828 bytes, enable this option unless you are certain you don't
+	  need firmware.
+
+	  You typically want this built-in (=y) but you can also enable this
+	  as a module, in which case the firmware_class module will be built.
+	  You also want to be sure to enable this built-in if you are going to
+	  enable built-in firmware (CONFIG_EXTRA_FIRMWARE).
+
+if FW_LOADER
 
 config EXTRA_FIRMWARE
-	string "External firmware blobs to build into the kernel binary"
-	depends on FW_LOADER
+	string "Build these firmware blobs into the kernel binary"
 	help
-	  Various drivers in the kernel source tree may require firmware,
-	  which is generally available in your distribution's linux-firmware
-	  package.
+	  Device drivers which require firmware can typically deal with
+	  having the kernel load firmware from the various supported
+	  /lib/firmware/ paths. This option enables you to build into the
+	  kernel firmware files. Built-in firmware searches are preceeded
+	  over firmware lookups using your filesystem over the supported
+	  /lib/firmware paths documented on CONFIG_FW_LOADER.
 
-	  The linux-firmware package should install firmware into
-	  /lib/firmware/ on your system, so they can be loaded by userspace
-	  helpers on request.
-
-	  This option allows firmware to be built into the kernel for the case
-	  where the user either cannot or doesn't want to provide it from
-	  userspace at runtime (for example, when the firmware in question is
-	  required for accessing the boot device, and the user doesn't want to
-	  use an initrd).
+	  This may be useful for testing or if the firmware is required early on
+	  in boot and cannot rely on the firmware being placed in an initrd or
+	  initramfs.
 
 	  This option is a string and takes the (space-separated) names of the
 	  firmware files -- the same names that appear in MODULE_FIRMWARE()
@@ -113,7 +138,7 @@ config EXTRA_FIRMWARE
 	  For example, you might set CONFIG_EXTRA_FIRMWARE="usb8388.bin", copy
 	  the usb8388.bin file into /lib/firmware, and build the kernel. Then
 	  any request_firmware("usb8388.bin") will be satisfied internally
-	  without needing to call out to userspace.
+	  inside the kernel without ever looking at your filesystem at runtime.
 
 	  WARNING: If you include additional firmware files into your binary
 	  kernel image that are not available under the terms of the GPL,
@@ -130,22 +155,89 @@ config EXTRA_FIRMWARE_DIR
 	  looks for the firmware files listed in the EXTRA_FIRMWARE option.
 
 config FW_LOADER_USER_HELPER
-	bool
+	bool "Enable the firmware sysfs fallback mechanism"
+	help
+	  This option enables a sysfs loading facility to enable firmware
+	  loading to the kernel through userspace as a fallback mechanism
+	  if and only if the kernel's direct filesystem lookup for the
+	  firmware failed using the different /lib/firmware/ paths, or the
+	  path specified in the firmware_class path module parameter, or the
+	  firmware_class path kernel boot parameter if the firmware_class is
+	  built-in. For details on how to work with the sysfs fallback mechanism
+	  refer to Documentation/driver-api/firmware/fallback-mechanisms.rst.
+
+	  The direct filesystem lookup for firwmare is always used first now.
+
+	  If the kernel's direct filesystem lookup for firware fails to find
+	  the requested firmware a sysfs fallback loading facility is made
+	  available and userspace is informed about this through uevents.
+	  The uevent can be supressed if the driver explicitly requested it,
+	  this is known as the driver using the custom fallback mechanism.
+	  If the custom fallback mechanism is used userspace must always
+	  acknowledge failure to find firmware as the timeout for the fallback
+	  mechanism is disabled, and failed requests will linger forever.
+
+	  This used to be the default firmware loading facility, and udev used
+	  listen for uvents to load firmware for the kernel. The firmware
+	  loading facility functionality in udev has been removed, as such it
+	  can no longer be relied upon as a fallback mechanism. Linux no longer
+	  relies on or uses a fallback mechanism in userspace.
+
+	  Since this was the default firmware loading facility at one point,
+	  old userspace may exist which relies upon it, and as such this
+	  mechanism can never be removed from the kernel.
+
+	  You should only enable this functionality if you are certain you
+	  require a fallback mechanism and have a userspace mechanism ready to
+	  load firmware in case it is not found. Another reason kernels may
+	  have this feature enabled is to support a driver which explicitly
+	  relies on this fallback mechanism. Only two drivers need this today:
+
+	    o CONFIG_LEDS_LP55XX_COMMON
+	    o CONFIG_DELL_RBU
+
+	  Outside of supporting the above drivers, another reason for needing
+	  this may be that your firmware resides outside of the paths the kernel
+	  looks for and cannot possibily be specified using the firmware_class
+	  path module parameter or kernel firmware_class path boot parameter
+	  if firmware_class is built-in.
+
+	  A modern use case may be to temporarily mount a custom partition
+	  during provisioning which is only accessible to userspace, and then
+	  to use it to look for and fetch the required firmware. Such type of
+	  driver functionality may not even ever be desirable upstream by
+	  vendors, and as such is only required to be supported as an interface
+	  for provisioning. Since udev's firmware loading facility has been
+	  removed you can use firmwared or a fork of it to customize how you
+	  want to load firmware based on uevents issued:
+	  https://github.com/teg/firmwared
+
+	  Enabling this option will increase your kernel image size by about
+	  13436 bytes.
+
+	  If you are unsure about this, say N here, unless you are Linux
+	  distribution and need to support the above two drivers, or you are
+	  certain you need to support some really custom firmware loading
+	  facility in userspace.
 
 config FW_LOADER_USER_HELPER_FALLBACK
-	bool "Fallback user-helper invocation for firmware loading"
-	depends on FW_LOADER
-	select FW_LOADER_USER_HELPER
+	bool "Force the firmware sysfs fallback mechanism when possible"
+	depends on FW_LOADER_USER_HELPER
 	help
-	  This option enables / disables the invocation of user-helper
-	  (e.g. udev) for loading firmware files as a fallback after the
-	  direct file loading in kernel fails.  The user-mode helper is
-	  no longer required unless you have a special firmware file that
-	  resides in a non-standard path. Moreover, the udev support has
-	  been deprecated upstream.
+	  Enabling this option forces a sysfs userspace fallback mechanism
+	  to be used for all firmware requests which explicitly do not disable a
+	  a fallback mechanism. Firmware calls which do prohibit a fallback
+	  mechanism is request_firmware_direct(). This option is kept for
+          backward compatibility purposes given this precise mechanism can also
+	  be enabled by setting the proc sysctl value to true:
+
+	       /proc/sys/kernel/firmware_config/force_sysfs_fallback
 
 	  If you are unsure about this, say N here.
 
+endif # FW_LOADER
+endmenu
+
 config WANT_DEV_COREDUMP
 	bool
 	help
-- 
2.17.0

^ permalink raw reply related

* [PATCH v5 6/6] firmware_loader: move kconfig FW_LOADER entries to its own file
From: Luis R. Rodriguez @ 2018-05-04 17:43 UTC (permalink / raw)
  To: gregkh
  Cc: akpm, keescook, josh, teg, wagi, hdegoede, andresx7, zohar,
	kubakici, shuah, mfuzzey, dhowells, pali.rohar, tiwai, kvalo,
	arend.vanspriel, zajec5, nbroeking, markivx, broonie,
	dmitry.torokhov, dwmw2, torvalds, Abhay_Salunke, bjorn.andersson,
	jewalt, oneukum, cantabile.desu, ast, hare, jejb, martin.petersen,
	khc, davem, maco, arve, tkjos, linux
In-Reply-To: <20180504174356.13227-1-mcgrof@kernel.org>

This will make it easier to track and easier to understand
what components and features are part of the FW_LOADER. There
are some components related to firmware which have *nothing* to
do with the FW_LOADER, souch as PREVENT_FIRMWARE_BUILD.

Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 drivers/base/Kconfig                 | 150 +--------------------------
 drivers/base/firmware_loader/Kconfig | 149 ++++++++++++++++++++++++++
 2 files changed, 150 insertions(+), 149 deletions(-)
 create mode 100644 drivers/base/firmware_loader/Kconfig

diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index bf2d464b0e2c..06d6e27784fa 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -88,155 +88,7 @@ config PREVENT_FIRMWARE_BUILD
 	    o CONFIG_WANXL through CONFIG_WANXL_BUILD_FIRMWARE
 	    o CONFIG_SCSI_AIC79XX through CONFIG_AIC79XX_BUILD_FIRMWARE
 
-menu "Firmware loader"
-
-config FW_LOADER
-	tristate "Firmware loading facility" if EXPERT
-	default y
-	---help---
-	  This enables the firmware loading facility in the kernel. The kernel
-	  will first look for built-in firmware, if it has any. Next, it will
-	  look for the requested firmware in a series of filesystem paths:
-
-		o firmware_class path module parameter or kernel boot param
-		o /lib/firmware/updates/UTS_RELEASE
-		o /lib/firmware/updates
-		o /lib/firmware/UTS_RELEASE
-		o /lib/firmware
-
-	  Enabling this feature only increases your kernel image by about
-	  828 bytes, enable this option unless you are certain you don't
-	  need firmware.
-
-	  You typically want this built-in (=y) but you can also enable this
-	  as a module, in which case the firmware_class module will be built.
-	  You also want to be sure to enable this built-in if you are going to
-	  enable built-in firmware (CONFIG_EXTRA_FIRMWARE).
-
-if FW_LOADER
-
-config EXTRA_FIRMWARE
-	string "Build these firmware blobs into the kernel binary"
-	help
-	  Device drivers which require firmware can typically deal with
-	  having the kernel load firmware from the various supported
-	  /lib/firmware/ paths. This option enables you to build into the
-	  kernel firmware files. Built-in firmware searches are preceeded
-	  over firmware lookups using your filesystem over the supported
-	  /lib/firmware paths documented on CONFIG_FW_LOADER.
-
-	  This may be useful for testing or if the firmware is required early on
-	  in boot and cannot rely on the firmware being placed in an initrd or
-	  initramfs.
-
-	  This option is a string and takes the (space-separated) names of the
-	  firmware files -- the same names that appear in MODULE_FIRMWARE()
-	  and request_firmware() in the source. These files should exist under
-	  the directory specified by the EXTRA_FIRMWARE_DIR option, which is
-	  /lib/firmware by default.
-
-	  For example, you might set CONFIG_EXTRA_FIRMWARE="usb8388.bin", copy
-	  the usb8388.bin file into /lib/firmware, and build the kernel. Then
-	  any request_firmware("usb8388.bin") will be satisfied internally
-	  inside the kernel without ever looking at your filesystem at runtime.
-
-	  WARNING: If you include additional firmware files into your binary
-	  kernel image that are not available under the terms of the GPL,
-	  then it may be a violation of the GPL to distribute the resulting
-	  image since it combines both GPL and non-GPL work. You should
-	  consult a lawyer of your own before distributing such an image.
-
-config EXTRA_FIRMWARE_DIR
-	string "Firmware blobs root directory"
-	depends on EXTRA_FIRMWARE != ""
-	default "/lib/firmware"
-	help
-	  This option controls the directory in which the kernel build system
-	  looks for the firmware files listed in the EXTRA_FIRMWARE option.
-
-config FW_LOADER_USER_HELPER
-	bool "Enable the firmware sysfs fallback mechanism"
-	help
-	  This option enables a sysfs loading facility to enable firmware
-	  loading to the kernel through userspace as a fallback mechanism
-	  if and only if the kernel's direct filesystem lookup for the
-	  firmware failed using the different /lib/firmware/ paths, or the
-	  path specified in the firmware_class path module parameter, or the
-	  firmware_class path kernel boot parameter if the firmware_class is
-	  built-in. For details on how to work with the sysfs fallback mechanism
-	  refer to Documentation/driver-api/firmware/fallback-mechanisms.rst.
-
-	  The direct filesystem lookup for firwmare is always used first now.
-
-	  If the kernel's direct filesystem lookup for firware fails to find
-	  the requested firmware a sysfs fallback loading facility is made
-	  available and userspace is informed about this through uevents.
-	  The uevent can be supressed if the driver explicitly requested it,
-	  this is known as the driver using the custom fallback mechanism.
-	  If the custom fallback mechanism is used userspace must always
-	  acknowledge failure to find firmware as the timeout for the fallback
-	  mechanism is disabled, and failed requests will linger forever.
-
-	  This used to be the default firmware loading facility, and udev used
-	  listen for uvents to load firmware for the kernel. The firmware
-	  loading facility functionality in udev has been removed, as such it
-	  can no longer be relied upon as a fallback mechanism. Linux no longer
-	  relies on or uses a fallback mechanism in userspace.
-
-	  Since this was the default firmware loading facility at one point,
-	  old userspace may exist which relies upon it, and as such this
-	  mechanism can never be removed from the kernel.
-
-	  You should only enable this functionality if you are certain you
-	  require a fallback mechanism and have a userspace mechanism ready to
-	  load firmware in case it is not found. Another reason kernels may
-	  have this feature enabled is to support a driver which explicitly
-	  relies on this fallback mechanism. Only two drivers need this today:
-
-	    o CONFIG_LEDS_LP55XX_COMMON
-	    o CONFIG_DELL_RBU
-
-	  Outside of supporting the above drivers, another reason for needing
-	  this may be that your firmware resides outside of the paths the kernel
-	  looks for and cannot possibily be specified using the firmware_class
-	  path module parameter or kernel firmware_class path boot parameter
-	  if firmware_class is built-in.
-
-	  A modern use case may be to temporarily mount a custom partition
-	  during provisioning which is only accessible to userspace, and then
-	  to use it to look for and fetch the required firmware. Such type of
-	  driver functionality may not even ever be desirable upstream by
-	  vendors, and as such is only required to be supported as an interface
-	  for provisioning. Since udev's firmware loading facility has been
-	  removed you can use firmwared or a fork of it to customize how you
-	  want to load firmware based on uevents issued:
-	  https://github.com/teg/firmwared
-
-	  Enabling this option will increase your kernel image size by about
-	  13436 bytes.
-
-	  If you are unsure about this, say N here, unless you are Linux
-	  distribution and need to support the above two drivers, or you are
-	  certain you need to support some really custom firmware loading
-	  facility in userspace.
-
-config FW_LOADER_USER_HELPER_FALLBACK
-	bool "Force the firmware sysfs fallback mechanism when possible"
-	depends on FW_LOADER_USER_HELPER
-	help
-	  Enabling this option forces a sysfs userspace fallback mechanism
-	  to be used for all firmware requests which explicitly do not disable a
-	  a fallback mechanism. Firmware calls which do prohibit a fallback
-	  mechanism is request_firmware_direct(). This option is kept for
-          backward compatibility purposes given this precise mechanism can also
-	  be enabled by setting the proc sysctl value to true:
-
-	       /proc/sys/kernel/firmware_config/force_sysfs_fallback
-
-	  If you are unsure about this, say N here.
-
-endif # FW_LOADER
-endmenu
+source "drivers/base/firmware_loader/Kconfig"
 
 config WANT_DEV_COREDUMP
 	bool
diff --git a/drivers/base/firmware_loader/Kconfig b/drivers/base/firmware_loader/Kconfig
new file mode 100644
index 000000000000..b4cf42d6fe51
--- /dev/null
+++ b/drivers/base/firmware_loader/Kconfig
@@ -0,0 +1,149 @@
+menu "Firmware loader"
+
+config FW_LOADER
+	tristate "Firmware loading facility" if EXPERT
+	default y
+	---help---
+	  This enables the firmware loading facility in the kernel. The kernel
+	  will first look for built-in firmware, if it has any. Next, it will
+	  look for the requested firmware in a series of filesystem paths:
+
+		o firmware_class path module parameter or kernel boot param
+		o /lib/firmware/updates/UTS_RELEASE
+		o /lib/firmware/updates
+		o /lib/firmware/UTS_RELEASE
+		o /lib/firmware
+
+	  Enabling this feature only increases your kernel image by about
+	  828 bytes, enable this option unless you are certain you don't
+	  need firmware.
+
+	  You typically want this built-in (=y) but you can also enable this
+	  as a module, in which case the firmware_class module will be built.
+	  You also want to be sure to enable this built-in if you are going to
+	  enable built-in firmware (CONFIG_EXTRA_FIRMWARE).
+
+if FW_LOADER
+
+config EXTRA_FIRMWARE
+	string "Build these firmware blobs into the kernel binary"
+	help
+	  Device drivers which require firmware can typically deal with
+	  having the kernel load firmware from the various supported
+	  /lib/firmware/ paths. This option enables you to build into the
+	  kernel firmware files. Built-in firmware searches are preceeded
+	  over firmware lookups using your filesystem over the supported
+	  /lib/firmware paths documented on CONFIG_FW_LOADER.
+
+	  This may be useful for testing or if the firmware is required early on
+	  in boot and cannot rely on the firmware being placed in an initrd or
+	  initramfs.
+
+	  This option is a string and takes the (space-separated) names of the
+	  firmware files -- the same names that appear in MODULE_FIRMWARE()
+	  and request_firmware() in the source. These files should exist under
+	  the directory specified by the EXTRA_FIRMWARE_DIR option, which is
+	  /lib/firmware by default.
+
+	  For example, you might set CONFIG_EXTRA_FIRMWARE="usb8388.bin", copy
+	  the usb8388.bin file into /lib/firmware, and build the kernel. Then
+	  any request_firmware("usb8388.bin") will be satisfied internally
+	  inside the kernel without ever looking at your filesystem at runtime.
+
+	  WARNING: If you include additional firmware files into your binary
+	  kernel image that are not available under the terms of the GPL,
+	  then it may be a violation of the GPL to distribute the resulting
+	  image since it combines both GPL and non-GPL work. You should
+	  consult a lawyer of your own before distributing such an image.
+
+config EXTRA_FIRMWARE_DIR
+	string "Firmware blobs root directory"
+	depends on EXTRA_FIRMWARE != ""
+	default "/lib/firmware"
+	help
+	  This option controls the directory in which the kernel build system
+	  looks for the firmware files listed in the EXTRA_FIRMWARE option.
+
+config FW_LOADER_USER_HELPER
+	bool "Enable the firmware sysfs fallback mechanism"
+	help
+	  This option enables a sysfs loading facility to enable firmware
+	  loading to the kernel through userspace as a fallback mechanism
+	  if and only if the kernel's direct filesystem lookup for the
+	  firmware failed using the different /lib/firmware/ paths, or the
+	  path specified in the firmware_class path module parameter, or the
+	  firmware_class path kernel boot parameter if the firmware_class is
+	  built-in. For details on how to work with the sysfs fallback mechanism
+	  refer to Documentation/driver-api/firmware/fallback-mechanisms.rst.
+
+	  The direct filesystem lookup for firwmare is always used first now.
+
+	  If the kernel's direct filesystem lookup for firware fails to find
+	  the requested firmware a sysfs fallback loading facility is made
+	  available and userspace is informed about this through uevents.
+	  The uevent can be supressed if the driver explicitly requested it,
+	  this is known as the driver using the custom fallback mechanism.
+	  If the custom fallback mechanism is used userspace must always
+	  acknowledge failure to find firmware as the timeout for the fallback
+	  mechanism is disabled, and failed requests will linger forever.
+
+	  This used to be the default firmware loading facility, and udev used
+	  listen for uvents to load firmware for the kernel. The firmware
+	  loading facility functionality in udev has been removed, as such it
+	  can no longer be relied upon as a fallback mechanism. Linux no longer
+	  relies on or uses a fallback mechanism in userspace.
+
+	  Since this was the default firmware loading facility at one point,
+	  old userspace may exist which relies upon it, and as such this
+	  mechanism can never be removed from the kernel.
+
+	  You should only enable this functionality if you are certain you
+	  require a fallback mechanism and have a userspace mechanism ready to
+	  load firmware in case it is not found. Another reason kernels may
+	  have this feature enabled is to support a driver which explicitly
+	  relies on this fallback mechanism. Only two drivers need this today:
+
+	    o CONFIG_LEDS_LP55XX_COMMON
+	    o CONFIG_DELL_RBU
+
+	  Outside of supporting the above drivers, another reason for needing
+	  this may be that your firmware resides outside of the paths the kernel
+	  looks for and cannot possibily be specified using the firmware_class
+	  path module parameter or kernel firmware_class path boot parameter
+	  if firmware_class is built-in.
+
+	  A modern use case may be to temporarily mount a custom partition
+	  during provisioning which is only accessible to userspace, and then
+	  to use it to look for and fetch the required firmware. Such type of
+	  driver functionality may not even ever be desirable upstream by
+	  vendors, and as such is only required to be supported as an interface
+	  for provisioning. Since udev's firmware loading facility has been
+	  removed you can use firmwared or a fork of it to customize how you
+	  want to load firmware based on uevents issued:
+	  https://github.com/teg/firmwared
+
+	  Enabling this option will increase your kernel image size by about
+	  13436 bytes.
+
+	  If you are unsure about this, say N here, unless you are Linux
+	  distribution and need to support the above two drivers, or you are
+	  certain you need to support some really custom firmware loading
+	  facility in userspace.
+
+config FW_LOADER_USER_HELPER_FALLBACK
+	bool "Force the firmware sysfs fallback mechanism when possible"
+	depends on FW_LOADER_USER_HELPER
+	help
+	  Enabling this option forces a sysfs userspace fallback mechanism
+	  to be used for all firmware requests which explicitly do not disable a
+	  a fallback mechanism. Firmware calls which do prohibit a fallback
+	  mechanism is request_firmware_direct(). This option is kept for
+          backward compatibility purposes given this precise mechanism can also
+	  be enabled by setting the proc sysctl value to true:
+
+	       /proc/sys/kernel/firmware_config/force_sysfs_fallback
+
+	  If you are unsure about this, say N here.
+
+endif # FW_LOADER
+endmenu
-- 
2.17.0

^ permalink raw reply related

* [PATCH v5 1/6] firmware: wrap FW_OPT_* into an enum
From: Luis R. Rodriguez @ 2018-05-04 17:43 UTC (permalink / raw)
  To: gregkh
  Cc: akpm, keescook, josh, teg, wagi, hdegoede, andresx7, zohar,
	kubakici, shuah, mfuzzey, dhowells, pali.rohar, tiwai, kvalo,
	arend.vanspriel, zajec5, nbroeking, markivx, broonie,
	dmitry.torokhov, dwmw2, torvalds, Abhay_Salunke, bjorn.andersson,
	jewalt, oneukum, cantabile.desu, ast, hare, jejb, martin.petersen,
	khc, davem, maco, arve, tkjos, linux
In-Reply-To: <20180504174356.13227-1-mcgrof@kernel.org>

From: Andres Rodriguez <andresx7@gmail.com>

This should let us associate enum kdoc to these values.
While at it, kdocify the fw_opt.

Signed-off-by: Andres Rodriguez <andresx7@gmail.com>
Acked-by: Luis R. Rodriguez <mcgrof@kernel.org>
[mcgrof: coding style fixes, merge kdoc with enum move]
Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
---
 drivers/base/firmware_loader/fallback.c | 12 ++++----
 drivers/base/firmware_loader/fallback.h |  6 ++--
 drivers/base/firmware_loader/firmware.h | 37 +++++++++++++++++++------
 drivers/base/firmware_loader/main.c     |  6 ++--
 4 files changed, 42 insertions(+), 19 deletions(-)

diff --git a/drivers/base/firmware_loader/fallback.c b/drivers/base/firmware_loader/fallback.c
index 358354148dec..b57a7b3b4122 100644
--- a/drivers/base/firmware_loader/fallback.c
+++ b/drivers/base/firmware_loader/fallback.c
@@ -512,7 +512,7 @@ static const struct attribute_group *fw_dev_attr_groups[] = {
 
 static struct fw_sysfs *
 fw_create_instance(struct firmware *firmware, const char *fw_name,
-		   struct device *device, unsigned int opt_flags)
+		   struct device *device, enum fw_opt opt_flags)
 {
 	struct fw_sysfs *fw_sysfs;
 	struct device *f_dev;
@@ -545,7 +545,7 @@ fw_create_instance(struct firmware *firmware, const char *fw_name,
  * In charge of constructing a sysfs fallback interface for firmware loading.
  **/
 static int fw_load_sysfs_fallback(struct fw_sysfs *fw_sysfs,
-				  unsigned int opt_flags, long timeout)
+				  enum fw_opt opt_flags, long timeout)
 {
 	int retval = 0;
 	struct device *f_dev = &fw_sysfs->dev;
@@ -599,7 +599,7 @@ static int fw_load_sysfs_fallback(struct fw_sysfs *fw_sysfs,
 
 static int fw_load_from_user_helper(struct firmware *firmware,
 				    const char *name, struct device *device,
-				    unsigned int opt_flags)
+				    enum fw_opt opt_flags)
 {
 	struct fw_sysfs *fw_sysfs;
 	long timeout;
@@ -640,7 +640,7 @@ static int fw_load_from_user_helper(struct firmware *firmware,
 	return ret;
 }
 
-static bool fw_force_sysfs_fallback(unsigned int opt_flags)
+static bool fw_force_sysfs_fallback(enum fw_opt opt_flags)
 {
 	if (fw_fallback_config.force_sysfs_fallback)
 		return true;
@@ -649,7 +649,7 @@ static bool fw_force_sysfs_fallback(unsigned int opt_flags)
 	return true;
 }
 
-static bool fw_run_sysfs_fallback(unsigned int opt_flags)
+static bool fw_run_sysfs_fallback(enum fw_opt opt_flags)
 {
 	if (fw_fallback_config.ignore_sysfs_fallback) {
 		pr_info_once("Ignoring firmware sysfs fallback due to sysctl knob\n");
@@ -664,7 +664,7 @@ static bool fw_run_sysfs_fallback(unsigned int opt_flags)
 
 int fw_sysfs_fallback(struct firmware *fw, const char *name,
 		      struct device *device,
-		      unsigned int opt_flags,
+		      enum fw_opt opt_flags,
 		      int ret)
 {
 	if (!fw_run_sysfs_fallback(opt_flags))
diff --git a/drivers/base/firmware_loader/fallback.h b/drivers/base/firmware_loader/fallback.h
index f8255670a663..a3b73a09db6c 100644
--- a/drivers/base/firmware_loader/fallback.h
+++ b/drivers/base/firmware_loader/fallback.h
@@ -5,6 +5,8 @@
 #include <linux/firmware.h>
 #include <linux/device.h>
 
+#include "firmware.h"
+
 /**
  * struct firmware_fallback_config - firmware fallback configuration settings
  *
@@ -31,7 +33,7 @@ struct firmware_fallback_config {
 #ifdef CONFIG_FW_LOADER_USER_HELPER
 int fw_sysfs_fallback(struct firmware *fw, const char *name,
 		      struct device *device,
-		      unsigned int opt_flags,
+		      enum fw_opt opt_flags,
 		      int ret);
 void kill_pending_fw_fallback_reqs(bool only_kill_custom);
 
@@ -43,7 +45,7 @@ void unregister_sysfs_loader(void);
 #else /* CONFIG_FW_LOADER_USER_HELPER */
 static inline int fw_sysfs_fallback(struct firmware *fw, const char *name,
 				    struct device *device,
-				    unsigned int opt_flags,
+				    enum fw_opt opt_flags,
 				    int ret)
 {
 	/* Keep carrying over the same error */
diff --git a/drivers/base/firmware_loader/firmware.h b/drivers/base/firmware_loader/firmware.h
index 64acbb1a392c..4f433b447367 100644
--- a/drivers/base/firmware_loader/firmware.h
+++ b/drivers/base/firmware_loader/firmware.h
@@ -2,6 +2,7 @@
 #ifndef __FIRMWARE_LOADER_H
 #define __FIRMWARE_LOADER_H
 
+#include <linux/bitops.h>
 #include <linux/firmware.h>
 #include <linux/types.h>
 #include <linux/kref.h>
@@ -10,13 +11,33 @@
 
 #include <generated/utsrelease.h>
 
-/* firmware behavior options */
-#define FW_OPT_UEVENT			(1U << 0)
-#define FW_OPT_NOWAIT			(1U << 1)
-#define FW_OPT_USERHELPER		(1U << 2)
-#define FW_OPT_NO_WARN			(1U << 3)
-#define FW_OPT_NOCACHE			(1U << 4)
-#define FW_OPT_NOFALLBACK		(1U << 5)
+/**
+ * enum fw_opt - options to control firmware loading behaviour
+ *
+ * @FW_OPT_UEVENT: Enables the fallback mechanism to send a kobject uevent
+ *	when the firmware is not found. Userspace is in charge to load the
+ *	firmware using the sysfs loading facility.
+ * @FW_OPT_NOWAIT: Used to describe the firmware request is asynchronous.
+ * @FW_OPT_USERHELPER: Enable the fallback mechanism, in case the direct
+ *	filesystem lookup fails at finding the firmware.  For details refer to
+ *	fw_sysfs_fallback().
+ * @FW_OPT_NO_WARN: Quiet, avoid printing warning messages.
+ * @FW_OPT_NOCACHE: Disables firmware caching. Firmware caching is used to
+ *	cache the firmware upon suspend, so that upon resume races against the
+ *	firmware file lookup on storage is avoided. Used for calls where the
+ *	file may be too big, or where the driver takes charge of its own
+ *	firmware caching mechanism.
+ * @FW_OPT_NOFALLBACK: Disable the fallback mechanism. Takes precedence over
+ *	&FW_OPT_UEVENT and &FW_OPT_USERHELPER.
+ */
+enum fw_opt {
+	FW_OPT_UEVENT =         BIT(0),
+	FW_OPT_NOWAIT =         BIT(1),
+	FW_OPT_USERHELPER =     BIT(2),
+	FW_OPT_NO_WARN =        BIT(3),
+	FW_OPT_NOCACHE =        BIT(4),
+	FW_OPT_NOFALLBACK =     BIT(5),
+};
 
 enum fw_status {
 	FW_STATUS_UNKNOWN,
@@ -110,6 +131,6 @@ static inline void fw_state_done(struct fw_priv *fw_priv)
 }
 
 int assign_fw(struct firmware *fw, struct device *device,
-	      unsigned int opt_flags);
+	      enum fw_opt opt_flags);
 
 #endif /* __FIRMWARE_LOADER_H */
diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c
index eb34089e4299..9919f0e6a7cc 100644
--- a/drivers/base/firmware_loader/main.c
+++ b/drivers/base/firmware_loader/main.c
@@ -443,7 +443,7 @@ static int fw_add_devm_name(struct device *dev, const char *name)
 #endif
 
 int assign_fw(struct firmware *fw, struct device *device,
-	      unsigned int opt_flags)
+	      enum fw_opt opt_flags)
 {
 	struct fw_priv *fw_priv = fw->priv;
 	int ret;
@@ -558,7 +558,7 @@ static void fw_abort_batch_reqs(struct firmware *fw)
 static int
 _request_firmware(const struct firmware **firmware_p, const char *name,
 		  struct device *device, void *buf, size_t size,
-		  unsigned int opt_flags)
+		  enum fw_opt opt_flags)
 {
 	struct firmware *fw = NULL;
 	int ret;
@@ -734,7 +734,7 @@ struct firmware_work {
 	struct device *device;
 	void *context;
 	void (*cont)(const struct firmware *fw, void *context);
-	unsigned int opt_flags;
+	enum fw_opt opt_flags;
 };
 
 static void request_firmware_work_func(struct work_struct *work)
-- 
2.17.0

^ permalink raw reply related

* Re: [PATCH] net: Work around crash in ipv6 fib-walk-continue
From: David Ahern @ 2018-05-04 17:47 UTC (permalink / raw)
  To: greearb, netdev
In-Reply-To: <1524160886-20401-1-git-send-email-greearb@candelatech.com>

On 4/19/18 12:01 PM, greearb@candelatech.com wrote:
> From: Ben Greear <greearb@candelatech.com>
> 
> This keeps us from crashing in certain test cases where we
> bring up many (1000, for instance) mac-vlans with IPv6
> enabled in the kernel.  This bug has been around for a
> very long time.
> 
> Until a real fix is found (and for stable), maybe it
> is better to return an incomplete fib walk instead
> of crashing.
> 
> BUG: unable to handle kernel NULL pointer dereference at 8
> IP: fib6_walk_continue+0x5b/0x140 [ipv6]
> PGD 80000007dfc0c067 P4D 80000007dfc0c067 PUD 7e66ff067 PMD 0
> Oops: 0000 [#1] PREEMPT SMP PTI
> Modules linked in: nf_conntrack_netlink nf_conntrack nfnetlink nf_defrag_ipv4 libcrc32c vrf]
> CPU: 3 PID: 15117 Comm: ip Tainted: G           O     4.16.0+ #5
> Hardware name: Iron_Systems,Inc CS-CAD-2U-A02/X10SRL-F, BIOS 2.0b 05/02/2017
> RIP: 0010:fib6_walk_continue+0x5b/0x140 [ipv6]
> RSP: 0018:ffffc90008c3bc10 EFLAGS: 00010287
> RAX: ffff88085ac45050 RBX: ffff8807e03008a0 RCX: 0000000000000000
> RDX: 0000000000000000 RSI: ffffc90008c3bc48 RDI: ffffffff8232b240
> RBP: ffff880819167600 R08: 0000000000000008 R09: ffff8807dff10071
> R10: ffffc90008c3bbd0 R11: 0000000000000000 R12: ffff8807e03008a0
> R13: 0000000000000002 R14: ffff8807e05744c8 R15: ffff8807e08ef000
> FS:  00007f2f04342700(0000) GS:ffff88087fcc0000(0000) knlGS:0000000000000000
> CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> CR2: 0000000000000008 CR3: 00000007e0556002 CR4: 00000000003606e0
> DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
> DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
> Call Trace:
>  inet6_dump_fib+0x14b/0x2c0 [ipv6]
>  netlink_dump+0x216/0x2a0
>  netlink_recvmsg+0x254/0x400
>  ? copy_msghdr_from_user+0xb5/0x110
>  ___sys_recvmsg+0xe9/0x230
>  ? find_held_lock+0x3b/0xb0
>  ? __handle_mm_fault+0x617/0x1180
>  ? __audit_syscall_entry+0xb3/0x110
>  ? __sys_recvmsg+0x39/0x70
>  __sys_recvmsg+0x39/0x70
>  do_syscall_64+0x63/0x120
>  entry_SYSCALL_64_after_hwframe+0x3d/0xa2
> RIP: 0033:0x7f2f03a72030
> RSP: 002b:00007fffab3de508 EFLAGS: 00000246 ORIG_RAX: 000000000000002f
> RAX: ffffffffffffffda RBX: 00007fffab3e641c RCX: 00007f2f03a72030
> RDX: 0000000000000000 RSI: 00007fffab3de570 RDI: 0000000000000004
> RBP: 0000000000000000 R08: 0000000000007e6c R09: 00007fffab3e63a8
> R10: 00007fffab3de5b0 R11: 0000000000000246 R12: 00007fffab3e6608
> R13: 000000000066b460 R14: 0000000000007e6c R15: 0000000000000000
> Code: 85 d2 74 17 f6 40 2a 04 74 11 8b 53 2c 85 d2 0f 84 d7 00 00 00 83 ea 01 89 53 2c c7 4
> RIP: fib6_walk_continue+0x5b/0x140 [ipv6] RSP: ffffc90008c3bc10
> CR2: 0000000000000008
> ---[ end trace bd03458864eb266c ]---
> 
> Signed-off-by: Ben Greear <greearb@candelatech.com>
> ---
> 

Does your use case that triggers this involve replacing routes? I just
noticed the route delete code in fib6_add_rt2node does not have the
'Adjust walkers' code that is in fib6_del_route.

Further, the adjust walkers code in fib6_del_route looks suspicious in
its timing with route deletes. If you have a reliable reproducer we can
try a few things with fib6_del_route and the walker code.

^ permalink raw reply

* [RFC PATCH 0/2] Introduce assert_in_softirq()
From: Sebastian Andrzej Siewior @ 2018-05-04 17:51 UTC (permalink / raw)
  To: linux-kernel
  Cc: tglx, Peter Zijlstra, Ingo Molnar, David S. Miller, Johannes Berg,
	Alexander Aring, Stefan Schmidt, netdev, linux-wireless,
	linux-wpan

ieee80211_rx_napi() has a check to ensure that it is invoked in softirq
context / with BH disabled. It is there because it invokes
netif_receive_skb() which has this requirement.
On -RT this check does not work as expected so there is always this
warning.
Tree wide there are two users of this check: ieee80211_rx_napi() and
ieee802154_rx(). This approach introduces assert_in_softirq() which does
the check if lockdep is enabled. This check could then become a nop on
-RT.

As an alternative netif_receive_skb() (or ieee80211_rx_napi() could do
local_bh_disable() / local_bh_enable() unconditionally. The _disable()
part is very cheap. The _enable() part is more expensive because it
includes a function call. We could avoid that jump in the likely case
when BH was already disabled by something like:

 static inline void local_bh_enable(void)
 {
         if (softirq_count() == SOFTIRQ_DISABLE_OFFSET)
                 __local_bh_enable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);
         else
                 preempt_count_sub(SOFTIRQ_DISABLE_OFFSET);
 }

Which would make bh_enable() cheaper for everyone.

Sebastian

^ permalink raw reply

* [RFC PATCH 1/2] lockdep: Add a assert_in_softirq()
From: Sebastian Andrzej Siewior @ 2018-05-04 17:51 UTC (permalink / raw)
  To: linux-kernel
  Cc: tglx, Peter Zijlstra, Ingo Molnar, David S. Miller, Johannes Berg,
	Alexander Aring, Stefan Schmidt, netdev, linux-wireless,
	linux-wpan, Anna-Maria Gleixner, Sebastian Andrzej Siewior
In-Reply-To: <20180504175144.12179-1-bigeasy@linutronix.de>

From: Anna-Maria Gleixner <anna-maria@linutronix.de>

Instead of directly warn on wrong context, check if softirq context is
set. This check could be a nop on RT.

Signed-off-by: Anna-Maria Gleixner <anna-maria@linutronix.de>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
---
 include/linux/lockdep.h | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/include/linux/lockdep.h b/include/linux/lockdep.h
index 6fc77d4dbdcd..59363c3047c6 100644
--- a/include/linux/lockdep.h
+++ b/include/linux/lockdep.h
@@ -608,11 +608,17 @@ do {									\
 			  "IRQs not disabled as expected\n");		\
 	} while (0)
 
+#define lockdep_assert_in_softirq()	do {				\
+		WARN_ONCE(debug_locks && !current->lockdep_recursion &&	\
+			  !current->softirq_context,			\
+			  "Not in softirq context as expected\n");	\
+	} while (0)
 #else
 # define might_lock(lock) do { } while (0)
 # define might_lock_read(lock) do { } while (0)
 # define lockdep_assert_irqs_enabled() do { } while (0)
 # define lockdep_assert_irqs_disabled() do { } while (0)
+# define lockdep_assert_in_softirq() do { } while (0)
 #endif
 
 #ifdef CONFIG_LOCKDEP
-- 
2.17.0

^ permalink raw reply related

* [RFC PATCH 2/2] net: mac808211: mac802154: use lockdep_assert_in_softirq() instead own warning
From: Sebastian Andrzej Siewior @ 2018-05-04 17:51 UTC (permalink / raw)
  To: linux-kernel
  Cc: tglx, Peter Zijlstra, Ingo Molnar, David S. Miller, Johannes Berg,
	Alexander Aring, Stefan Schmidt, netdev, linux-wireless,
	linux-wpan, Anna-Maria Gleixner, Sebastian Andrzej Siewior
In-Reply-To: <20180504175144.12179-1-bigeasy@linutronix.de>

From: Anna-Maria Gleixner <anna-maria@linutronix.de>

The warning in ieee802154_rx() and ieee80211_rx_napi() is there to ensure
the softirq context for the subsequent netif_receive_skb() call. The check
could be moved into the netif_receive_skb() function to prevent all calling
functions implement the checks on their own. Use the lockdep variant for
softirq context check. While at it, add a lockdep based check for irq
enabled as mentioned in the comment above netif_receive_skb().

Signed-off-by: Anna-Maria Gleixner <anna-maria@linutronix.de>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
---
 net/core/dev.c     | 3 +++
 net/mac80211/rx.c  | 2 --
 net/mac802154/rx.c | 2 --
 3 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/net/core/dev.c b/net/core/dev.c
index af0558b00c6c..7b4b8611cc21 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -4750,6 +4750,9 @@ static int netif_receive_skb_internal(struct sk_buff *skb)
  */
 int netif_receive_skb(struct sk_buff *skb)
 {
+	lockdep_assert_irqs_enabled();
+	lockdep_assert_in_softirq();
+
 	trace_netif_receive_skb_entry(skb);
 
 	return netif_receive_skb_internal(skb);
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 03102aff0953..653332d81c17 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -4324,8 +4324,6 @@ void ieee80211_rx_napi(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
 	struct ieee80211_supported_band *sband;
 	struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
 
-	WARN_ON_ONCE(softirq_count() == 0);
-
 	if (WARN_ON(status->band >= NUM_NL80211_BANDS))
 		goto drop;
 
diff --git a/net/mac802154/rx.c b/net/mac802154/rx.c
index 4dcf6e18563a..66916c270efc 100644
--- a/net/mac802154/rx.c
+++ b/net/mac802154/rx.c
@@ -258,8 +258,6 @@ void ieee802154_rx(struct ieee802154_local *local, struct sk_buff *skb)
 {
 	u16 crc;
 
-	WARN_ON_ONCE(softirq_count() == 0);
-
 	if (local->suspended)
 		goto drop;
 
-- 
2.17.0

^ permalink raw reply related

* Re: [PATCH] net: Work around crash in ipv6 fib-walk-continue
From: Ben Greear @ 2018-05-04 17:57 UTC (permalink / raw)
  To: David Ahern, netdev
In-Reply-To: <513868fb-b548-0312-a276-482fed070d0f@gmail.com>

On 05/04/2018 10:47 AM, David Ahern wrote:
> On 4/19/18 12:01 PM, greearb@candelatech.com wrote:
>> From: Ben Greear <greearb@candelatech.com>
>>
>> This keeps us from crashing in certain test cases where we
>> bring up many (1000, for instance) mac-vlans with IPv6
>> enabled in the kernel.  This bug has been around for a
>> very long time.
>>
>> Until a real fix is found (and for stable), maybe it
>> is better to return an incomplete fib walk instead
>> of crashing.
>>
>> BUG: unable to handle kernel NULL pointer dereference at 8
>> IP: fib6_walk_continue+0x5b/0x140 [ipv6]
>> PGD 80000007dfc0c067 P4D 80000007dfc0c067 PUD 7e66ff067 PMD 0
>> Oops: 0000 [#1] PREEMPT SMP PTI
>> Modules linked in: nf_conntrack_netlink nf_conntrack nfnetlink nf_defrag_ipv4 libcrc32c vrf]
>> CPU: 3 PID: 15117 Comm: ip Tainted: G           O     4.16.0+ #5
>> Hardware name: Iron_Systems,Inc CS-CAD-2U-A02/X10SRL-F, BIOS 2.0b 05/02/2017
>> RIP: 0010:fib6_walk_continue+0x5b/0x140 [ipv6]
>> RSP: 0018:ffffc90008c3bc10 EFLAGS: 00010287
>> RAX: ffff88085ac45050 RBX: ffff8807e03008a0 RCX: 0000000000000000
>> RDX: 0000000000000000 RSI: ffffc90008c3bc48 RDI: ffffffff8232b240
>> RBP: ffff880819167600 R08: 0000000000000008 R09: ffff8807dff10071
>> R10: ffffc90008c3bbd0 R11: 0000000000000000 R12: ffff8807e03008a0
>> R13: 0000000000000002 R14: ffff8807e05744c8 R15: ffff8807e08ef000
>> FS:  00007f2f04342700(0000) GS:ffff88087fcc0000(0000) knlGS:0000000000000000
>> CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
>> CR2: 0000000000000008 CR3: 00000007e0556002 CR4: 00000000003606e0
>> DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
>> DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
>> Call Trace:
>>  inet6_dump_fib+0x14b/0x2c0 [ipv6]
>>  netlink_dump+0x216/0x2a0
>>  netlink_recvmsg+0x254/0x400
>>  ? copy_msghdr_from_user+0xb5/0x110
>>  ___sys_recvmsg+0xe9/0x230
>>  ? find_held_lock+0x3b/0xb0
>>  ? __handle_mm_fault+0x617/0x1180
>>  ? __audit_syscall_entry+0xb3/0x110
>>  ? __sys_recvmsg+0x39/0x70
>>  __sys_recvmsg+0x39/0x70
>>  do_syscall_64+0x63/0x120
>>  entry_SYSCALL_64_after_hwframe+0x3d/0xa2
>> RIP: 0033:0x7f2f03a72030
>> RSP: 002b:00007fffab3de508 EFLAGS: 00000246 ORIG_RAX: 000000000000002f
>> RAX: ffffffffffffffda RBX: 00007fffab3e641c RCX: 00007f2f03a72030
>> RDX: 0000000000000000 RSI: 00007fffab3de570 RDI: 0000000000000004
>> RBP: 0000000000000000 R08: 0000000000007e6c R09: 00007fffab3e63a8
>> R10: 00007fffab3de5b0 R11: 0000000000000246 R12: 00007fffab3e6608
>> R13: 000000000066b460 R14: 0000000000007e6c R15: 0000000000000000
>> Code: 85 d2 74 17 f6 40 2a 04 74 11 8b 53 2c 85 d2 0f 84 d7 00 00 00 83 ea 01 89 53 2c c7 4
>> RIP: fib6_walk_continue+0x5b/0x140 [ipv6] RSP: ffffc90008c3bc10
>> CR2: 0000000000000008
>> ---[ end trace bd03458864eb266c ]---
>>
>> Signed-off-by: Ben Greear <greearb@candelatech.com>
>> ---
>>
>
> Does your use case that triggers this involve replacing routes? I just
> noticed the route delete code in fib6_add_rt2node does not have the
> 'Adjust walkers' code that is in fib6_del_route.
>
> Further, the adjust walkers code in fib6_del_route looks suspicious in
> its timing with route deletes. If you have a reliable reproducer we can
> try a few things with fib6_del_route and the walker code.

Yes, we replace routes, and yes we can reliably reproduce it and will
be happy to test patches.

Thanks,
Ben


-- 
Ben Greear <greearb@candelatech.com>
Candela Technologies Inc  http://www.candelatech.com

^ permalink raw reply

* Re: [PATCH net-next v8 1/7] sched: Add Common Applications Kept Enhanced (cake) qdisc
From: Cong Wang @ 2018-05-04 17:57 UTC (permalink / raw)
  To: Toke Høiland-Jørgensen
  Cc: Linux Kernel Network Developers, Cake List
In-Reply-To: <152544255298.11750.16512595539680113590.stgit@alrua-kau>

On Fri, May 4, 2018 at 7:02 AM, Toke Høiland-Jørgensen <toke@toke.dk> wrote:
> +struct cake_sched_data {
> +       struct cake_tin_data *tins;
> +
> +       struct cake_heap_entry overflow_heap[CAKE_QUEUES * CAKE_MAX_TINS];
> +       u16             overflow_timeout;
> +
> +       u16             tin_cnt;
> +       u8              tin_mode;
> +       u8              flow_mode;
> +       u8              ack_filter;
> +       u8              atm_mode;
> +
> +       /* time_next = time_this + ((len * rate_ns) >> rate_shft) */
> +       u16             rate_shft;
> +       u64             time_next_packet;
> +       u64             failsafe_next_packet;
> +       u32             rate_ns;
> +       u32             rate_bps;
> +       u16             rate_flags;
> +       s16             rate_overhead;
> +       u16             rate_mpu;
> +       u32             interval;
> +       u32             target;
> +
> +       /* resource tracking */
> +       u32             buffer_used;
> +       u32             buffer_max_used;
> +       u32             buffer_limit;
> +       u32             buffer_config_limit;
> +
> +       /* indices for dequeue */
> +       u16             cur_tin;
> +       u16             cur_flow;
> +
> +       struct qdisc_watchdog watchdog;
> +       const u8        *tin_index;
> +       const u8        *tin_order;
> +
> +       /* bandwidth capacity estimate */
> +       u64             last_packet_time;
> +       u64             avg_packet_interval;
> +       u64             avg_window_begin;
> +       u32             avg_window_bytes;
> +       u32             avg_peak_bandwidth;
> +       u64             last_reconfig_time;
> +
> +       /* packet length stats */
> +       u32 avg_netoff;
> +       u16 max_netlen;
> +       u16 max_adjlen;
> +       u16 min_netlen;
> +       u16 min_adjlen;
> +};


So sch_cake doesn't accept normal tc filters? Is this intentional?
If so, why?


> +static u16 quantum_div[CAKE_QUEUES + 1] = {0};
> +
> +#define REC_INV_SQRT_CACHE (16)
> +static u32 cobalt_rec_inv_sqrt_cache[REC_INV_SQRT_CACHE] = {0};
> +
> +/* http://en.wikipedia.org/wiki/Methods_of_computing_square_roots
> + * new_invsqrt = (invsqrt / 2) * (3 - count * invsqrt^2)
> + *
> + * Here, invsqrt is a fixed point number (< 1.0), 32bit mantissa, aka Q0.32
> + */
> +
> +static void cobalt_newton_step(struct cobalt_vars *vars)
> +{
> +       u32 invsqrt = vars->rec_inv_sqrt;
> +       u32 invsqrt2 = ((u64)invsqrt * invsqrt) >> 32;
> +       u64 val = (3LL << 32) - ((u64)vars->count * invsqrt2);
> +
> +       val >>= 2; /* avoid overflow in following multiply */
> +       val = (val * invsqrt) >> (32 - 2 + 1);
> +
> +       vars->rec_inv_sqrt = val;
> +}
> +
> +static void cobalt_invsqrt(struct cobalt_vars *vars)
> +{
> +       if (vars->count < REC_INV_SQRT_CACHE)
> +               vars->rec_inv_sqrt = cobalt_rec_inv_sqrt_cache[vars->count];
> +       else
> +               cobalt_newton_step(vars);
> +}


Looks pretty much duplicated with codel...


> +
> +/* There is a big difference in timing between the accurate values placed in
> + * the cache and the approximations given by a single Newton step for small
> + * count values, particularly when stepping from count 1 to 2 or vice versa.
> + * Above 16, a single Newton step gives sufficient accuracy in either
> + * direction, given the precision stored.
> + *
> + * The magnitude of the error when stepping up to count 2 is such as to give
> + * the value that *should* have been produced at count 4.
> + */
> +
> +static void cobalt_cache_init(void)
> +{
> +       struct cobalt_vars v;
> +
> +       memset(&v, 0, sizeof(v));
> +       v.rec_inv_sqrt = ~0U;
> +       cobalt_rec_inv_sqrt_cache[0] = v.rec_inv_sqrt;
> +
> +       for (v.count = 1; v.count < REC_INV_SQRT_CACHE; v.count++) {
> +               cobalt_newton_step(&v);
> +               cobalt_newton_step(&v);
> +               cobalt_newton_step(&v);
> +               cobalt_newton_step(&v);
> +
> +               cobalt_rec_inv_sqrt_cache[v.count] = v.rec_inv_sqrt;
> +       }
> +}
> +
> +static void cobalt_vars_init(struct cobalt_vars *vars)
> +{
> +       memset(vars, 0, sizeof(*vars));
> +
> +       if (!cobalt_rec_inv_sqrt_cache[0]) {
> +               cobalt_cache_init();
> +               cobalt_rec_inv_sqrt_cache[0] = ~0;
> +       }
> +}
> +
> +/* CoDel control_law is t + interval/sqrt(count)
> + * We maintain in rec_inv_sqrt the reciprocal value of sqrt(count) to avoid
> + * both sqrt() and divide operation.
> + */
> +static cobalt_time_t cobalt_control(cobalt_time_t t,
> +                                   cobalt_time_t interval,
> +                                   u32 rec_inv_sqrt)
> +{
> +       return t + reciprocal_scale(interval, rec_inv_sqrt);
> +}
> +
> +/* Call this when a packet had to be dropped due to queue overflow.  Returns
> + * true if the BLUE state was quiescent before but active after this call.
> + */
> +static bool cobalt_queue_full(struct cobalt_vars *vars,
> +                             struct cobalt_params *p,
> +                             cobalt_time_t now)
> +{
> +       bool up = false;
> +
> +       if ((now - vars->blue_timer) > p->target) {
> +               up = !vars->p_drop;
> +               vars->p_drop += p->p_inc;
> +               if (vars->p_drop < p->p_inc)
> +                       vars->p_drop = ~0;
> +               vars->blue_timer = now;
> +       }
> +       vars->dropping = true;
> +       vars->drop_next = now;
> +       if (!vars->count)
> +               vars->count = 1;
> +
> +       return up;
> +}
> +
> +/* Call this when the queue was serviced but turned out to be empty.  Returns
> + * true if the BLUE state was active before but quiescent after this call.
> + */
> +static bool cobalt_queue_empty(struct cobalt_vars *vars,
> +                              struct cobalt_params *p,
> +                              cobalt_time_t now)
> +{
> +       bool down = false;
> +
> +       if (vars->p_drop && (now - vars->blue_timer) > p->target) {
> +               if (vars->p_drop < p->p_dec)
> +                       vars->p_drop = 0;
> +               else
> +                       vars->p_drop -= p->p_dec;
> +               vars->blue_timer = now;
> +               down = !vars->p_drop;
> +       }
> +       vars->dropping = false;
> +
> +       if (vars->count && (now - vars->drop_next) >= 0) {
> +               vars->count--;
> +               cobalt_invsqrt(vars);
> +               vars->drop_next = cobalt_control(vars->drop_next,
> +                                                p->interval,
> +                                                vars->rec_inv_sqrt);
> +       }
> +
> +       return down;
> +}
> +
> +/* Call this with a freshly dequeued packet for possible congestion marking.
> + * Returns true as an instruction to drop the packet, false for delivery.
> + */
> +static bool cobalt_should_drop(struct cobalt_vars *vars,
> +                              struct cobalt_params *p,
> +                              cobalt_time_t now,
> +                              struct sk_buff *skb)
> +{
> +       bool drop = false;
> +
> +       /* Simplified Codel implementation */
> +       cobalt_tdiff_t sojourn  = now - cobalt_get_enqueue_time(skb);
> +
> +/* The 'schedule' variable records, in its sign, whether 'now' is before or
> + * after 'drop_next'.  This allows 'drop_next' to be updated before the next
> + * scheduling decision is actually branched, without destroying that
> + * information.  Similarly, the first 'schedule' value calculated is preserved
> + * in the boolean 'next_due'.
> + *
> + * As for 'drop_next', we take advantage of the fact that 'interval' is both
> + * the delay between first exceeding 'target' and the first signalling event,
> + * *and* the scaling factor for the signalling frequency.  It's therefore very
> + * natural to use a single mechanism for both purposes, and eliminates a
> + * significant amount of reference Codel's spaghetti code.  To help with this,
> + * both the '0' and '1' entries in the invsqrt cache are 0xFFFFFFFF, as close
> + * as possible to 1.0 in fixed-point.
> + */
> +
> +       cobalt_tdiff_t schedule = now - vars->drop_next;
> +
> +       bool over_target = sojourn > p->target &&
> +                          sojourn > p->mtu_time * 4;
> +       bool next_due    = vars->count && schedule >= 0;
> +
> +       vars->ecn_marked = false;
> +
> +       if (over_target) {
> +               if (!vars->dropping) {
> +                       vars->dropping = true;
> +                       vars->drop_next = cobalt_control(now,
> +                                                        p->interval,
> +                                                        vars->rec_inv_sqrt);
> +               }
> +               if (!vars->count)
> +                       vars->count = 1;
> +       } else if (vars->dropping) {
> +               vars->dropping = false;
> +       }
> +
> +       if (next_due && vars->dropping) {
> +               /* Use ECN mark if possible, otherwise drop */
> +               drop = !(vars->ecn_marked = INET_ECN_set_ce(skb));
> +
> +               vars->count++;
> +               if (!vars->count)
> +                       vars->count--;
> +               cobalt_invsqrt(vars);
> +               vars->drop_next = cobalt_control(vars->drop_next,
> +                                                p->interval,
> +                                                vars->rec_inv_sqrt);
> +               schedule = now - vars->drop_next;
> +       } else {
> +               while (next_due) {
> +                       vars->count--;
> +                       cobalt_invsqrt(vars);
> +                       vars->drop_next = cobalt_control(vars->drop_next,
> +                                                        p->interval,
> +                                                        vars->rec_inv_sqrt);
> +                       schedule = now - vars->drop_next;
> +                       next_due = vars->count && schedule >= 0;
> +               }
> +       }
> +
> +       /* Simple BLUE implementation.  Lack of ECN is deliberate. */
> +       if (vars->p_drop)
> +               drop |= (prandom_u32() < vars->p_drop);
> +
> +       /* Overload the drop_next field as an activity timeout */
> +       if (!vars->count)
> +               vars->drop_next = now + p->interval;
> +       else if (schedule > 0 && !drop)
> +               vars->drop_next = now;
> +
> +       return drop;
> +}
> +
> +/* Cake has several subtle multiple bit settings. In these cases you
> + *  would be matching triple isolate mode as well.
> + */
> +
> +static bool cake_dsrc(int flow_mode)
> +{
> +       return (flow_mode & CAKE_FLOW_DUAL_SRC) == CAKE_FLOW_DUAL_SRC;
> +}
> +
> +static bool cake_ddst(int flow_mode)
> +{
> +       return (flow_mode & CAKE_FLOW_DUAL_DST) == CAKE_FLOW_DUAL_DST;
> +}
> +
> +static u32 cake_hash(struct cake_tin_data *q, const struct sk_buff *skb,
> +                    int flow_mode)
> +{
> +       struct flow_keys keys, host_keys;
> +       u32 flow_hash = 0, srchost_hash, dsthost_hash;
> +       u16 reduced_hash, srchost_idx, dsthost_idx;
> +
> +       if (unlikely(flow_mode == CAKE_FLOW_NONE))
> +               return 0;
> +
> +       skb_flow_dissect_flow_keys(skb, &keys,
> +                                  FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL);
> +
> +       /* flow_hash_from_keys() sorts the addresses by value, so we have
> +        * to preserve their order in a separate data structure to treat
> +        * src and dst host addresses as independently selectable.
> +        */
> +       host_keys = keys;
> +       host_keys.ports.ports     = 0;
> +       host_keys.basic.ip_proto  = 0;
> +       host_keys.keyid.keyid     = 0;
> +       host_keys.tags.flow_label = 0;
> +
> +       switch (host_keys.control.addr_type) {
> +       case FLOW_DISSECTOR_KEY_IPV4_ADDRS:
> +               host_keys.addrs.v4addrs.src = 0;
> +               dsthost_hash = flow_hash_from_keys(&host_keys);
> +               host_keys.addrs.v4addrs.src = keys.addrs.v4addrs.src;
> +               host_keys.addrs.v4addrs.dst = 0;
> +               srchost_hash = flow_hash_from_keys(&host_keys);
> +               break;
> +
> +       case FLOW_DISSECTOR_KEY_IPV6_ADDRS:
> +               memset(&host_keys.addrs.v6addrs.src, 0,
> +                      sizeof(host_keys.addrs.v6addrs.src));
> +               dsthost_hash = flow_hash_from_keys(&host_keys);
> +               host_keys.addrs.v6addrs.src = keys.addrs.v6addrs.src;
> +               memset(&host_keys.addrs.v6addrs.dst, 0,
> +                      sizeof(host_keys.addrs.v6addrs.dst));
> +               srchost_hash = flow_hash_from_keys(&host_keys);
> +               break;
> +
> +       default:
> +               dsthost_hash = 0;
> +               srchost_hash = 0;
> +       }
> +
> +       /* This *must* be after the above switch, since as a
> +        * side-effect it sorts the src and dst addresses.
> +        */
> +       if (flow_mode & CAKE_FLOW_FLOWS)
> +               flow_hash = flow_hash_from_keys(&keys);
> +
> +       if (!(flow_mode & CAKE_FLOW_FLOWS)) {
> +               if (flow_mode & CAKE_FLOW_SRC_IP)
> +                       flow_hash ^= srchost_hash;
> +
> +               if (flow_mode & CAKE_FLOW_DST_IP)
> +                       flow_hash ^= dsthost_hash;
> +       }
> +
> +       reduced_hash = flow_hash % CAKE_QUEUES;
> +
> +       /* set-associative hashing */
> +       /* fast path if no hash collision (direct lookup succeeds) */
> +       if (likely(q->tags[reduced_hash] == flow_hash &&
> +                  q->flows[reduced_hash].set)) {
> +               q->way_directs++;
> +       } else {
> +               u32 inner_hash = reduced_hash % CAKE_SET_WAYS;
> +               u32 outer_hash = reduced_hash - inner_hash;
> +               u32 i, k;
> +               bool allocate_src = false;
> +               bool allocate_dst = false;
> +
> +               /* check if any active queue in the set is reserved for
> +                * this flow.
> +                */
> +               for (i = 0, k = inner_hash; i < CAKE_SET_WAYS;
> +                    i++, k = (k + 1) % CAKE_SET_WAYS) {
> +                       if (q->tags[outer_hash + k] == flow_hash) {
> +                               if (i)
> +                                       q->way_hits++;
> +
> +                               if (!q->flows[outer_hash + k].set) {
> +                                       /* need to increment host refcnts */
> +                                       allocate_src = cake_dsrc(flow_mode);
> +                                       allocate_dst = cake_ddst(flow_mode);
> +                               }
> +
> +                               goto found;
> +                       }
> +               }
> +
> +               /* no queue is reserved for this flow, look for an
> +                * empty one.
> +                */
> +               for (i = 0; i < CAKE_SET_WAYS;
> +                        i++, k = (k + 1) % CAKE_SET_WAYS) {
> +                       if (!q->flows[outer_hash + k].set) {
> +                               q->way_misses++;
> +                               allocate_src = cake_dsrc(flow_mode);
> +                               allocate_dst = cake_ddst(flow_mode);
> +                               goto found;
> +                       }
> +               }
> +
> +               /* With no empty queues, default to the original
> +                * queue, accept the collision, update the host tags.
> +                */
> +               q->way_collisions++;
> +               q->hosts[q->flows[reduced_hash].srchost].srchost_refcnt--;
> +               q->hosts[q->flows[reduced_hash].dsthost].dsthost_refcnt--;
> +               allocate_src = cake_dsrc(flow_mode);
> +               allocate_dst = cake_ddst(flow_mode);
> +found:
> +               /* reserve queue for future packets in same flow */
> +               reduced_hash = outer_hash + k;
> +               q->tags[reduced_hash] = flow_hash;
> +
> +               if (allocate_src) {
> +                       srchost_idx = srchost_hash % CAKE_QUEUES;
> +                       inner_hash = srchost_idx % CAKE_SET_WAYS;
> +                       outer_hash = srchost_idx - inner_hash;
> +                       for (i = 0, k = inner_hash; i < CAKE_SET_WAYS;
> +                               i++, k = (k + 1) % CAKE_SET_WAYS) {
> +                               if (q->hosts[outer_hash + k].srchost_tag ==
> +                                   srchost_hash)
> +                                       goto found_src;
> +                       }
> +                       for (i = 0; i < CAKE_SET_WAYS;
> +                               i++, k = (k + 1) % CAKE_SET_WAYS) {
> +                               if (!q->hosts[outer_hash + k].srchost_refcnt)
> +                                       break;
> +                       }
> +                       q->hosts[outer_hash + k].srchost_tag = srchost_hash;
> +found_src:
> +                       srchost_idx = outer_hash + k;
> +                       q->hosts[srchost_idx].srchost_refcnt++;
> +                       q->flows[reduced_hash].srchost = srchost_idx;
> +               }
> +
> +               if (allocate_dst) {
> +                       dsthost_idx = dsthost_hash % CAKE_QUEUES;
> +                       inner_hash = dsthost_idx % CAKE_SET_WAYS;
> +                       outer_hash = dsthost_idx - inner_hash;
> +                       for (i = 0, k = inner_hash; i < CAKE_SET_WAYS;
> +                            i++, k = (k + 1) % CAKE_SET_WAYS) {
> +                               if (q->hosts[outer_hash + k].dsthost_tag ==
> +                                   dsthost_hash)
> +                                       goto found_dst;
> +                       }
> +                       for (i = 0; i < CAKE_SET_WAYS;
> +                            i++, k = (k + 1) % CAKE_SET_WAYS) {
> +                               if (!q->hosts[outer_hash + k].dsthost_refcnt)
> +                                       break;
> +                       }
> +                       q->hosts[outer_hash + k].dsthost_tag = dsthost_hash;
> +found_dst:
> +                       dsthost_idx = outer_hash + k;
> +                       q->hosts[dsthost_idx].dsthost_refcnt++;
> +                       q->flows[reduced_hash].dsthost = dsthost_idx;
> +               }
> +       }
> +
> +       return reduced_hash;
> +}
> +
> +/* helper functions : might be changed when/if skb use a standard list_head */
> +/* remove one skb from head of slot queue */
> +
> +static struct sk_buff *dequeue_head(struct cake_flow *flow)
> +{
> +       struct sk_buff *skb = flow->head;
> +
> +       if (skb) {
> +               flow->head = skb->next;
> +               skb->next = NULL;
> +
> +               if (skb == flow->ackcheck)
> +                       flow->ackcheck = NULL;
> +       }
> +
> +       return skb;
> +}
> +
> +/* add skb to flow queue (tail add) */
> +
> +static void flow_queue_add(struct cake_flow *flow, struct sk_buff *skb)
> +{
> +       if (!flow->head)
> +               flow->head = skb;
> +       else
> +               flow->tail->next = skb;
> +       flow->tail = skb;
> +       skb->next = NULL;
> +}
> +
> +static cobalt_time_t cake_ewma(cobalt_time_t avg, cobalt_time_t sample,
> +                                     u32 shift)
> +{
> +       avg -= avg >> shift;
> +       avg += sample >> shift;
> +       return avg;
> +}
> +
> +static void cake_heap_swap(struct cake_sched_data *q, u16 i, u16 j)
> +{
> +       struct cake_heap_entry ii = q->overflow_heap[i];
> +       struct cake_heap_entry jj = q->overflow_heap[j];
> +
> +       q->overflow_heap[i] = jj;
> +       q->overflow_heap[j] = ii;
> +
> +       q->tins[ii.t].overflow_idx[ii.b] = j;
> +       q->tins[jj.t].overflow_idx[jj.b] = i;
> +}
> +
> +static u32 cake_heap_get_backlog(const struct cake_sched_data *q, u16 i)
> +{
> +       struct cake_heap_entry ii = q->overflow_heap[i];
> +
> +       return q->tins[ii.t].backlogs[ii.b];
> +}
> +
> +static void cake_heapify(struct cake_sched_data *q, u16 i)
> +{
> +       static const u32 a = CAKE_MAX_TINS * CAKE_QUEUES;
> +       u32 m = i;
> +       u32 mb = cake_heap_get_backlog(q, m);
> +
> +       while (m < a) {
> +               u32 l = m + m + 1;
> +               u32 r = l + 1;
> +
> +               if (l < a) {
> +                       u32 lb = cake_heap_get_backlog(q, l);
> +
> +                       if (lb > mb) {
> +                               m  = l;
> +                               mb = lb;
> +                       }
> +               }
> +
> +               if (r < a) {
> +                       u32 rb = cake_heap_get_backlog(q, r);
> +
> +                       if (rb > mb) {
> +                               m  = r;
> +                               mb = rb;
> +                       }
> +               }
> +
> +               if (m != i) {
> +                       cake_heap_swap(q, i, m);
> +                       i = m;
> +               } else {
> +                       break;
> +               }
> +       }
> +}
> +
> +static void cake_heapify_up(struct cake_sched_data *q, u16 i)
> +{
> +       while (i > 0 && i < CAKE_MAX_TINS * CAKE_QUEUES) {
> +               u16 p = (i - 1) >> 1;
> +               u32 ib = cake_heap_get_backlog(q, i);
> +               u32 pb = cake_heap_get_backlog(q, p);
> +
> +               if (ib > pb) {
> +                       cake_heap_swap(q, i, p);
> +                       i = p;
> +               } else {
> +                       break;
> +               }
> +       }
> +}
> +
> +static int cake_advance_shaper(struct cake_sched_data *q,
> +                              struct cake_tin_data *b,
> +                              struct sk_buff *skb,
> +                              u64 now, bool drop)
> +{
> +       u32 len = qdisc_pkt_len(skb);
> +
> +       /* charge packet bandwidth to this tin
> +        * and to the global shaper.
> +        */
> +       if (q->rate_ns) {
> +               s64 tdiff1 = b->tin_time_next_packet - now;
> +               s64 tdiff2 = (len * (u64)b->tin_rate_ns) >> b->tin_rate_shft;
> +               s64 tdiff3 = (len * (u64)q->rate_ns) >> q->rate_shft;
> +               s64 tdiff4 = tdiff3 + (tdiff3 >> 1);
> +
> +               if (tdiff1 < 0)
> +                       b->tin_time_next_packet += tdiff2;
> +               else if (tdiff1 < tdiff2)
> +                       b->tin_time_next_packet = now + tdiff2;
> +
> +               q->time_next_packet += tdiff3;
> +               if (!drop)
> +                       q->failsafe_next_packet += tdiff4;
> +       }
> +       return len;
> +}
> +
> +static unsigned int cake_drop(struct Qdisc *sch, struct sk_buff **to_free)
> +{
> +       struct cake_sched_data *q = qdisc_priv(sch);
> +       struct sk_buff *skb;
> +       u32 idx = 0, tin = 0, len;
> +       struct cake_tin_data *b;
> +       struct cake_flow *flow;
> +       struct cake_heap_entry qq;
> +       u64 now = cobalt_get_time();
> +
> +       if (!q->overflow_timeout) {
> +               int i;
> +               /* Build fresh max-heap */
> +               for (i = CAKE_MAX_TINS * CAKE_QUEUES / 2; i >= 0; i--)
> +                       cake_heapify(q, i);
> +       }
> +       q->overflow_timeout = 65535;
> +
> +       /* select longest queue for pruning */
> +       qq  = q->overflow_heap[0];
> +       tin = qq.t;
> +       idx = qq.b;
> +
> +       b = &q->tins[tin];
> +       flow = &b->flows[idx];
> +       skb = dequeue_head(flow);
> +       if (unlikely(!skb)) {
> +               /* heap has gone wrong, rebuild it next time */
> +               q->overflow_timeout = 0;
> +               return idx + (tin << 16);
> +       }
> +
> +       if (cobalt_queue_full(&flow->cvars, &b->cparams, now))
> +               b->unresponsive_flow_count++;
> +
> +       len = qdisc_pkt_len(skb);
> +       q->buffer_used      -= skb->truesize;
> +       b->backlogs[idx]    -= len;
> +       b->tin_backlog      -= len;
> +       sch->qstats.backlog -= len;
> +       qdisc_tree_reduce_backlog(sch, 1, len);
> +
> +       b->tin_dropped++;
> +       sch->qstats.drops++;
> +
> +       __qdisc_drop(skb, to_free);
> +       sch->q.qlen--;
> +
> +       cake_heapify(q, 0);
> +
> +       return idx + (tin << 16);
> +}
> +
> +static void cake_reconfigure(struct Qdisc *sch);
> +
> +static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch,
> +                       struct sk_buff **to_free)
> +{
> +       struct cake_sched_data *q = qdisc_priv(sch);
> +       u32 idx, tin;
> +       struct cake_tin_data *b;
> +       struct cake_flow *flow;
> +       /* signed len to handle corner case filtered ACK larger than trigger */
> +       int len = qdisc_pkt_len(skb);
> +       u64 now = cobalt_get_time();
> +
> +       tin = 0;
> +       b = &q->tins[tin];
> +
> +       /* choose flow to insert into */
> +       idx = cake_hash(b, skb, q->flow_mode);
> +       flow = &b->flows[idx];
> +
> +       /* ensure shaper state isn't stale */
> +       if (!b->tin_backlog) {
> +               if (b->tin_time_next_packet < now)
> +                       b->tin_time_next_packet = now;
> +
> +               if (!sch->q.qlen) {
> +                       if (q->time_next_packet < now) {
> +                               q->failsafe_next_packet = now;
> +                               q->time_next_packet = now;
> +                       } else if (q->time_next_packet > now &&
> +                                  q->failsafe_next_packet > now) {
> +                               u64 next = min(q->time_next_packet,
> +                                              q->failsafe_next_packet);
> +                               sch->qstats.overlimits++;
> +                               qdisc_watchdog_schedule_ns(&q->watchdog, next);
> +                       }
> +               }
> +       }
> +
> +       if (unlikely(len > b->max_skblen))
> +               b->max_skblen = len;
> +
> +       cobalt_set_enqueue_time(skb, now);
> +       flow_queue_add(flow, skb);
> +
> +       sch->q.qlen++;
> +       q->buffer_used      += skb->truesize;
> +
> +       /* stats */
> +       b->packets++;
> +       b->bytes            += len;
> +       b->backlogs[idx]    += len;
> +       b->tin_backlog      += len;
> +       sch->qstats.backlog += len;
> +       q->avg_window_bytes += len;
> +
> +       if (q->overflow_timeout)
> +               cake_heapify_up(q, b->overflow_idx[idx]);
> +
> +       /* incoming bandwidth capacity estimate */
> +       q->avg_window_bytes = 0;
> +       q->last_packet_time = now;
> +
> +       /* flowchain */
> +       if (!flow->set || flow->set == CAKE_SET_DECAYING) {
> +               struct cake_host *srchost = &b->hosts[flow->srchost];
> +               struct cake_host *dsthost = &b->hosts[flow->dsthost];
> +               u16 host_load = 1;
> +
> +               if (!flow->set) {
> +                       list_add_tail(&flow->flowchain, &b->new_flows);
> +               } else {
> +                       b->decaying_flow_count--;
> +                       list_move_tail(&flow->flowchain, &b->new_flows);
> +               }
> +               flow->set = CAKE_SET_SPARSE;
> +               b->sparse_flow_count++;
> +
> +               if (cake_dsrc(q->flow_mode))
> +                       host_load = max(host_load, srchost->srchost_refcnt);
> +
> +               if (cake_ddst(q->flow_mode))
> +                       host_load = max(host_load, dsthost->dsthost_refcnt);
> +
> +               flow->deficit = (b->flow_quantum *
> +                                quantum_div[host_load]) >> 16;
> +       } else if (flow->set == CAKE_SET_SPARSE_WAIT) {
> +               /* this flow was empty, accounted as a sparse flow, but actually
> +                * in the bulk rotation.
> +                */
> +               flow->set = CAKE_SET_BULK;
> +               b->sparse_flow_count--;
> +               b->bulk_flow_count++;
> +       }
> +
> +       if (q->buffer_used > q->buffer_max_used)
> +               q->buffer_max_used = q->buffer_used;
> +
> +       if (q->buffer_used > q->buffer_limit) {
> +               u32 dropped = 0;
> +
> +               while (q->buffer_used > q->buffer_limit) {
> +                       dropped++;
> +                       cake_drop(sch, to_free);
> +               }
> +               b->drop_overlimit += dropped;
> +       }
> +       return NET_XMIT_SUCCESS;
> +}
> +
> +static struct sk_buff *cake_dequeue_one(struct Qdisc *sch)
> +{
> +       struct cake_sched_data *q = qdisc_priv(sch);
> +       struct cake_tin_data *b = &q->tins[q->cur_tin];
> +       struct cake_flow *flow = &b->flows[q->cur_flow];
> +       struct sk_buff *skb = NULL;
> +       u32 len;
> +
> +       if (flow->head) {
> +               skb = dequeue_head(flow);
> +               len = qdisc_pkt_len(skb);
> +               b->backlogs[q->cur_flow] -= len;
> +               b->tin_backlog           -= len;
> +               sch->qstats.backlog      -= len;
> +               q->buffer_used           -= skb->truesize;
> +               sch->q.qlen--;
> +
> +               if (q->overflow_timeout)
> +                       cake_heapify(q, b->overflow_idx[q->cur_flow]);
> +       }
> +       return skb;
> +}
> +
> +/* Discard leftover packets from a tin no longer in use. */
> +static void cake_clear_tin(struct Qdisc *sch, u16 tin)
> +{
> +       struct cake_sched_data *q = qdisc_priv(sch);
> +       struct sk_buff *skb;
> +
> +       q->cur_tin = tin;
> +       for (q->cur_flow = 0; q->cur_flow < CAKE_QUEUES; q->cur_flow++)
> +               while (!!(skb = cake_dequeue_one(sch)))
> +                       kfree_skb(skb);
> +}
> +
> +static struct sk_buff *cake_dequeue(struct Qdisc *sch)
> +{
> +       struct cake_sched_data *q = qdisc_priv(sch);
> +       struct sk_buff *skb;
> +       struct cake_tin_data *b = &q->tins[q->cur_tin];
> +       struct cake_flow *flow;
> +       struct cake_host *srchost, *dsthost;
> +       struct list_head *head;
> +       u32 len;
> +       u16 host_load;
> +       cobalt_time_t now = ktime_get_ns();
> +       cobalt_time_t delay;
> +       bool first_flow = true;
> +
> +begin:
> +       if (!sch->q.qlen)
> +               return NULL;
> +
> +       /* global hard shaper */
> +       if (q->time_next_packet > now && q->failsafe_next_packet > now) {
> +               u64 next = min(q->time_next_packet, q->failsafe_next_packet);
> +
> +               sch->qstats.overlimits++;
> +               qdisc_watchdog_schedule_ns(&q->watchdog, next);
> +               return NULL;
> +       }
> +
> +       /* Choose a class to work on. */
> +       if (!q->rate_ns) {
> +               /* In unlimited mode, can't rely on shaper timings, just balance
> +                * with DRR
> +                */
> +               while (b->tin_deficit < 0 ||
> +                      !(b->sparse_flow_count + b->bulk_flow_count)) {
> +                       if (b->tin_deficit <= 0)
> +                               b->tin_deficit += b->tin_quantum_band;
> +
> +                       q->cur_tin++;
> +                       b++;
> +                       if (q->cur_tin >= q->tin_cnt) {
> +                               q->cur_tin = 0;
> +                               b = q->tins;
> +                       }
> +               }
> +       } else {
> +               /* In shaped mode, choose:
> +                * - Highest-priority tin with queue and meeting schedule, or
> +                * - The earliest-scheduled tin with queue.
> +                */
> +               int tin, best_tin = 0;
> +               s64 best_time = 0xFFFFFFFFFFFFUL;
> +
> +               for (tin = 0; tin < q->tin_cnt; tin++) {
> +                       b = q->tins + tin;
> +                       if ((b->sparse_flow_count + b->bulk_flow_count) > 0) {
> +                               s64 tdiff = b->tin_time_next_packet - now;
> +
> +                               if (tdiff <= 0 || tdiff <= best_time) {
> +                                       best_time = tdiff;
> +                                       best_tin = tin;
> +                               }
> +                       }
> +               }
> +
> +               q->cur_tin = best_tin;
> +               b = q->tins + best_tin;
> +       }
> +
> +retry:
> +       /* service this class */
> +       head = &b->decaying_flows;
> +       if (!first_flow || list_empty(head)) {
> +               head = &b->new_flows;
> +               if (list_empty(head)) {
> +                       head = &b->old_flows;
> +                       if (unlikely(list_empty(head))) {
> +                               head = &b->decaying_flows;
> +                               if (unlikely(list_empty(head)))
> +                                       goto begin;
> +                       }
> +               }
> +       }
> +       flow = list_first_entry(head, struct cake_flow, flowchain);
> +       q->cur_flow = flow - b->flows;
> +       first_flow = false;
> +
> +       /* triple isolation (modified DRR++) */
> +       srchost = &b->hosts[flow->srchost];
> +       dsthost = &b->hosts[flow->dsthost];
> +       host_load = 1;
> +
> +       if (cake_dsrc(q->flow_mode))
> +               host_load = max(host_load, srchost->srchost_refcnt);
> +
> +       if (cake_ddst(q->flow_mode))
> +               host_load = max(host_load, dsthost->dsthost_refcnt);
> +
> +       WARN_ON(host_load > CAKE_QUEUES);
> +
> +       /* flow isolation (DRR++) */
> +       if (flow->deficit <= 0) {
> +               /* The shifted prandom_u32() is a way to apply dithering to
> +                * avoid accumulating roundoff errors
> +                */
> +               flow->deficit += (b->flow_quantum * quantum_div[host_load] +
> +                                 (prandom_u32() >> 16)) >> 16;
> +               list_move_tail(&flow->flowchain, &b->old_flows);
> +
> +               /* Keep all flows with deficits out of the sparse and decaying
> +                * rotations.  No non-empty flow can go into the decaying
> +                * rotation, so they can't get deficits
> +                */
> +               if (flow->set == CAKE_SET_SPARSE) {
> +                       if (flow->head) {
> +                               b->sparse_flow_count--;
> +                               b->bulk_flow_count++;
> +                               flow->set = CAKE_SET_BULK;
> +                       } else {
> +                               /* we've moved it to the bulk rotation for
> +                                * correct deficit accounting but we still want
> +                                * to count it as a sparse flow, not a bulk one.
> +                                */
> +                               flow->set = CAKE_SET_SPARSE_WAIT;
> +                       }
> +               }
> +               goto retry;
> +       }
> +
> +       /* Retrieve a packet via the AQM */
> +       while (1) {
> +               skb = cake_dequeue_one(sch);
> +               if (!skb) {
> +                       /* this queue was actually empty */
> +                       if (cobalt_queue_empty(&flow->cvars, &b->cparams, now))
> +                               b->unresponsive_flow_count--;
> +
> +                       if (flow->cvars.p_drop || flow->cvars.count ||
> +                           now < flow->cvars.drop_next) {
> +                               /* keep in the flowchain until the state has
> +                                * decayed to rest
> +                                */
> +                               list_move_tail(&flow->flowchain,
> +                                              &b->decaying_flows);
> +                               if (flow->set == CAKE_SET_BULK) {
> +                                       b->bulk_flow_count--;
> +                                       b->decaying_flow_count++;
> +                               } else if (flow->set == CAKE_SET_SPARSE ||
> +                                          flow->set == CAKE_SET_SPARSE_WAIT) {
> +                                       b->sparse_flow_count--;
> +                                       b->decaying_flow_count++;
> +                               }
> +                               flow->set = CAKE_SET_DECAYING;
> +                       } else {
> +                               /* remove empty queue from the flowchain */
> +                               list_del_init(&flow->flowchain);
> +                               if (flow->set == CAKE_SET_SPARSE ||
> +                                   flow->set == CAKE_SET_SPARSE_WAIT)
> +                                       b->sparse_flow_count--;
> +                               else if (flow->set == CAKE_SET_BULK)
> +                                       b->bulk_flow_count--;
> +                               else
> +                                       b->decaying_flow_count--;
> +
> +                               flow->set = CAKE_SET_NONE;
> +                               srchost->srchost_refcnt--;
> +                               dsthost->dsthost_refcnt--;
> +                       }
> +                       goto begin;
> +               }
> +
> +               /* Last packet in queue may be marked, shouldn't be dropped */
> +               if (!cobalt_should_drop(&flow->cvars, &b->cparams, now, skb) ||
> +                   !flow->head)
> +                       break;
> +
> +               b->tin_dropped++;
> +               qdisc_tree_reduce_backlog(sch, 1, qdisc_pkt_len(skb));
> +               qdisc_qstats_drop(sch);
> +               kfree_skb(skb);
> +       }
> +
> +       b->tin_ecn_mark += !!flow->cvars.ecn_marked;
> +       qdisc_bstats_update(sch, skb);
> +
> +       /* collect delay stats */
> +       delay = now - cobalt_get_enqueue_time(skb);
> +       b->avge_delay = cake_ewma(b->avge_delay, delay, 8);
> +       b->peak_delay = cake_ewma(b->peak_delay, delay,
> +                                 delay > b->peak_delay ? 2 : 8);
> +       b->base_delay = cake_ewma(b->base_delay, delay,
> +                                 delay < b->base_delay ? 2 : 8);
> +
> +       len = cake_advance_shaper(q, b, skb, now, false);
> +       flow->deficit -= len;
> +       b->tin_deficit -= len;
> +
> +       if (q->time_next_packet > now && sch->q.qlen) {
> +               u64 next = min(q->time_next_packet, q->failsafe_next_packet);
> +
> +               qdisc_watchdog_schedule_ns(&q->watchdog, next);
> +       } else if (!sch->q.qlen) {
> +               int i;
> +
> +               for (i = 0; i < q->tin_cnt; i++) {
> +                       if (q->tins[i].decaying_flow_count) {
> +                               u64 next = now + q->tins[i].cparams.target;
> +
> +                               qdisc_watchdog_schedule_ns(&q->watchdog, next);
> +                               break;
> +                       }
> +               }
> +       }
> +
> +       if (q->overflow_timeout)
> +               q->overflow_timeout--;
> +
> +       return skb;
> +}
> +
> +static void cake_reset(struct Qdisc *sch)
> +{
> +       u32 c;
> +
> +       for (c = 0; c < CAKE_MAX_TINS; c++)
> +               cake_clear_tin(sch, c);
> +}
> +
> +static const struct nla_policy cake_policy[TCA_CAKE_MAX + 1] = {
> +       [TCA_CAKE_BASE_RATE]     = { .type = NLA_U32 },
> +       [TCA_CAKE_DIFFSERV_MODE] = { .type = NLA_U32 },
> +       [TCA_CAKE_ATM]           = { .type = NLA_U32 },
> +       [TCA_CAKE_FLOW_MODE]     = { .type = NLA_U32 },
> +       [TCA_CAKE_OVERHEAD]      = { .type = NLA_S32 },
> +       [TCA_CAKE_RTT]           = { .type = NLA_U32 },
> +       [TCA_CAKE_TARGET]        = { .type = NLA_U32 },
> +       [TCA_CAKE_AUTORATE]      = { .type = NLA_U32 },
> +       [TCA_CAKE_MEMORY]        = { .type = NLA_U32 },
> +       [TCA_CAKE_NAT]           = { .type = NLA_U32 },
> +       [TCA_CAKE_RAW]           = { .type = NLA_U32 },
> +       [TCA_CAKE_WASH]          = { .type = NLA_U32 },
> +       [TCA_CAKE_MPU]           = { .type = NLA_U32 },
> +       [TCA_CAKE_INGRESS]       = { .type = NLA_U32 },
> +       [TCA_CAKE_ACK_FILTER]    = { .type = NLA_U32 },
> +};
> +
> +static void cake_set_rate(struct cake_tin_data *b, u64 rate, u32 mtu,
> +                         cobalt_time_t ns_target, cobalt_time_t rtt_est_ns)
> +{
> +       /* convert byte-rate into time-per-byte
> +        * so it will always unwedge in reasonable time.
> +        */
> +       static const u64 MIN_RATE = 64;
> +       u64 rate_ns = 0;
> +       u8  rate_shft = 0;
> +       cobalt_time_t byte_target_ns;
> +       u32 byte_target = mtu;
> +
> +       b->flow_quantum = 1514;
> +       if (rate) {
> +               b->flow_quantum = max(min(rate >> 12, 1514ULL), 300ULL);
> +               rate_shft = 32;
> +               rate_ns = ((u64)NSEC_PER_SEC) << rate_shft;
> +               do_div(rate_ns, max(MIN_RATE, rate));
> +               while (!!(rate_ns >> 32)) {
> +                       rate_ns >>= 1;
> +                       rate_shft--;
> +               }
> +       } /* else unlimited, ie. zero delay */
> +
> +       b->tin_rate_bps  = rate;
> +       b->tin_rate_ns   = rate_ns;
> +       b->tin_rate_shft = rate_shft;
> +
> +       byte_target_ns = (byte_target * rate_ns) >> rate_shft;
> +
> +       b->cparams.target = max((byte_target_ns * 3) / 2, ns_target);
> +       b->cparams.interval = max(rtt_est_ns +
> +                                    b->cparams.target - ns_target,
> +                                    b->cparams.target * 2);
> +       b->cparams.mtu_time = byte_target_ns;
> +       b->cparams.p_inc = 1 << 24; /* 1/256 */
> +       b->cparams.p_dec = 1 << 20; /* 1/4096 */
> +}
> +
> +static void cake_reconfigure(struct Qdisc *sch)
> +{
> +       struct cake_sched_data *q = qdisc_priv(sch);
> +       struct cake_tin_data *b = &q->tins[0];
> +       int c, ft = 0;
> +
> +       q->tin_cnt = 1;
> +       cake_set_rate(b, q->rate_bps, psched_mtu(qdisc_dev(sch)),
> +                     US2TIME(q->target), US2TIME(q->interval));
> +       b->tin_quantum_band = 65535;
> +       b->tin_quantum_prio = 65535;
> +
> +       for (c = q->tin_cnt; c < CAKE_MAX_TINS; c++) {
> +               cake_clear_tin(sch, c);
> +               q->tins[c].cparams.mtu_time = q->tins[ft].cparams.mtu_time;
> +       }
> +
> +       q->rate_ns   = q->tins[ft].tin_rate_ns;
> +       q->rate_shft = q->tins[ft].tin_rate_shft;
> +
> +       if (q->buffer_config_limit) {
> +               q->buffer_limit = q->buffer_config_limit;
> +       } else if (q->rate_bps) {
> +               u64 t = (u64)q->rate_bps * q->interval;
> +
> +               do_div(t, USEC_PER_SEC / 4);
> +               q->buffer_limit = max_t(u32, t, 4U << 20);
> +       } else {
> +               q->buffer_limit = ~0;
> +       }
> +
> +       sch->flags &= ~TCQ_F_CAN_BYPASS;
> +
> +       q->buffer_limit = min(q->buffer_limit,
> +                             max(sch->limit * psched_mtu(qdisc_dev(sch)),
> +                                 q->buffer_config_limit));
> +}
> +
> +static int cake_change(struct Qdisc *sch, struct nlattr *opt,
> +                      struct netlink_ext_ack *extack)
> +{
> +       struct cake_sched_data *q = qdisc_priv(sch);
> +       struct nlattr *tb[TCA_CAKE_MAX + 1];
> +       int err;
> +
> +       if (!opt)
> +               return -EINVAL;
> +
> +       err = nla_parse_nested(tb, TCA_CAKE_MAX, opt, cake_policy, extack);
> +       if (err < 0)
> +               return err;
> +
> +       if (tb[TCA_CAKE_BASE_RATE])
> +               q->rate_bps = nla_get_u32(tb[TCA_CAKE_BASE_RATE]);
> +
> +       if (tb[TCA_CAKE_FLOW_MODE])
> +               q->flow_mode = (nla_get_u32(tb[TCA_CAKE_FLOW_MODE]) &
> +                               CAKE_FLOW_MASK);
> +
> +       if (tb[TCA_CAKE_RTT]) {
> +               q->interval = nla_get_u32(tb[TCA_CAKE_RTT]);
> +
> +               if (!q->interval)
> +                       q->interval = 1;
> +       }
> +
> +       if (tb[TCA_CAKE_TARGET]) {
> +               q->target = nla_get_u32(tb[TCA_CAKE_TARGET]);
> +
> +               if (!q->target)
> +                       q->target = 1;
> +       }
> +
> +       if (tb[TCA_CAKE_MEMORY])
> +               q->buffer_config_limit = nla_get_u32(tb[TCA_CAKE_MEMORY]);
> +
> +       if (q->tins) {
> +               sch_tree_lock(sch);
> +               cake_reconfigure(sch);
> +               sch_tree_unlock(sch);
> +       }
> +
> +       return 0;
> +}
> +
> +static void cake_free(void *addr)
> +{
> +       if (addr)
> +               kvfree(addr);
> +}


Are you sure you have to check NULL for kvfree()?



> +
> +static void cake_destroy(struct Qdisc *sch)
> +{
> +       struct cake_sched_data *q = qdisc_priv(sch);
> +
> +       qdisc_watchdog_cancel(&q->watchdog);
> +
> +       if (q->tins)
> +               cake_free(q->tins);


Duplicated NULL check.


> +}
> +
> +static int cake_init(struct Qdisc *sch, struct nlattr *opt,
> +                    struct netlink_ext_ack *extack)
> +{
> +       struct cake_sched_data *q = qdisc_priv(sch);
> +       int i, j;
> +
> +       sch->limit = 10240;
> +       q->tin_mode = CAKE_DIFFSERV_BESTEFFORT;
> +       q->flow_mode  = CAKE_FLOW_TRIPLE;
> +
> +       q->rate_bps = 0; /* unlimited by default */
> +
> +       q->interval = 100000; /* 100ms default */
> +       q->target   =   5000; /* 5ms: codel RFC argues
> +                              * for 5 to 10% of interval
> +                              */
> +
> +       q->cur_tin = 0;
> +       q->cur_flow  = 0;
> +
> +       if (opt) {
> +               int err = cake_change(sch, opt, extack);
> +
> +               if (err)
> +                       return err;


Not sure if you really want to reallocate q->tines below for this
case.


> +       }
> +
> +       qdisc_watchdog_init(&q->watchdog, sch);
> +
> +       quantum_div[0] = ~0;
> +       for (i = 1; i <= CAKE_QUEUES; i++)
> +               quantum_div[i] = 65535 / i;
> +
> +       q->tins = kvzalloc(CAKE_MAX_TINS * sizeof(struct cake_tin_data),
> +                          GFP_KERNEL | __GFP_NOWARN);



Why __GFP_NOWARN?



> +       if (!q->tins)
> +               goto nomem;
> +
> +       for (i = 0; i < CAKE_MAX_TINS; i++) {
> +               struct cake_tin_data *b = q->tins + i;
> +
> +               INIT_LIST_HEAD(&b->new_flows);
> +               INIT_LIST_HEAD(&b->old_flows);
> +               INIT_LIST_HEAD(&b->decaying_flows);
> +               b->sparse_flow_count = 0;
> +               b->bulk_flow_count = 0;
> +               b->decaying_flow_count = 0;
> +
> +               for (j = 0; j < CAKE_QUEUES; j++) {
> +                       struct cake_flow *flow = b->flows + j;
> +                       u32 k = j * CAKE_MAX_TINS + i;
> +
> +                       INIT_LIST_HEAD(&flow->flowchain);
> +                       cobalt_vars_init(&flow->cvars);
> +
> +                       q->overflow_heap[k].t = i;
> +                       q->overflow_heap[k].b = j;
> +                       b->overflow_idx[j] = k;
> +               }
> +       }
> +
> +       cake_reconfigure(sch);
> +       q->avg_peak_bandwidth = q->rate_bps;
> +       q->min_netlen = ~0;
> +       q->min_adjlen = ~0;
> +       return 0;
> +
> +nomem:
> +       cake_destroy(sch);
> +       return -ENOMEM;
> +}
> +
> +static int cake_dump(struct Qdisc *sch, struct sk_buff *skb)
> +{
> +       struct cake_sched_data *q = qdisc_priv(sch);
> +       struct nlattr *opts;
> +
> +       opts = nla_nest_start(skb, TCA_OPTIONS);
> +       if (!opts)
> +               goto nla_put_failure;
> +
> +       if (nla_put_u32(skb, TCA_CAKE_BASE_RATE, q->rate_bps))
> +               goto nla_put_failure;
> +
> +       if (nla_put_u32(skb, TCA_CAKE_DIFFSERV_MODE, q->tin_mode))
> +               goto nla_put_failure;
> +
> +       if (nla_put_u32(skb, TCA_CAKE_ATM, q->atm_mode))
> +               goto nla_put_failure;
> +
> +       if (nla_put_u32(skb, TCA_CAKE_FLOW_MODE,
> +                       q->flow_mode & CAKE_FLOW_MASK))
> +               goto nla_put_failure;
> +
> +       if (nla_put_u32(skb, TCA_CAKE_NAT,
> +                       !!(q->flow_mode & CAKE_FLOW_NAT_FLAG)))
> +               goto nla_put_failure;
> +
> +       if (nla_put_u32(skb, TCA_CAKE_SPLIT_GSO,
> +                       !!(q->rate_flags & CAKE_FLAG_SPLIT_GSO)))
> +               goto nla_put_failure;
> +
> +       if (nla_put_u32(skb, TCA_CAKE_WASH,
> +                       !!(q->rate_flags & CAKE_FLAG_WASH)))
> +               goto nla_put_failure;
> +
> +       if (nla_put_u32(skb, TCA_CAKE_OVERHEAD, q->rate_overhead))
> +               goto nla_put_failure;
> +
> +       if (nla_put_u32(skb, TCA_CAKE_MPU, q->rate_mpu))
> +               goto nla_put_failure;
> +
> +       if (!(q->rate_flags & CAKE_FLAG_OVERHEAD))
> +               if (nla_put_u32(skb, TCA_CAKE_RAW, 0))
> +                       goto nla_put_failure;
> +
> +       if (nla_put_u32(skb, TCA_CAKE_RTT, q->interval))
> +               goto nla_put_failure;
> +
> +       if (nla_put_u32(skb, TCA_CAKE_TARGET, q->target))
> +               goto nla_put_failure;
> +
> +       if (nla_put_u32(skb, TCA_CAKE_AUTORATE,
> +                       !!(q->rate_flags & CAKE_FLAG_AUTORATE_INGRESS)))
> +               goto nla_put_failure;
> +
> +       if (nla_put_u32(skb, TCA_CAKE_INGRESS,
> +                       !!(q->rate_flags & CAKE_FLAG_INGRESS)))
> +               goto nla_put_failure;


Is this used by a following patch? If so, please move it there.



> +
> +       if (nla_put_u32(skb, TCA_CAKE_ACK_FILTER, q->ack_filter))
> +               goto nla_put_failure;


Ditto.



> +
> +       if (nla_put_u32(skb, TCA_CAKE_MEMORY, q->buffer_config_limit))
> +               goto nla_put_failure;
> +
> +       return nla_nest_end(skb, opts);
> +
> +nla_put_failure:
> +       return -1;
> +}
> +
> +static int cake_dump_stats(struct Qdisc *sch, struct gnet_dump *d)
> +{
> +       struct cake_sched_data *q = qdisc_priv(sch);
> +       struct nlattr *stats = nla_nest_start(d->skb, TCA_STATS_APP);
> +       struct nlattr *tstats, *ts;
> +       int i;
> +
> +       if (!stats)
> +               return -1;
> +
> +#define PUT_STAT_U32(attr, data) do {                                 \
> +               if (nla_put_u32(d->skb, TCA_CAKE_STATS_ ## attr, data)) \
> +                       goto nla_put_failure;                          \
> +       } while (0)
> +
> +       PUT_STAT_U32(CAPACITY_ESTIMATE, q->avg_peak_bandwidth);
> +       PUT_STAT_U32(MEMORY_LIMIT, q->buffer_limit);
> +       PUT_STAT_U32(MEMORY_USED, q->buffer_max_used);
> +       PUT_STAT_U32(AVG_NETOFF, ((q->avg_netoff + 0x8000) >> 16));
> +       PUT_STAT_U32(MAX_NETLEN, q->max_netlen);
> +       PUT_STAT_U32(MAX_ADJLEN, q->max_adjlen);
> +       PUT_STAT_U32(MIN_NETLEN, q->min_netlen);
> +       PUT_STAT_U32(MIN_ADJLEN, q->min_adjlen);
> +
> +#undef PUT_STAT_U32
> +
> +       tstats = nla_nest_start(d->skb, TCA_CAKE_STATS_TIN_STATS);
> +       if (!tstats)
> +               goto nla_put_failure;
> +
> +#define PUT_TSTAT_U32(attr, data) do {                                 \
> +               if (nla_put_u32(d->skb, TCA_CAKE_TIN_STATS_ ## attr, data)) \
> +                       goto nla_put_failure;                           \
> +       } while (0)
> +#define PUT_TSTAT_U64(attr, data) do {                                 \
> +               if (nla_put_u64_64bit(d->skb, TCA_CAKE_TIN_STATS_ ## attr, \
> +                                       data, TCA_CAKE_TIN_STATS_PAD))  \
> +                       goto nla_put_failure;                           \
> +       } while (0)
> +
> +       for (i = 0; i < q->tin_cnt; i++) {
> +               struct cake_tin_data *b = &q->tins[i];
> +
> +               ts = nla_nest_start(d->skb, i + 1);
> +               if (!ts)
> +                       goto nla_put_failure;
> +
> +               PUT_TSTAT_U32(THRESHOLD_RATE, b->tin_rate_bps);
> +               PUT_TSTAT_U32(TARGET_US, cobalt_time_to_us(b->cparams.target));
> +               PUT_TSTAT_U32(INTERVAL_US,
> +                             cobalt_time_to_us(b->cparams.interval));
> +
> +               PUT_TSTAT_U32(SENT_PACKETS, b->packets);
> +               PUT_TSTAT_U64(SENT_BYTES64, b->bytes);
> +               PUT_TSTAT_U32(DROPPED_PACKETS, b->tin_dropped);
> +               PUT_TSTAT_U32(ECN_MARKED_PACKETS, b->tin_ecn_mark);
> +               PUT_TSTAT_U64(BACKLOG_BYTES64, b->tin_backlog);
> +               PUT_TSTAT_U32(ACKS_DROPPED_PACKETS, b->ack_drops);
> +
> +               PUT_TSTAT_U32(PEAK_DELAY_US, cobalt_time_to_us(b->peak_delay));
> +               PUT_TSTAT_U32(AVG_DELAY_US, cobalt_time_to_us(b->avge_delay));
> +               PUT_TSTAT_U32(BASE_DELAY_US, cobalt_time_to_us(b->base_delay));
> +
> +               PUT_TSTAT_U32(WAY_INDIRECT_HITS, b->way_hits);
> +               PUT_TSTAT_U32(WAY_MISSES, b->way_misses);
> +               PUT_TSTAT_U32(WAY_COLLISIONS, b->way_collisions);
> +
> +               PUT_TSTAT_U32(SPARSE_FLOWS, b->sparse_flow_count +
> +                                          b->decaying_flow_count);
> +               PUT_TSTAT_U32(BULK_FLOWS, b->bulk_flow_count);
> +               PUT_TSTAT_U32(UNRESPONSIVE_FLOWS, b->unresponsive_flow_count);
> +               PUT_TSTAT_U32(MAX_SKBLEN, b->max_skblen);
> +
> +               PUT_TSTAT_U32(FLOW_QUANTUM, b->flow_quantum);
> +               nla_nest_end(d->skb, ts);
> +       }
> +
> +#undef PUT_TSTAT_U32
> +#undef PUT_TSTAT_U64
> +
> +       nla_nest_end(d->skb, tstats);
> +       return nla_nest_end(d->skb, stats);
> +
> +nla_put_failure:
> +       nla_nest_cancel(d->skb, stats);
> +       return -1;
> +}
> +
> +static struct Qdisc_ops cake_qdisc_ops __read_mostly = {
> +       .id             =       "cake",
> +       .priv_size      =       sizeof(struct cake_sched_data),
> +       .enqueue        =       cake_enqueue,
> +       .dequeue        =       cake_dequeue,
> +       .peek           =       qdisc_peek_dequeued,
> +       .init           =       cake_init,
> +       .reset          =       cake_reset,
> +       .destroy        =       cake_destroy,
> +       .change         =       cake_change,
> +       .dump           =       cake_dump,
> +       .dump_stats     =       cake_dump_stats,
> +       .owner          =       THIS_MODULE,
> +};
> +
> +static int __init cake_module_init(void)
> +{
> +       return register_qdisc(&cake_qdisc_ops);
> +}
> +
> +static void __exit cake_module_exit(void)
> +{
> +       unregister_qdisc(&cake_qdisc_ops);
> +}
> +
> +module_init(cake_module_init)
> +module_exit(cake_module_exit)
> +MODULE_AUTHOR("Jonathan Morton");
> +MODULE_LICENSE("Dual BSD/GPL");
> +MODULE_DESCRIPTION("The CAKE shaper.");
>

^ permalink raw reply

* [PATCH V2 net-next] liquidio: support use of ethtool to set link speed of CN23XX-225 cards
From: Felix Manlunas @ 2018-05-04 18:07 UTC (permalink / raw)
  To: davem
  Cc: netdev, raghu.vatsavayi, derek.chickles, satananda.burla,
	felix.manlunas, weilin.chang

From: Weilin Chang <weilin.chang@cavium.com>

Support setting the link speed of CN23XX-225 cards (which can do 25Gbps or
10Gbps) via ethtool_ops.set_link_ksettings.

Also fix the function assigned to ethtool_ops.get_link_ksettings to use the
new link_ksettings api completely (instead of partially via
ethtool_convert_legacy_u32_to_link_mode).

Signed-off-by: Weilin Chang <weilin.chang@cavium.com>
Acked-by: Raghu Vatsavayi <raghu.vatsavayi@cavium.com>
Signed-off-by: Felix Manlunas <felix.manlunas@cavium.com>
---
Patch Change Log:
  V1 -> V2:
    In lio_set_link_ksettings(), set the order of local variable
    declarations from longest to shortest line.

 drivers/net/ethernet/cavium/liquidio/lio_core.c    | 196 +++++++++++++++++++++
 drivers/net/ethernet/cavium/liquidio/lio_ethtool.c | 195 +++++++++++++++++---
 drivers/net/ethernet/cavium/liquidio/lio_main.c    |  20 +++
 drivers/net/ethernet/cavium/liquidio/lio_vf_main.c |   5 +
 .../net/ethernet/cavium/liquidio/liquidio_common.h |   4 +
 .../net/ethernet/cavium/liquidio/octeon_device.h   |  14 ++
 .../net/ethernet/cavium/liquidio/octeon_network.h  |  15 ++
 7 files changed, 425 insertions(+), 24 deletions(-)

diff --git a/drivers/net/ethernet/cavium/liquidio/lio_core.c b/drivers/net/ethernet/cavium/liquidio/lio_core.c
index 6821afc..8093c5e 100644
--- a/drivers/net/ethernet/cavium/liquidio/lio_core.c
+++ b/drivers/net/ethernet/cavium/liquidio/lio_core.c
@@ -1481,3 +1481,199 @@ int octnet_get_link_stats(struct net_device *netdev)
 
 	return 0;
 }
+
+static void liquidio_nic_seapi_ctl_callback(struct octeon_device *oct,
+					    u32 status,
+					    void *buf)
+{
+	struct liquidio_nic_seapi_ctl_context *ctx;
+	struct octeon_soft_command *sc = buf;
+
+	ctx = sc->ctxptr;
+
+	oct = lio_get_device(ctx->octeon_id);
+	if (status) {
+		dev_err(&oct->pci_dev->dev, "%s: instruction failed. Status: %llx\n",
+			__func__,
+			CVM_CAST64(status));
+	}
+	ctx->status = status;
+	complete(&ctx->complete);
+}
+
+int liquidio_set_speed(struct lio *lio, int speed)
+{
+	struct liquidio_nic_seapi_ctl_context *ctx;
+	struct octeon_device *oct = lio->oct_dev;
+	struct oct_nic_seapi_resp *resp;
+	struct octeon_soft_command *sc;
+	union octnet_cmd *ncmd;
+	u32 ctx_size;
+	int retval;
+	u32 var;
+
+	if (oct->speed_setting == speed)
+		return 0;
+
+	if (!OCTEON_CN23XX_PF(oct)) {
+		dev_err(&oct->pci_dev->dev, "%s: SET SPEED only for PF\n",
+			__func__);
+		return -EOPNOTSUPP;
+	}
+
+	ctx_size = sizeof(struct liquidio_nic_seapi_ctl_context);
+	sc = octeon_alloc_soft_command(oct, OCTNET_CMD_SIZE,
+				       sizeof(struct oct_nic_seapi_resp),
+				       ctx_size);
+	if (!sc)
+		return -ENOMEM;
+
+	ncmd = sc->virtdptr;
+	ctx  = sc->ctxptr;
+	resp = sc->virtrptr;
+	memset(resp, 0, sizeof(struct oct_nic_seapi_resp));
+
+	ctx->octeon_id = lio_get_device_id(oct);
+	ctx->status = 0;
+	init_completion(&ctx->complete);
+
+	ncmd->u64 = 0;
+	ncmd->s.cmd = SEAPI_CMD_SPEED_SET;
+	ncmd->s.param1 = speed;
+
+	octeon_swap_8B_data((u64 *)ncmd, (OCTNET_CMD_SIZE >> 3));
+
+	sc->iq_no = lio->linfo.txpciq[0].s.q_no;
+
+	octeon_prepare_soft_command(oct, sc, OPCODE_NIC,
+				    OPCODE_NIC_UBOOT_CTL, 0, 0, 0);
+
+	sc->callback = liquidio_nic_seapi_ctl_callback;
+	sc->callback_arg = sc;
+	sc->wait_time = 5000;
+
+	retval = octeon_send_soft_command(oct, sc);
+	if (retval == IQ_SEND_FAILED) {
+		dev_info(&oct->pci_dev->dev, "Failed to send soft command\n");
+		retval = -EBUSY;
+	} else {
+		/* Wait for response or timeout */
+		if (wait_for_completion_timeout(&ctx->complete,
+						msecs_to_jiffies(10000)) == 0) {
+			dev_err(&oct->pci_dev->dev, "%s: sc timeout\n",
+				__func__);
+			octeon_free_soft_command(oct, sc);
+			return -EINTR;
+		}
+
+		retval = resp->status;
+
+		if (retval) {
+			dev_err(&oct->pci_dev->dev, "%s failed, retval=%d\n",
+				__func__, retval);
+			octeon_free_soft_command(oct, sc);
+			return -EIO;
+		}
+
+		var = be32_to_cpu((__force __be32)resp->speed);
+		if (var != speed) {
+			dev_err(&oct->pci_dev->dev,
+				"%s: setting failed speed= %x, expect %x\n",
+				__func__, var, speed);
+		}
+
+		oct->speed_setting = var;
+	}
+
+	octeon_free_soft_command(oct, sc);
+
+	return retval;
+}
+
+int liquidio_get_speed(struct lio *lio)
+{
+	struct liquidio_nic_seapi_ctl_context *ctx;
+	struct octeon_device *oct = lio->oct_dev;
+	struct oct_nic_seapi_resp *resp;
+	struct octeon_soft_command *sc;
+	union octnet_cmd *ncmd;
+	u32 ctx_size;
+	int retval;
+
+	ctx_size = sizeof(struct liquidio_nic_seapi_ctl_context);
+	sc = octeon_alloc_soft_command(oct, OCTNET_CMD_SIZE,
+				       sizeof(struct oct_nic_seapi_resp),
+				       ctx_size);
+	if (!sc)
+		return -ENOMEM;
+
+	ncmd = sc->virtdptr;
+	ctx  = sc->ctxptr;
+	resp = sc->virtrptr;
+	memset(resp, 0, sizeof(struct oct_nic_seapi_resp));
+
+	ctx->octeon_id = lio_get_device_id(oct);
+	ctx->status = 0;
+	init_completion(&ctx->complete);
+
+	ncmd->u64 = 0;
+	ncmd->s.cmd = SEAPI_CMD_SPEED_GET;
+
+	octeon_swap_8B_data((u64 *)ncmd, (OCTNET_CMD_SIZE >> 3));
+
+	sc->iq_no = lio->linfo.txpciq[0].s.q_no;
+
+	octeon_prepare_soft_command(oct, sc, OPCODE_NIC,
+				    OPCODE_NIC_UBOOT_CTL, 0, 0, 0);
+
+	sc->callback = liquidio_nic_seapi_ctl_callback;
+	sc->callback_arg = sc;
+	sc->wait_time = 5000;
+
+	retval = octeon_send_soft_command(oct, sc);
+	if (retval == IQ_SEND_FAILED) {
+		dev_info(&oct->pci_dev->dev, "Failed to send soft command\n");
+		oct->no_speed_setting = 1;
+		oct->speed_setting = 25;
+
+		retval = -EBUSY;
+	} else {
+		if (wait_for_completion_timeout(&ctx->complete,
+						msecs_to_jiffies(10000)) == 0) {
+			dev_err(&oct->pci_dev->dev, "%s: sc timeout\n",
+				__func__);
+
+			oct->speed_setting = 25;
+			oct->no_speed_setting = 1;
+
+			octeon_free_soft_command(oct, sc);
+
+			return -EINTR;
+		}
+		retval = resp->status;
+		if (retval) {
+			dev_err(&oct->pci_dev->dev,
+				"%s failed retval=%d\n", __func__, retval);
+			oct->no_speed_setting = 1;
+			oct->speed_setting = 25;
+			octeon_free_soft_command(oct, sc);
+			retval = -EIO;
+		} else {
+			u32 var;
+
+			var = be32_to_cpu((__force __be32)resp->speed);
+			oct->speed_setting = var;
+			if (var == 0xffff) {
+				oct->no_speed_setting = 1;
+				/* unable to access boot variables
+				 * get the default value based on the NIC type
+				 */
+				oct->speed_setting = 25;
+			}
+		}
+	}
+
+	octeon_free_soft_command(oct, sc);
+
+	return retval;
+}
diff --git a/drivers/net/ethernet/cavium/liquidio/lio_ethtool.c b/drivers/net/ethernet/cavium/liquidio/lio_ethtool.c
index a1d8430..06f7449 100644
--- a/drivers/net/ethernet/cavium/liquidio/lio_ethtool.c
+++ b/drivers/net/ethernet/cavium/liquidio/lio_ethtool.c
@@ -230,46 +230,147 @@ static int lio_get_link_ksettings(struct net_device *netdev,
 	struct lio *lio = GET_LIO(netdev);
 	struct octeon_device *oct = lio->oct_dev;
 	struct oct_link_info *linfo;
-	u32 supported = 0, advertising = 0;
 
 	linfo = &lio->linfo;
 
+	ethtool_link_ksettings_zero_link_mode(ecmd, supported);
+	ethtool_link_ksettings_zero_link_mode(ecmd, advertising);
+
 	switch (linfo->link.s.phy_type) {
 	case LIO_PHY_PORT_TP:
 		ecmd->base.port = PORT_TP;
-		supported = (SUPPORTED_10000baseT_Full |
-			     SUPPORTED_TP | SUPPORTED_Pause);
-		advertising = (ADVERTISED_10000baseT_Full | ADVERTISED_Pause);
 		ecmd->base.autoneg = AUTONEG_DISABLE;
+		ethtool_link_ksettings_add_link_mode(ecmd, supported, TP);
+		ethtool_link_ksettings_add_link_mode(ecmd, supported, Pause);
+		ethtool_link_ksettings_add_link_mode(ecmd, supported,
+						     10000baseT_Full);
+
+		ethtool_link_ksettings_add_link_mode(ecmd, advertising, Pause);
+		ethtool_link_ksettings_add_link_mode(ecmd, advertising,
+						     10000baseT_Full);
+
 		break;
 
 	case LIO_PHY_PORT_FIBRE:
-		ecmd->base.port = PORT_FIBRE;
-
-		if (linfo->link.s.speed == SPEED_10000) {
-			supported = SUPPORTED_10000baseT_Full;
-			advertising = ADVERTISED_10000baseT_Full;
+		if (linfo->link.s.if_mode == INTERFACE_MODE_XAUI ||
+		    linfo->link.s.if_mode == INTERFACE_MODE_RXAUI ||
+		    linfo->link.s.if_mode == INTERFACE_MODE_XLAUI ||
+		    linfo->link.s.if_mode == INTERFACE_MODE_XFI) {
+			dev_dbg(&oct->pci_dev->dev, "ecmd->base.transceiver is XCVR_EXTERNAL\n");
+		} else {
+			dev_err(&oct->pci_dev->dev, "Unknown link interface mode: %d\n",
+				linfo->link.s.if_mode);
 		}
 
-		supported |= SUPPORTED_FIBRE | SUPPORTED_Pause;
-		advertising |= ADVERTISED_Pause;
+		ecmd->base.port = PORT_FIBRE;
 		ecmd->base.autoneg = AUTONEG_DISABLE;
+		ethtool_link_ksettings_add_link_mode(ecmd, supported, FIBRE);
+
+		ethtool_link_ksettings_add_link_mode(ecmd, supported, Pause);
+		ethtool_link_ksettings_add_link_mode(ecmd, advertising, Pause);
+		if (oct->subsystem_id == OCTEON_CN2350_25GB_SUBSYS_ID ||
+		    oct->subsystem_id == OCTEON_CN2360_25GB_SUBSYS_ID) {
+			if (OCTEON_CN23XX_PF(oct)) {
+				ethtool_link_ksettings_add_link_mode
+					(ecmd, supported, 25000baseSR_Full);
+				ethtool_link_ksettings_add_link_mode
+					(ecmd, supported, 25000baseKR_Full);
+				ethtool_link_ksettings_add_link_mode
+					(ecmd, supported, 25000baseCR_Full);
+
+				if (oct->no_speed_setting == 0)  {
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, supported,
+						 10000baseSR_Full);
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, supported,
+						 10000baseKR_Full);
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, supported,
+						 10000baseCR_Full);
+				}
+
+				if (oct->no_speed_setting == 0)
+					liquidio_get_speed(lio);
+				else
+					oct->speed_setting = 25;
+
+				if (oct->speed_setting == 10) {
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, advertising,
+						 10000baseSR_Full);
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, advertising,
+						 10000baseKR_Full);
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, advertising,
+						 10000baseCR_Full);
+				}
+				if (oct->speed_setting == 25) {
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, advertising,
+						 25000baseSR_Full);
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, advertising,
+						 25000baseKR_Full);
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, advertising,
+						 25000baseCR_Full);
+				}
+			} else { /* VF */
+				if (linfo->link.s.speed == 10000) {
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, supported,
+						 10000baseSR_Full);
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, supported,
+						 10000baseKR_Full);
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, supported,
+						 10000baseCR_Full);
+
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, advertising,
+						 10000baseSR_Full);
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, advertising,
+						 10000baseKR_Full);
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, advertising,
+						 10000baseCR_Full);
+				}
+
+				if (linfo->link.s.speed == 25000) {
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, supported,
+						 25000baseSR_Full);
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, supported,
+						 25000baseKR_Full);
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, supported,
+						 25000baseCR_Full);
+
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, advertising,
+						 25000baseSR_Full);
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, advertising,
+						 25000baseKR_Full);
+					ethtool_link_ksettings_add_link_mode
+						(ecmd, advertising,
+						 25000baseCR_Full);
+				}
+			}
+		} else {
+			ethtool_link_ksettings_add_link_mode(ecmd, supported,
+							     10000baseT_Full);
+			ethtool_link_ksettings_add_link_mode(ecmd, advertising,
+							     10000baseT_Full);
+		}
 		break;
 	}
 
-	if (linfo->link.s.if_mode == INTERFACE_MODE_XAUI ||
-	    linfo->link.s.if_mode == INTERFACE_MODE_RXAUI ||
-	    linfo->link.s.if_mode == INTERFACE_MODE_XLAUI ||
-	    linfo->link.s.if_mode == INTERFACE_MODE_XFI) {
-		ethtool_convert_legacy_u32_to_link_mode(
-			ecmd->link_modes.supported, supported);
-		ethtool_convert_legacy_u32_to_link_mode(
-			ecmd->link_modes.advertising, advertising);
-	} else {
-		dev_err(&oct->pci_dev->dev, "Unknown link interface reported %d\n",
-			linfo->link.s.if_mode);
-	}
-
 	if (linfo->link.s.link_up) {
 		ecmd->base.speed = linfo->link.s.speed;
 		ecmd->base.duplex = linfo->link.s.duplex;
@@ -281,6 +382,51 @@ static int lio_get_link_ksettings(struct net_device *netdev,
 	return 0;
 }
 
+static int lio_set_link_ksettings(struct net_device *netdev,
+				  const struct ethtool_link_ksettings *ecmd)
+{
+	const int speed = ecmd->base.speed;
+	struct lio *lio = GET_LIO(netdev);
+	struct oct_link_info *linfo;
+	struct octeon_device *oct;
+	u32 is25G = 0;
+
+	oct = lio->oct_dev;
+
+	linfo = &lio->linfo;
+
+	if (oct->subsystem_id == OCTEON_CN2350_25GB_SUBSYS_ID ||
+	    oct->subsystem_id == OCTEON_CN2360_25GB_SUBSYS_ID) {
+		is25G = 1;
+	} else {
+		return -EOPNOTSUPP;
+	}
+
+	if (oct->no_speed_setting) {
+		dev_err(&oct->pci_dev->dev, "%s: Changing speed is not supported\n",
+			__func__);
+		return -EOPNOTSUPP;
+	}
+
+	if ((ecmd->base.duplex != DUPLEX_UNKNOWN &&
+	     ecmd->base.duplex != linfo->link.s.duplex) ||
+	     ecmd->base.autoneg != AUTONEG_DISABLE ||
+	    (ecmd->base.speed != 10000 && ecmd->base.speed != 25000 &&
+	     ecmd->base.speed != SPEED_UNKNOWN))
+		return -EOPNOTSUPP;
+
+	if ((oct->speed_boot == speed / 1000) &&
+	    oct->speed_boot == oct->speed_setting)
+		return 0;
+
+	liquidio_set_speed(lio, speed / 1000);
+
+	dev_dbg(&oct->pci_dev->dev, "Port speed is set to %dG\n",
+		oct->speed_setting);
+
+	return 0;
+}
+
 static void
 lio_get_drvinfo(struct net_device *netdev, struct ethtool_drvinfo *drvinfo)
 {
@@ -2966,6 +3112,7 @@ static int lio_set_priv_flags(struct net_device *netdev, u32 flags)
 
 static const struct ethtool_ops lio_ethtool_ops = {
 	.get_link_ksettings	= lio_get_link_ksettings,
+	.set_link_ksettings	= lio_set_link_ksettings,
 	.get_link		= ethtool_op_get_link,
 	.get_drvinfo		= lio_get_drvinfo,
 	.get_ringparam		= lio_ethtool_get_ringparam,
diff --git a/drivers/net/ethernet/cavium/liquidio/lio_main.c b/drivers/net/ethernet/cavium/liquidio/lio_main.c
index ee75048..e500528 100644
--- a/drivers/net/ethernet/cavium/liquidio/lio_main.c
+++ b/drivers/net/ethernet/cavium/liquidio/lio_main.c
@@ -912,6 +912,9 @@ liquidio_probe(struct pci_dev *pdev,
 	/* set linux specific device pointer */
 	oct_dev->pci_dev = (void *)pdev;
 
+	oct_dev->subsystem_id = pdev->subsystem_vendor |
+		(pdev->subsystem_device << 16);
+
 	hs = &handshake[oct_dev->octeon_id];
 	init_completion(&hs->init);
 	init_completion(&hs->started);
@@ -3664,6 +3667,23 @@ static int setup_nic_devices(struct octeon_device *octeon_dev)
 			"NIC ifidx:%d Setup successful\n", i);
 
 		octeon_free_soft_command(octeon_dev, sc);
+
+		if (octeon_dev->subsystem_id ==
+			OCTEON_CN2350_25GB_SUBSYS_ID ||
+		    octeon_dev->subsystem_id ==
+			OCTEON_CN2360_25GB_SUBSYS_ID) {
+			liquidio_get_speed(lio);
+
+			if (octeon_dev->speed_setting == 0) {
+				octeon_dev->speed_setting = 25;
+				octeon_dev->no_speed_setting = 1;
+			}
+		} else {
+			octeon_dev->no_speed_setting = 1;
+			octeon_dev->speed_setting = 10;
+		}
+		octeon_dev->speed_boot = octeon_dev->speed_setting;
+
 	}
 
 	devlink = devlink_alloc(&liquidio_devlink_ops,
diff --git a/drivers/net/ethernet/cavium/liquidio/lio_vf_main.c b/drivers/net/ethernet/cavium/liquidio/lio_vf_main.c
index 6295eee..7fa0212 100644
--- a/drivers/net/ethernet/cavium/liquidio/lio_vf_main.c
+++ b/drivers/net/ethernet/cavium/liquidio/lio_vf_main.c
@@ -411,6 +411,9 @@ liquidio_vf_probe(struct pci_dev *pdev,
 	/* set linux specific device pointer */
 	oct_dev->pci_dev = pdev;
 
+	oct_dev->subsystem_id = pdev->subsystem_vendor |
+		(pdev->subsystem_device << 16);
+
 	if (octeon_device_init(oct_dev)) {
 		liquidio_vf_remove(pdev);
 		return -ENOMEM;
@@ -2199,6 +2202,8 @@ static int setup_nic_devices(struct octeon_device *octeon_dev)
 			"NIC ifidx:%d Setup successful\n", i);
 
 		octeon_free_soft_command(octeon_dev, sc);
+
+		octeon_dev->no_speed_setting = 1;
 	}
 
 	return 0;
diff --git a/drivers/net/ethernet/cavium/liquidio/liquidio_common.h b/drivers/net/ethernet/cavium/liquidio/liquidio_common.h
index 0265704..285b248 100644
--- a/drivers/net/ethernet/cavium/liquidio/liquidio_common.h
+++ b/drivers/net/ethernet/cavium/liquidio/liquidio_common.h
@@ -93,6 +93,7 @@ enum octeon_tag_type {
 
 #define OPCODE_NIC_VF_REP_PKT          0x15
 #define OPCODE_NIC_VF_REP_CMD          0x16
+#define OPCODE_NIC_UBOOT_CTL           0x17
 
 #define CORE_DRV_TEST_SCATTER_OP    0xFFF5
 
@@ -249,6 +250,9 @@ static inline void add_sg_size(struct octeon_sg_entry *sg_entry,
 #define   OCTNET_CMD_VLAN_FILTER_ENABLE 0x1
 #define   OCTNET_CMD_VLAN_FILTER_DISABLE 0x0
 
+#define   SEAPI_CMD_SPEED_SET           0x2
+#define   SEAPI_CMD_SPEED_GET           0x3
+
 #define   LIO_CMD_WAIT_TM 100
 
 /* RX(packets coming from wire) Checksum verification flags */
diff --git a/drivers/net/ethernet/cavium/liquidio/octeon_device.h b/drivers/net/ethernet/cavium/liquidio/octeon_device.h
index 9430c0a..94a4ed88d 100644
--- a/drivers/net/ethernet/cavium/liquidio/octeon_device.h
+++ b/drivers/net/ethernet/cavium/liquidio/octeon_device.h
@@ -43,6 +43,13 @@
 #define  OCTEON_CN23XX_REV_1_1        0x01
 #define  OCTEON_CN23XX_REV_2_0        0x80
 
+/**SubsystemId for the chips */
+#define	 OCTEON_CN2350_10GB_SUBSYS_ID_1	0X3177d
+#define	 OCTEON_CN2350_10GB_SUBSYS_ID_2	0X4177d
+#define	 OCTEON_CN2360_10GB_SUBSYS_ID	0X5177d
+#define	 OCTEON_CN2350_25GB_SUBSYS_ID	0X7177d
+#define	 OCTEON_CN2360_25GB_SUBSYS_ID	0X6177d
+
 /** Endian-swap modes supported by Octeon. */
 enum octeon_pci_swap_mode {
 	OCTEON_PCI_PASSTHROUGH = 0,
@@ -430,6 +437,8 @@ struct octeon_device {
 
 	u16 rev_id;
 
+	u32 subsystem_id;
+
 	u16 pf_num;
 
 	u16 vf_num;
@@ -584,6 +593,11 @@ struct octeon_device {
 	struct lio_vf_rep_list vf_rep_list;
 	struct devlink *devlink;
 	enum devlink_eswitch_mode eswitch_mode;
+
+	/* for 25G NIC speed change */
+	u8  speed_boot;
+	u8  speed_setting;
+	u8  no_speed_setting;
 };
 
 #define  OCT_DRV_ONLINE 1
diff --git a/drivers/net/ethernet/cavium/liquidio/octeon_network.h b/drivers/net/ethernet/cavium/liquidio/octeon_network.h
index 8571f11..dd3177a 100644
--- a/drivers/net/ethernet/cavium/liquidio/octeon_network.h
+++ b/drivers/net/ethernet/cavium/liquidio/octeon_network.h
@@ -81,6 +81,18 @@ struct oct_nic_stats_ctrl {
 	struct net_device *netdev;
 };
 
+struct oct_nic_seapi_resp {
+	u64 rh;
+	u32 speed;
+	u64 status;
+};
+
+struct liquidio_nic_seapi_ctl_context {
+	int octeon_id;
+	u32 status;
+	struct completion complete;
+};
+
 /** LiquidIO per-interface network private data */
 struct lio {
 	/** State of the interface. Rx/Tx happens only in the RUNNING state.  */
@@ -230,6 +242,9 @@ void lio_delete_glists(struct lio *lio);
 
 int lio_setup_glists(struct octeon_device *oct, struct lio *lio, int num_qs);
 
+int liquidio_get_speed(struct lio *lio);
+int liquidio_set_speed(struct lio *lio, int speed);
+
 /**
  * \brief Net device change_mtu
  * @param netdev network device

^ permalink raw reply related

* Hello
From: Faruk Sakawo @ 2018-05-04 18:14 UTC (permalink / raw)


I have a business to discuss with you, can we talk?


Regards
Faruk Sakawo

^ permalink raw reply

* [PATCH net] Added support for 802.1ad Q in Q Ethernet tagged frames
From: Elad Nachman @ 2018-05-04 18:25 UTC (permalink / raw)
  To: netdev, davem

stmmac reception handler calls stmmac_rx_vlan() to strip the vlan before calling napi_gro_receive().

The function assumes VLAN tagged frames are always tagged with 802.1Q protocol,
and assigns ETH_P_8021Q to the skb by hard-coding the parameter on call to __vlan_hwaccel_put_tag() .

This causes packets not to be passed to the VLAN slave if it was created with 802.1AD protocol
(ip link add link eth0 eth0.100 type vlan proto 802.1ad id 100).

This fix passes the protocol from the VLAN header into __vlan_hwaccel_put_tag()
instead of using the hard-coded value of ETH_P_8021Q.

Signed-off-by: Elad Nachman <eladn@gilat.com>


---
 drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index b65e2d1..ced2d34 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -3293,17 +3293,19 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev)
 
 static void stmmac_rx_vlan(struct net_device *dev, struct sk_buff *skb)
 {
-	struct ethhdr *ehdr;
+	struct vlan_ethhdr *veth;
 	u16 vlanid;
+	__be16 vlan_proto;
 
 	if ((dev->features & NETIF_F_HW_VLAN_CTAG_RX) ==
 	    NETIF_F_HW_VLAN_CTAG_RX &&
 	    !__vlan_get_tag(skb, &vlanid)) {
 		/* pop the vlan tag */
-		ehdr = (struct ethhdr *)skb->data;
-		memmove(skb->data + VLAN_HLEN, ehdr, ETH_ALEN * 2);
+		veth = (struct vlan_ethhdr *)skb->data;
+		vlan_proto = veth->h_vlan_proto;
+		memmove(skb->data + VLAN_HLEN, veth, ETH_ALEN * 2);
 		skb_pull(skb, VLAN_HLEN);
-		__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlanid);
+		__vlan_hwaccel_put_tag(skb, vlan_proto, vlanid);
 	}
 }
 
-- 
2.7.4

^ permalink raw reply related

* [net-next PATCH v2 0/8] UDP GSO Segmentation clean-ups
From: Alexander Duyck @ 2018-05-04 18:28 UTC (permalink / raw)
  To: netdev, willemb, davem

This patch set addresses a number of issues I found while sorting out
enabling UDP GSO Segmentation support for ixgbe/ixgbevf. Specifically there
were a number of issues related to the checksum and such that seemed to
cause either minor irregularities or kernel panics in the case of the
offload request being allowed to traverse between name spaces.

With this set applied I am was able to get UDP GSO traffic to pass over
vxlan tunnels in both offloaded modes and non-offloaded modes for ixgbe and
ixgbevf.

I submitted the driver specific patches earlier as an RFC:
https://patchwork.ozlabs.org/project/netdev/list/?series=42477&archive=both&state=*

v2: Updated patches based on feedback from Eric Dumazet
    Split first patch into several patches based on feedback from Eric

---

Alexander Duyck (8):
      udp: Record gso_segs when supporting UDP segmentation offload
      udp: Verify that pulling UDP header in GSO segmentation doesn't fail
      udp: Do not pass MSS as parameter to GSO segmentation
      udp: Do not pass checksum as a parameter to GSO segmentation
      udp: Partially unroll handling of first segment and last segment
      udp: Add support for software checksum and GSO_PARTIAL with GSO offload
      udp: Do not copy destructor if one is not present
      net: Add NETIF_F_GSO_UDP_L4 to list of GSO offloads with fallback


 include/linux/netdev_features.h |    3 +
 include/net/udp.h               |    3 -
 net/ipv4/udp.c                  |    2 +
 net/ipv4/udp_offload.c          |  104 ++++++++++++++++++++++++++-------------
 net/ipv6/udp_offload.c          |   16 ------
 5 files changed, 74 insertions(+), 54 deletions(-)

^ permalink raw reply

* [net-next PATCH v2 1/8] udp: Record gso_segs when supporting UDP segmentation offload
From: Alexander Duyck @ 2018-05-04 18:28 UTC (permalink / raw)
  To: netdev, willemb, davem
In-Reply-To: <20180504182537.5194.72775.stgit@localhost.localdomain>

From: Alexander Duyck <alexander.h.duyck@intel.com>

We need to record the number of segments that will be generated when this
frame is segmented. The expectation is that if gso_size is set then
gso_segs is set as well. Without this some drivers such as ixgbe get
confused if they attempt to offload this as they record 0 segments for the
entire packet instead of the correct value.

Reviewed-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: Alexander Duyck <alexander.h.duyck@intel.com>
---
 net/ipv4/udp.c |    2 ++
 1 file changed, 2 insertions(+)

diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index dd3102a37ef9..e07db83b311e 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -793,6 +793,8 @@ static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4,
 
 		skb_shinfo(skb)->gso_size = cork->gso_size;
 		skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4;
+		skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(len - sizeof(uh),
+							 cork->gso_size);
 		goto csum_partial;
 	}
 

^ permalink raw reply related

* [net-next PATCH v2 2/8] udp: Verify that pulling UDP header in GSO segmentation doesn't fail
From: Alexander Duyck @ 2018-05-04 18:29 UTC (permalink / raw)
  To: netdev, willemb, davem
In-Reply-To: <20180504182537.5194.72775.stgit@localhost.localdomain>

From: Alexander Duyck <alexander.h.duyck@intel.com>

We should verify that we can pull the UDP header before we attempt to do
so. Otherwise if this fails we have no way of knowing and GSO will not work
correctly.

Signed-off-by: Alexander Duyck <alexander.h.duyck@intel.com>
---

v2: New break-out patch based on one patch from earlier series

 net/ipv4/udp_offload.c |    9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c
index 006257092f06..8303fff42940 100644
--- a/net/ipv4/udp_offload.c
+++ b/net/ipv4/udp_offload.c
@@ -191,14 +191,17 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 				  netdev_features_t features,
 				  unsigned int mss, __sum16 check)
 {
+	struct sk_buff *seg, *segs = ERR_PTR(-EINVAL);
 	struct sock *sk = gso_skb->sk;
 	unsigned int sum_truesize = 0;
-	struct sk_buff *segs, *seg;
 	unsigned int hdrlen;
 	struct udphdr *uh;
 
 	if (gso_skb->len <= sizeof(*uh) + mss)
-		return ERR_PTR(-EINVAL);
+		goto out;
+
+	if (!pskb_may_pull(gso_skb, sizeof(*uh)))
+		goto out;
 
 	hdrlen = gso_skb->data - skb_mac_header(gso_skb);
 	skb_pull(gso_skb, sizeof(*uh));
@@ -230,7 +233,7 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 	}
 
 	refcount_add(sum_truesize - gso_skb->truesize, &sk->sk_wmem_alloc);
-
+out:
 	return segs;
 }
 EXPORT_SYMBOL_GPL(__udp_gso_segment);

^ permalink raw reply related

* [net-next PATCH v2 3/8] udp: Do not pass MSS as parameter to GSO segmentation
From: Alexander Duyck @ 2018-05-04 18:29 UTC (permalink / raw)
  To: netdev, willemb, davem
In-Reply-To: <20180504182537.5194.72775.stgit@localhost.localdomain>

From: Alexander Duyck <alexander.h.duyck@intel.com>

There is no point in passing MSS as a parameter for for the GSO
segmentation call as it is already available via the shared info for the
skb itself.

Signed-off-by: Alexander Duyck <alexander.h.duyck@intel.com>
---

v2: New break-out patch based on one patch from earlier series

 include/net/udp.h      |    2 +-
 net/ipv4/udp_offload.c |    6 ++++--
 net/ipv6/udp_offload.c |    2 +-
 3 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/include/net/udp.h b/include/net/udp.h
index 05990746810e..8bd83b044ecd 100644
--- a/include/net/udp.h
+++ b/include/net/udp.h
@@ -176,7 +176,7 @@ struct sk_buff **udp_gro_receive(struct sk_buff **head, struct sk_buff *skb,
 
 struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 				  netdev_features_t features,
-				  unsigned int mss, __sum16 check);
+				  __sum16 check);
 
 static inline struct udphdr *udp_gro_udphdr(struct sk_buff *skb)
 {
diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c
index 8303fff42940..f21b63adcbbc 100644
--- a/net/ipv4/udp_offload.c
+++ b/net/ipv4/udp_offload.c
@@ -189,14 +189,16 @@ struct sk_buff *skb_udp_tunnel_segment(struct sk_buff *skb,
 
 struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 				  netdev_features_t features,
-				  unsigned int mss, __sum16 check)
+				  __sum16 check)
 {
 	struct sk_buff *seg, *segs = ERR_PTR(-EINVAL);
 	struct sock *sk = gso_skb->sk;
 	unsigned int sum_truesize = 0;
 	unsigned int hdrlen;
 	struct udphdr *uh;
+	unsigned int mss;
 
+	mss = skb_shinfo(gso_skb)->gso_size;
 	if (gso_skb->len <= sizeof(*uh) + mss)
 		goto out;
 
@@ -247,7 +249,7 @@ static struct sk_buff *__udp4_gso_segment(struct sk_buff *gso_skb,
 	if (!can_checksum_protocol(features, htons(ETH_P_IP)))
 		return ERR_PTR(-EIO);
 
-	return __udp_gso_segment(gso_skb, features, mss,
+	return __udp_gso_segment(gso_skb, features,
 				 udp_v4_check(sizeof(struct udphdr) + mss,
 					      iph->saddr, iph->daddr, 0));
 }
diff --git a/net/ipv6/udp_offload.c b/net/ipv6/udp_offload.c
index f7b85b1e6b3e..dea03ec09715 100644
--- a/net/ipv6/udp_offload.c
+++ b/net/ipv6/udp_offload.c
@@ -26,7 +26,7 @@ static struct sk_buff *__udp6_gso_segment(struct sk_buff *gso_skb,
 	if (!can_checksum_protocol(features, htons(ETH_P_IPV6)))
 		return ERR_PTR(-EIO);
 
-	return __udp_gso_segment(gso_skb, features, mss,
+	return __udp_gso_segment(gso_skb, features,
 				 udp_v6_check(sizeof(struct udphdr) + mss,
 					      &ip6h->saddr, &ip6h->daddr, 0));
 }

^ permalink raw reply related

* [net-next PATCH v2 4/8] udp: Do not pass checksum as a parameter to GSO segmentation
From: Alexander Duyck @ 2018-05-04 18:30 UTC (permalink / raw)
  To: netdev, willemb, davem
In-Reply-To: <20180504182537.5194.72775.stgit@localhost.localdomain>

From: Alexander Duyck <alexander.h.duyck@intel.com>

This patch is meant to allow us to avoid having to recompute the checksum
from scratch and have it passed as a parameter.

Instead of taking that approach we can take advantage of the fact that the
length that was used to compute the existing checksum is included in the
UDP header. If we cancel that out by adding the value XOR with 0xFFFF we
can then just add the new length in and fold that into the new result.

I think this may be fixing a checksum bug in the original code as well
since the checksum that was passed included the UDP header in the checksum
computation, but then excluded it for the adjustment on the last frame. I
believe this may have an effect on things in the cases where the two differ
by bits that would result in things crossing the byte boundaries.

Signed-off-by: Alexander Duyck <alexander.h.duyck@intel.com>
---

v2: New break-out patch based on one patch from earlier series

 include/net/udp.h      |    3 +--
 net/ipv4/udp_offload.c |   35 +++++++++++++++++++++--------------
 net/ipv6/udp_offload.c |    7 +------
 3 files changed, 23 insertions(+), 22 deletions(-)

diff --git a/include/net/udp.h b/include/net/udp.h
index 8bd83b044ecd..9289b6425032 100644
--- a/include/net/udp.h
+++ b/include/net/udp.h
@@ -175,8 +175,7 @@ struct sk_buff **udp_gro_receive(struct sk_buff **head, struct sk_buff *skb,
 int udp_gro_complete(struct sk_buff *skb, int nhoff, udp_lookup_t lookup);
 
 struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
-				  netdev_features_t features,
-				  __sum16 check);
+				  netdev_features_t features);
 
 static inline struct udphdr *udp_gro_udphdr(struct sk_buff *skb)
 {
diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c
index f21b63adcbbc..5c4bb8b9e83e 100644
--- a/net/ipv4/udp_offload.c
+++ b/net/ipv4/udp_offload.c
@@ -188,8 +188,7 @@ struct sk_buff *skb_udp_tunnel_segment(struct sk_buff *skb,
 EXPORT_SYMBOL(skb_udp_tunnel_segment);
 
 struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
-				  netdev_features_t features,
-				  __sum16 check)
+				  netdev_features_t features)
 {
 	struct sk_buff *seg, *segs = ERR_PTR(-EINVAL);
 	struct sock *sk = gso_skb->sk;
@@ -197,6 +196,8 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 	unsigned int hdrlen;
 	struct udphdr *uh;
 	unsigned int mss;
+	__sum16 check;
+	__be16 newlen;
 
 	mss = skb_shinfo(gso_skb)->gso_size;
 	if (gso_skb->len <= sizeof(*uh) + mss)
@@ -218,17 +219,28 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 		return segs;
 	}
 
+	uh = udp_hdr(segs);
+
+	/* compute checksum adjustment based on old length versus new */
+	newlen = htons(sizeof(*uh) + mss);
+	check = ~csum_fold((__force __wsum)((__force u32)uh->check +
+					    ((__force u32)uh->len ^ 0xFFFF) +
+					    (__force u32)newlen));
+
 	for (seg = segs; seg; seg = seg->next) {
 		uh = udp_hdr(seg);
-		uh->len = htons(seg->len - hdrlen);
-		uh->check = check;
 
 		/* last packet can be partial gso_size */
-		if (!seg->next)
-			csum_replace2(&uh->check, htons(mss),
-				      htons(seg->len - hdrlen - sizeof(*uh)));
+		if (!seg->next) {
+			newlen = htons(seg->len - hdrlen);
+			check = ~csum_fold((__force __wsum)((__force u32)uh->check +
+							    ((__force u32)uh->len ^ 0xFFFF) +
+							    (__force u32)newlen));
+		}
+
+		uh->len = newlen;
+		uh->check = check;
 
-		uh->check = ~uh->check;
 		seg->destructor = sock_wfree;
 		seg->sk = sk;
 		sum_truesize += seg->truesize;
@@ -243,15 +255,10 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 static struct sk_buff *__udp4_gso_segment(struct sk_buff *gso_skb,
 					  netdev_features_t features)
 {
-	const struct iphdr *iph = ip_hdr(gso_skb);
-	unsigned int mss = skb_shinfo(gso_skb)->gso_size;
-
 	if (!can_checksum_protocol(features, htons(ETH_P_IP)))
 		return ERR_PTR(-EIO);
 
-	return __udp_gso_segment(gso_skb, features,
-				 udp_v4_check(sizeof(struct udphdr) + mss,
-					      iph->saddr, iph->daddr, 0));
+	return __udp_gso_segment(gso_skb, features);
 }
 
 static struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb,
diff --git a/net/ipv6/udp_offload.c b/net/ipv6/udp_offload.c
index dea03ec09715..61e34f1d2fa2 100644
--- a/net/ipv6/udp_offload.c
+++ b/net/ipv6/udp_offload.c
@@ -20,15 +20,10 @@
 static struct sk_buff *__udp6_gso_segment(struct sk_buff *gso_skb,
 					  netdev_features_t features)
 {
-	const struct ipv6hdr *ip6h = ipv6_hdr(gso_skb);
-	unsigned int mss = skb_shinfo(gso_skb)->gso_size;
-
 	if (!can_checksum_protocol(features, htons(ETH_P_IPV6)))
 		return ERR_PTR(-EIO);
 
-	return __udp_gso_segment(gso_skb, features,
-				 udp_v6_check(sizeof(struct udphdr) + mss,
-					      &ip6h->saddr, &ip6h->daddr, 0));
+	return __udp_gso_segment(gso_skb, features);
 }
 
 static struct sk_buff *udp6_ufo_fragment(struct sk_buff *skb,

^ permalink raw reply related

* [net-next PATCH v2 5/8] udp: Partially unroll handling of first segment and last segment
From: Alexander Duyck @ 2018-05-04 18:30 UTC (permalink / raw)
  To: netdev, willemb, davem
In-Reply-To: <20180504182537.5194.72775.stgit@localhost.localdomain>

From: Alexander Duyck <alexander.h.duyck@intel.com>

This patch allows us to take care of unrolling the first segment and the
last segment of the loop for processing the segmented skb. Part of the
motivation for this is that it makes it easier to process the fact that the
first fame and all of the frames in between should be mostly identical
in terms of header data, and the last frame has differences in the length
and partial checksum.

In addition I am dropping the header length calculation since we don't
really need it for anything but the last frame and it can be easily
obtained by just pulling the data_len and offset of tail from the transport
header.

Signed-off-by: Alexander Duyck <alexander.h.duyck@intel.com>
---

v2: New break-out patch based on one patch from earlier series

 net/ipv4/udp_offload.c |   35 ++++++++++++++++++++---------------
 1 file changed, 20 insertions(+), 15 deletions(-)

diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c
index 5c4bb8b9e83e..946d06d2aa0c 100644
--- a/net/ipv4/udp_offload.c
+++ b/net/ipv4/udp_offload.c
@@ -193,7 +193,6 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 	struct sk_buff *seg, *segs = ERR_PTR(-EINVAL);
 	struct sock *sk = gso_skb->sk;
 	unsigned int sum_truesize = 0;
-	unsigned int hdrlen;
 	struct udphdr *uh;
 	unsigned int mss;
 	__sum16 check;
@@ -206,7 +205,6 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 	if (!pskb_may_pull(gso_skb, sizeof(*uh)))
 		goto out;
 
-	hdrlen = gso_skb->data - skb_mac_header(gso_skb);
 	skb_pull(gso_skb, sizeof(*uh));
 
 	/* clear destructor to avoid skb_segment assigning it to tail */
@@ -219,7 +217,8 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 		return segs;
 	}
 
-	uh = udp_hdr(segs);
+	seg = segs;
+	uh = udp_hdr(seg);
 
 	/* compute checksum adjustment based on old length versus new */
 	newlen = htons(sizeof(*uh) + mss);
@@ -227,25 +226,31 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 					    ((__force u32)uh->len ^ 0xFFFF) +
 					    (__force u32)newlen));
 
-	for (seg = segs; seg; seg = seg->next) {
-		uh = udp_hdr(seg);
+	for (;;) {
+		seg->destructor = sock_wfree;
+		seg->sk = sk;
+		sum_truesize += seg->truesize;
 
-		/* last packet can be partial gso_size */
-		if (!seg->next) {
-			newlen = htons(seg->len - hdrlen);
-			check = ~csum_fold((__force __wsum)((__force u32)uh->check +
-							    ((__force u32)uh->len ^ 0xFFFF) +
-							    (__force u32)newlen));
-		}
+		if (!seg->next)
+			break;
 
 		uh->len = newlen;
 		uh->check = check;
 
-		seg->destructor = sock_wfree;
-		seg->sk = sk;
-		sum_truesize += seg->truesize;
+		seg = seg->next;
+		uh = udp_hdr(seg);
 	}
 
+	/* last packet can be partial gso_size, account for that in checksum */
+	newlen = htons(skb_tail_pointer(seg) - skb_transport_header(seg) +
+		       seg->data_len);
+	check = ~csum_fold((__force __wsum)((__force u32)uh->check +
+					    ((__force u32)uh->len ^ 0xFFFF)  +
+					    (__force u32)newlen));
+	uh->len = newlen;
+	uh->check = check;
+
+	/* update refcount for the packet */
 	refcount_add(sum_truesize - gso_skb->truesize, &sk->sk_wmem_alloc);
 out:
 	return segs;

^ permalink raw reply related

* [net-next PATCH v2 6/8] udp: Add support for software checksum and GSO_PARTIAL with GSO offload
From: Alexander Duyck @ 2018-05-04 18:31 UTC (permalink / raw)
  To: netdev, willemb, davem
In-Reply-To: <20180504182537.5194.72775.stgit@localhost.localdomain>

From: Alexander Duyck <alexander.h.duyck@intel.com>

This patch adds support for a software provided checksum and GSO_PARTIAL
segmentation support. With this we can offload UDP segmentation on devices
that only have partial support for tunnels.

Since we are no longer needing the hardware checksum we can drop the checks
in the segmentation code that were verifying if it was present.

Signed-off-by: Alexander Duyck <alexander.h.duyck@intel.com>
---
 net/ipv4/udp_offload.c |   28 ++++++++++++++++++----------
 net/ipv6/udp_offload.c |   11 +----------
 2 files changed, 19 insertions(+), 20 deletions(-)

diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c
index 946d06d2aa0c..fd94bbb369b2 100644
--- a/net/ipv4/udp_offload.c
+++ b/net/ipv4/udp_offload.c
@@ -217,6 +217,13 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 		return segs;
 	}
 
+	/* GSO partial and frag_list segmentation only requires splitting
+	 * the frame into an MSS multiple and possibly a remainder, both
+	 * cases return a GSO skb. So update the mss now.
+	 */
+	if (skb_is_gso(segs))
+		mss *= skb_shinfo(segs)->gso_segs;
+
 	seg = segs;
 	uh = udp_hdr(seg);
 
@@ -237,6 +244,11 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 		uh->len = newlen;
 		uh->check = check;
 
+		if (seg->ip_summed == CHECKSUM_PARTIAL)
+			gso_reset_checksum(seg, ~check);
+		else
+			uh->check = gso_make_checksum(seg, ~check);
+
 		seg = seg->next;
 		uh = udp_hdr(seg);
 	}
@@ -250,6 +262,11 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 	uh->len = newlen;
 	uh->check = check;
 
+	if (seg->ip_summed == CHECKSUM_PARTIAL)
+		gso_reset_checksum(seg, ~check);
+	else
+		uh->check = gso_make_checksum(seg, ~check);
+
 	/* update refcount for the packet */
 	refcount_add(sum_truesize - gso_skb->truesize, &sk->sk_wmem_alloc);
 out:
@@ -257,15 +274,6 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 }
 EXPORT_SYMBOL_GPL(__udp_gso_segment);
 
-static struct sk_buff *__udp4_gso_segment(struct sk_buff *gso_skb,
-					  netdev_features_t features)
-{
-	if (!can_checksum_protocol(features, htons(ETH_P_IP)))
-		return ERR_PTR(-EIO);
-
-	return __udp_gso_segment(gso_skb, features);
-}
-
 static struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb,
 					 netdev_features_t features)
 {
@@ -289,7 +297,7 @@ static struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb,
 		goto out;
 
 	if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4)
-		return __udp4_gso_segment(skb, features);
+		return __udp_gso_segment(skb, features);
 
 	mss = skb_shinfo(skb)->gso_size;
 	if (unlikely(skb->len <= mss))
diff --git a/net/ipv6/udp_offload.c b/net/ipv6/udp_offload.c
index 61e34f1d2fa2..03a2ff3fe1e6 100644
--- a/net/ipv6/udp_offload.c
+++ b/net/ipv6/udp_offload.c
@@ -17,15 +17,6 @@
 #include <net/ip6_checksum.h>
 #include "ip6_offload.h"
 
-static struct sk_buff *__udp6_gso_segment(struct sk_buff *gso_skb,
-					  netdev_features_t features)
-{
-	if (!can_checksum_protocol(features, htons(ETH_P_IPV6)))
-		return ERR_PTR(-EIO);
-
-	return __udp_gso_segment(gso_skb, features);
-}
-
 static struct sk_buff *udp6_ufo_fragment(struct sk_buff *skb,
 					 netdev_features_t features)
 {
@@ -58,7 +49,7 @@ static struct sk_buff *udp6_ufo_fragment(struct sk_buff *skb,
 			goto out;
 
 		if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4)
-			return __udp6_gso_segment(skb, features);
+			return __udp_gso_segment(skb, features);
 
 		/* Do software UFO. Complete and fill in the UDP checksum as HW cannot
 		 * do checksum of UDP packets sent as multiple IP fragments.

^ permalink raw reply related

* [net-next PATCH v2 7/8] udp: Do not copy destructor if one is not present
From: Alexander Duyck @ 2018-05-04 18:31 UTC (permalink / raw)
  To: netdev, willemb, davem
In-Reply-To: <20180504182537.5194.72775.stgit@localhost.localdomain>

From: Alexander Duyck <alexander.h.duyck@intel.com>

This patch makes it so that if a destructor is not present we avoid trying
to update the skb socket or any reference counting that would be associated
with the NULL socket and/or descriptor. By doing this we can support
traffic coming from another namespace without any issues.

Signed-off-by: Alexander Duyck <alexander.h.duyck@intel.com>
---

v2: Do not overwrite destructor if not sock_wfree as per Eric
    Drop WARN_ON as per Eric

 net/ipv4/udp_offload.c |   21 ++++++++++++++-------
 1 file changed, 14 insertions(+), 7 deletions(-)

diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c
index fd94bbb369b2..e802f6331589 100644
--- a/net/ipv4/udp_offload.c
+++ b/net/ipv4/udp_offload.c
@@ -195,6 +195,7 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 	unsigned int sum_truesize = 0;
 	struct udphdr *uh;
 	unsigned int mss;
+	bool copy_dtor;
 	__sum16 check;
 	__be16 newlen;
 
@@ -208,12 +209,14 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 	skb_pull(gso_skb, sizeof(*uh));
 
 	/* clear destructor to avoid skb_segment assigning it to tail */
-	WARN_ON_ONCE(gso_skb->destructor != sock_wfree);
-	gso_skb->destructor = NULL;
+	copy_dtor = gso_skb->destructor == sock_wfree;
+	if (copy_dtor)
+		gso_skb->destructor = NULL;
 
 	segs = skb_segment(gso_skb, features);
 	if (unlikely(IS_ERR_OR_NULL(segs))) {
-		gso_skb->destructor = sock_wfree;
+		if (copy_dtor)
+			gso_skb->destructor = sock_wfree;
 		return segs;
 	}
 
@@ -234,9 +237,11 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 					    (__force u32)newlen));
 
 	for (;;) {
-		seg->destructor = sock_wfree;
-		seg->sk = sk;
-		sum_truesize += seg->truesize;
+		if (copy_dtor) {
+			seg->destructor = sock_wfree;
+			seg->sk = sk;
+			sum_truesize += seg->truesize;
+		}
 
 		if (!seg->next)
 			break;
@@ -268,7 +273,9 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
 		uh->check = gso_make_checksum(seg, ~check);
 
 	/* update refcount for the packet */
-	refcount_add(sum_truesize - gso_skb->truesize, &sk->sk_wmem_alloc);
+	if (copy_dtor)
+		refcount_add(sum_truesize - gso_skb->truesize,
+			     &sk->sk_wmem_alloc);
 out:
 	return segs;
 }

^ permalink raw reply related

* [net-next PATCH v2 8/8] net: Add NETIF_F_GSO_UDP_L4 to list of GSO offloads with fallback
From: Alexander Duyck @ 2018-05-04 18:31 UTC (permalink / raw)
  To: netdev, willemb, davem
In-Reply-To: <20180504182537.5194.72775.stgit@localhost.localdomain>

From: Alexander Duyck <alexander.h.duyck@intel.com>

Enable UDP offload as a generic software offload since we can now handle it
for multiple cases including if the checksum isn't present and via
gso_partial in the case of tunnels.

Signed-off-by: Alexander Duyck <alexander.h.duyck@intel.com>
---
 include/linux/netdev_features.h |    3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/include/linux/netdev_features.h b/include/linux/netdev_features.h
index c87c3a3453c1..efbd8b2c0197 100644
--- a/include/linux/netdev_features.h
+++ b/include/linux/netdev_features.h
@@ -184,7 +184,8 @@ enum {
 
 /* List of features with software fallbacks. */
 #define NETIF_F_GSO_SOFTWARE	(NETIF_F_ALL_TSO | \
-				 NETIF_F_GSO_SCTP)
+				 NETIF_F_GSO_SCTP| \
+				 NETIF_F_GSO_UDP_L4)
 
 /*
  * If one device supports one of these features, then enable them

^ permalink raw reply related

* Re: [RFC PATCH 2/2] net: mac808211: mac802154: use lockdep_assert_in_softirq() instead own warning
From: Peter Zijlstra @ 2018-05-04 18:32 UTC (permalink / raw)
  To: Sebastian Andrzej Siewior
  Cc: linux-kernel, tglx, Ingo Molnar, David S. Miller, Johannes Berg,
	Alexander Aring, Stefan Schmidt, netdev, linux-wireless,
	linux-wpan, Anna-Maria Gleixner
In-Reply-To: <20180504175144.12179-3-bigeasy@linutronix.de>

On Fri, May 04, 2018 at 07:51:44PM +0200, Sebastian Andrzej Siewior wrote:
> From: Anna-Maria Gleixner <anna-maria@linutronix.de>
> 
> The warning in ieee802154_rx() and ieee80211_rx_napi() is there to ensure
> the softirq context for the subsequent netif_receive_skb() call. 

That's not in fact what it does though; so while that might indeed be
the intent that's not what it does.

^ permalink raw reply

* Re: [RFC PATCH 2/2] net: mac808211: mac802154: use lockdep_assert_in_softirq() instead own warning
From: Sebastian Andrzej Siewior @ 2018-05-04 18:45 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: linux-kernel, tglx, Ingo Molnar, David S. Miller, Johannes Berg,
	Alexander Aring, Stefan Schmidt, netdev, linux-wireless,
	linux-wpan, Anna-Maria Gleixner
In-Reply-To: <20180504183249.GU12217@hirez.programming.kicks-ass.net>

On 2018-05-04 20:32:49 [+0200], Peter Zijlstra wrote:
> On Fri, May 04, 2018 at 07:51:44PM +0200, Sebastian Andrzej Siewior wrote:
> > From: Anna-Maria Gleixner <anna-maria@linutronix.de>
> > 
> > The warning in ieee802154_rx() and ieee80211_rx_napi() is there to ensure
> > the softirq context for the subsequent netif_receive_skb() call. 
> 
> That's not in fact what it does though; so while that might indeed be
> the intent that's not what it does.

It was introduced in commit d20ef63d3246 ("mac80211: document
ieee80211_rx() context requirement"):

    mac80211: document ieee80211_rx() context requirement
    
    ieee80211_rx() must be called with softirqs disabled
    since the networking stack requires this for netif_rx()
    and some code in mac80211 can assume that it can not
    be processing its own tasklet and this call at the same
    time.
    
    It may be possible to remove this requirement after a
    careful audit of mac80211 and doing any needed locking
    improvements in it along with disabling softirqs around
    netif_rx(). An alternative might be to push all packet
    processing to process context in mac80211, instead of
    to the tasklet, and add other synchronisation.

Sebastian

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox