diff mbox series

[v3,09/24] wfx: add hwio.c/hwio.h

Message ID 20201104155207.128076-10-Jerome.Pouiller@silabs.com
State New
Headers show
Series None | expand

Commit Message

Jérôme Pouiller Nov. 4, 2020, 3:51 p.m. UTC
From: Jérôme Pouiller <jerome.pouiller@silabs.com>

Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
---
 drivers/net/wireless/silabs/wfx/hwio.c | 352 +++++++++++++++++++++++++
 drivers/net/wireless/silabs/wfx/hwio.h |  75 ++++++
 2 files changed, 427 insertions(+)
 create mode 100644 drivers/net/wireless/silabs/wfx/hwio.c
 create mode 100644 drivers/net/wireless/silabs/wfx/hwio.h

Comments

Greg Kroah-Hartman Dec. 22, 2020, 3:27 p.m. UTC | #1
On Tue, Dec 22, 2020 at 05:10:11PM +0200, Kalle Valo wrote:
> Jerome Pouiller <Jerome.Pouiller@silabs.com> writes:
> 
> > +/*
> > + * Internal helpers.
> > + *
> > + * About CONFIG_VMAP_STACK:
> > + * When CONFIG_VMAP_STACK is enabled, it is not possible to run DMA on stack
> > + * allocated data. Functions below that work with registers (aka functions
> > + * ending with "32") automatically reallocate buffers with kmalloc. However,
> > + * functions that work with arbitrary length buffers let's caller to handle
> > + * memory location. In doubt, enable CONFIG_DEBUG_SG to detect badly located
> > + * buffer.
> > + */
> 
> This sounds very hacky to me, I have understood that you should never
> use stack with DMA.

You should never do that because some platforms do not support it, so no
driver should ever try to do that as they do not know what platform they
are running on.

thanks,

greg k-h
Jérôme Pouiller Dec. 22, 2020, 9:02 p.m. UTC | #2
On Tuesday 22 December 2020 16:27:01 CET Greg Kroah-Hartman wrote:
> 
> On Tue, Dec 22, 2020 at 05:10:11PM +0200, Kalle Valo wrote:
> > Jerome Pouiller <Jerome.Pouiller@silabs.com> writes:
> >
> > > +/*
> > > + * Internal helpers.
> > > + *
> > > + * About CONFIG_VMAP_STACK:
> > > + * When CONFIG_VMAP_STACK is enabled, it is not possible to run DMA on stack
> > > + * allocated data. Functions below that work with registers (aka functions
> > > + * ending with "32") automatically reallocate buffers with kmalloc. However,
> > > + * functions that work with arbitrary length buffers let's caller to handle
> > > + * memory location. In doubt, enable CONFIG_DEBUG_SG to detect badly located
> > > + * buffer.
> > > + */
> >
> > This sounds very hacky to me, I have understood that you should never
> > use stack with DMA.
> 
> You should never do that because some platforms do not support it, so no
> driver should ever try to do that as they do not know what platform they
> are running on.

Yes, I have learned this rule the hard way.

There is no better way than a comment to warn the user that the argument
will be used with a DMA? A Sparse annotation, for example?
Kalle Valo Dec. 23, 2020, 5:28 a.m. UTC | #3
Jérôme Pouiller <jerome.pouiller@silabs.com> writes:

> On Tuesday 22 December 2020 16:27:01 CET Greg Kroah-Hartman wrote:
>> 
>> On Tue, Dec 22, 2020 at 05:10:11PM +0200, Kalle Valo wrote:
>> > Jerome Pouiller <Jerome.Pouiller@silabs.com> writes:
>> >
>> > > +/*
>> > > + * Internal helpers.
>> > > + *
>> > > + * About CONFIG_VMAP_STACK:
>> > > + * When CONFIG_VMAP_STACK is enabled, it is not possible to run DMA on stack
>> > > + * allocated data. Functions below that work with registers (aka functions
>> > > + * ending with "32") automatically reallocate buffers with kmalloc. However,
>> > > + * functions that work with arbitrary length buffers let's caller to handle
>> > > + * memory location. In doubt, enable CONFIG_DEBUG_SG to detect badly located
>> > > + * buffer.
>> > > + */
>> >
>> > This sounds very hacky to me, I have understood that you should never
>> > use stack with DMA.
>> 
>> You should never do that because some platforms do not support it, so no
>> driver should ever try to do that as they do not know what platform they
>> are running on.
>
> Yes, I have learned this rule the hard way.
>
> There is no better way than a comment to warn the user that the argument
> will be used with a DMA? A Sparse annotation, for example?

I have not seen anything, but something like sparse annotation would be
useful. Please let me know if you find anything like that.

But I think that CONFIG_VMAP_STACK is irrelevant and the comment should
be clarified that using stack memory must NOT be used for DMA operations
in any circumstances.
Jérôme Pouiller Dec. 23, 2020, 8:01 a.m. UTC | #4
On Tuesday 22 December 2020 16:27:01 CET Greg Kroah-Hartman wrote:
> On Tue, Dec 22, 2020 at 05:10:11PM +0200, Kalle Valo wrote:
> > Jerome Pouiller <Jerome.Pouiller@silabs.com> writes:
> >
> > > +/*
> > > + * Internal helpers.
> > > + *
> > > + * About CONFIG_VMAP_STACK:
> > > + * When CONFIG_VMAP_STACK is enabled, it is not possible to run DMA on stack
> > > + * allocated data. Functions below that work with registers (aka functions
> > > + * ending with "32") automatically reallocate buffers with kmalloc. However,
> > > + * functions that work with arbitrary length buffers let's caller to handle
> > > + * memory location. In doubt, enable CONFIG_DEBUG_SG to detect badly located
> > > + * buffer.
> > > + */
> >
> > This sounds very hacky to me, I have understood that you should never
> > use stack with DMA.
> 
> You should never do that because some platforms do not support it, so no
> driver should ever try to do that as they do not know what platform they
> are running on.

Just to be curious, why these platforms don't support DMA in a stack
allocated area? If the memory is contiguous (= not vmalloced), correctly
aligned and in the first 4GB of physical memory, it should be sufficient,
shouldn't?
Greg Kroah-Hartman Dec. 23, 2020, 8:09 a.m. UTC | #5
On Wed, Dec 23, 2020 at 09:01:33AM +0100, Jérôme Pouiller wrote:
> On Tuesday 22 December 2020 16:27:01 CET Greg Kroah-Hartman wrote:
> > On Tue, Dec 22, 2020 at 05:10:11PM +0200, Kalle Valo wrote:
> > > Jerome Pouiller <Jerome.Pouiller@silabs.com> writes:
> > >
> > > > +/*
> > > > + * Internal helpers.
> > > > + *
> > > > + * About CONFIG_VMAP_STACK:
> > > > + * When CONFIG_VMAP_STACK is enabled, it is not possible to run DMA on stack
> > > > + * allocated data. Functions below that work with registers (aka functions
> > > > + * ending with "32") automatically reallocate buffers with kmalloc. However,
> > > > + * functions that work with arbitrary length buffers let's caller to handle
> > > > + * memory location. In doubt, enable CONFIG_DEBUG_SG to detect badly located
> > > > + * buffer.
> > > > + */
> > >
> > > This sounds very hacky to me, I have understood that you should never
> > > use stack with DMA.
> > 
> > You should never do that because some platforms do not support it, so no
> > driver should ever try to do that as they do not know what platform they
> > are running on.
> 
> Just to be curious, why these platforms don't support DMA in a stack
> allocated area?

Hardware is odd :(

> If the memory is contiguous (= not vmalloced), correctly
> aligned and in the first 4GB of physical memory, it should be sufficient,
> shouldn't?

Nope, sorry, this just does not work at all on many platforms.

thanks,

greg k-h
Dan Carpenter Jan. 4, 2021, 12:34 p.m. UTC | #6
On Tue, Dec 22, 2020 at 10:02:09PM +0100, Jérôme Pouiller wrote:
> On Tuesday 22 December 2020 16:27:01 CET Greg Kroah-Hartman wrote:
> > 
> > On Tue, Dec 22, 2020 at 05:10:11PM +0200, Kalle Valo wrote:
> > > Jerome Pouiller <Jerome.Pouiller@silabs.com> writes:
> > >
> > > > +/*
> > > > + * Internal helpers.
> > > > + *
> > > > + * About CONFIG_VMAP_STACK:
> > > > + * When CONFIG_VMAP_STACK is enabled, it is not possible to run DMA on stack
> > > > + * allocated data. Functions below that work with registers (aka functions
> > > > + * ending with "32") automatically reallocate buffers with kmalloc. However,
> > > > + * functions that work with arbitrary length buffers let's caller to handle
> > > > + * memory location. In doubt, enable CONFIG_DEBUG_SG to detect badly located
> > > > + * buffer.
> > > > + */
> > >
> > > This sounds very hacky to me, I have understood that you should never
> > > use stack with DMA.
> > 
> > You should never do that because some platforms do not support it, so no
> > driver should ever try to do that as they do not know what platform they
> > are running on.
> 
> Yes, I have learned this rule the hard way.
> 
> There is no better way than a comment to warn the user that the argument
> will be used with a DMA? A Sparse annotation, for example?
> 

There is a Smatch warning for this, but I hadn't looked at the results
in a while. :/  I'm not sure how many are valid.  Some kind of
annotation would be nice.

regards,
dan carpenter

drivers/staging/gdm724x/gdm_usb.c:69 request_mac_address() error: doing dma on the stack (buf)
drivers/staging/rtl8192u/r8192U_core.c:1553 rtl8192_tx() error: doing dma on the stack (&zero)
drivers/staging/comedi/drivers/dt9812.c:249 dt9812_read_info() error: doing dma on the stack (&cmd)
drivers/staging/comedi/drivers/dt9812.c:273 dt9812_read_multiple_registers() error: doing dma on the stack (&cmd)
drivers/staging/comedi/drivers/dt9812.c:299 dt9812_write_multiple_registers() error: doing dma on the stack (&cmd)
drivers/staging/comedi/drivers/dt9812.c:318 dt9812_rmw_multiple_registers() error: doing dma on the stack (&cmd)
drivers/staging/comedi/drivers/dt9812.c:330 dt9812_digital_in() error: doing dma on the stack (value)
drivers/staging/comedi/drivers/dt9812.c:456 dt9812_analog_in() error: doing dma on the stack (val)
drivers/staging/comedi/drivers/dt9812.c:692 dt9812_reset_device() error: doing dma on the stack (&tmp8)
drivers/staging/comedi/drivers/dt9812.c:700 dt9812_reset_device() error: doing dma on the stack (&tmp8)
drivers/staging/comedi/drivers/dt9812.c:711 dt9812_reset_device() error: doing dma on the stack (&tmp16)
drivers/staging/comedi/drivers/dt9812.c:718 dt9812_reset_device() error: doing dma on the stack (&tmp16)
drivers/staging/comedi/drivers/dt9812.c:725 dt9812_reset_device() error: doing dma on the stack (&tmp16)
drivers/staging/comedi/drivers/dt9812.c:732 dt9812_reset_device() error: doing dma on the stack (&tmp32)
drivers/usb/storage/alauda.c:498 alauda_check_status2() error: doing dma on the stack (command)
drivers/usb/storage/alauda.c:503 alauda_check_status2() error: doing dma on the stack (data)
drivers/usb/storage/alauda.c:527 alauda_get_redu_data() error: doing dma on the stack (command)
drivers/usb/storage/alauda.c:702 alauda_erase_block() error: doing dma on the stack (command)
drivers/usb/storage/alauda.c:707 alauda_erase_block() error: doing dma on the stack (buf)
drivers/usb/storage/alauda.c:731 alauda_read_block_raw() error: doing dma on the stack (command)
drivers/usb/storage/alauda.c:782 alauda_write_block() error: doing dma on the stack (command)
drivers/usb/class/usblp.c:593 usblp_ioctl() error: doing dma on the stack (&newChannel)
drivers/usb/serial/iuu_phoenix.c:542 iuu_uart_flush() error: doing dma on the stack (&rxcmd)
drivers/firewire/core-device.c:565 read_config_rom() error: doing dma on the stack (&dummy)
drivers/firewire/core-device.c:1111 reread_config_rom() error: doing dma on the stack (&q)
drivers/media/usb/uvc/uvc_v4l2.c:910 uvc_ioctl_g_input() error: doing dma on the stack (&i)
drivers/media/usb/uvc/uvc_v4l2.c:942 uvc_ioctl_s_input() error: doing dma on the stack (&i)
drivers/media/usb/cx231xx/cx231xx-pcb-cfg.c:662 initialize_cx231xx() error: doing dma on the stack (data)
drivers/media/usb/cx231xx/cx231xx-avcore.c:90 uninitGPIO() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:1297 cx231xx_enable_i2c_port_3() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:1313 cx231xx_enable_i2c_port_3() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:1546 cx231xx_set_Colibri_For_LowIF() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2261 cx231xx_set_power_mode() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2278 cx231xx_set_power_mode() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2288 cx231xx_set_power_mode() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2297 cx231xx_set_power_mode() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2311 cx231xx_set_power_mode() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2321 cx231xx_set_power_mode() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2332 cx231xx_set_power_mode() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2342 cx231xx_set_power_mode() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2353 cx231xx_set_power_mode() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2376 cx231xx_set_power_mode() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2386 cx231xx_set_power_mode() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2396 cx231xx_set_power_mode() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2407 cx231xx_set_power_mode() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2417 cx231xx_set_power_mode() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2446 cx231xx_set_power_mode() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2457 cx231xx_set_power_mode() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2469 cx231xx_power_suspend() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2481 cx231xx_power_suspend() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2497 cx231xx_start_stream() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2509 cx231xx_start_stream() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2523 cx231xx_stop_stream() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2534 cx231xx_stop_stream() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2591 cx231xx_initialize_stream_xfer() error: doing dma on the stack (val)
drivers/media/usb/cx231xx/cx231xx-avcore.c:2599 cx231xx_initialize_stream_xfer() error: doing dma on the stack (val)
drivers/media/usb/cx231xx/cx231xx-core.c:635 cx231xx_demod_reset() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-core.c:644 cx231xx_demod_reset() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-core.c:649 cx231xx_demod_reset() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-core.c:654 cx231xx_demod_reset() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-core.c:658 cx231xx_demod_reset() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-core.c:1253 cx231xx_stop_TS1() error: doing dma on the stack (val)
drivers/media/usb/cx231xx/cx231xx-core.c:1260 cx231xx_stop_TS1() error: doing dma on the stack (val)
drivers/media/usb/cx231xx/cx231xx-core.c:1272 cx231xx_start_TS1() error: doing dma on the stack (val)
drivers/media/usb/cx231xx/cx231xx-core.c:1279 cx231xx_start_TS1() error: doing dma on the stack (val)
drivers/media/usb/cx231xx/cx231xx-core.c:1533 cx231xx_mode_register() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-core.c:1546 cx231xx_mode_register() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-video.c:1236 cx231xx_g_register() error: doing dma on the stack (value)
drivers/media/usb/cx231xx/cx231xx-video.c:1297 cx231xx_s_register() error: doing dma on the stack (data)
drivers/media/usb/gspca/kinect.c:209 write_register() error: doing dma on the stack (reply)
drivers/net/usb/rndis_host.c:129 rndis_command() error: doing dma on the stack (&notification)
Dan Carpenter Jan. 4, 2021, 12:38 p.m. UTC | #7
Of course, Smatch didn't trigger any warnings in the wfx driver.  I need
to try re-write this check to use the cross function database so
function pointers are handled.

regards,
dan carpenter
Johan Hovold Jan. 4, 2021, 2:48 p.m. UTC | #8
On Mon, Jan 04, 2021 at 03:34:10PM +0300, Dan Carpenter wrote:

> There is a Smatch warning for this, but I hadn't looked at the results
> in a while. :/  I'm not sure how many are valid.  Some kind of
> annotation would be nice.

> drivers/usb/class/usblp.c:593 usblp_ioctl() error: doing dma on the stack (&newChannel)
> drivers/usb/serial/iuu_phoenix.c:542 iuu_uart_flush() error: doing dma on the stack (&rxcmd)

I only looked at these two but they are are indeed valid, and I've now
fixed them up.

Johan
diff mbox series

Patch

diff --git a/drivers/net/wireless/silabs/wfx/hwio.c b/drivers/net/wireless/silabs/wfx/hwio.c
new file mode 100644
index 000000000000..36fbc5b5d64c
--- /dev/null
+++ b/drivers/net/wireless/silabs/wfx/hwio.c
@@ -0,0 +1,352 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Low-level I/O functions.
+ *
+ * Copyright (c) 2017-2020, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+#include "hwio.h"
+#include "wfx.h"
+#include "bus.h"
+#include "traces.h"
+
+/*
+ * Internal helpers.
+ *
+ * About CONFIG_VMAP_STACK:
+ * When CONFIG_VMAP_STACK is enabled, it is not possible to run DMA on stack
+ * allocated data. Functions below that work with registers (aka functions
+ * ending with "32") automatically reallocate buffers with kmalloc. However,
+ * functions that work with arbitrary length buffers let's caller to handle
+ * memory location. In doubt, enable CONFIG_DEBUG_SG to detect badly located
+ * buffer.
+ */
+
+static int read32(struct wfx_dev *wdev, int reg, u32 *val)
+{
+	int ret;
+	__le32 *tmp = kmalloc(sizeof(u32), GFP_KERNEL);
+
+	*val = ~0; // Never return undefined value
+	if (!tmp)
+		return -ENOMEM;
+	ret = wdev->hwbus_ops->copy_from_io(wdev->hwbus_priv, reg, tmp,
+					    sizeof(u32));
+	if (ret >= 0)
+		*val = le32_to_cpu(*tmp);
+	kfree(tmp);
+	if (ret)
+		dev_err(wdev->dev, "%s: bus communication error: %d\n",
+			__func__, ret);
+	return ret;
+}
+
+static int write32(struct wfx_dev *wdev, int reg, u32 val)
+{
+	int ret;
+	__le32 *tmp = kmalloc(sizeof(u32), GFP_KERNEL);
+
+	if (!tmp)
+		return -ENOMEM;
+	*tmp = cpu_to_le32(val);
+	ret = wdev->hwbus_ops->copy_to_io(wdev->hwbus_priv, reg, tmp,
+					  sizeof(u32));
+	kfree(tmp);
+	if (ret)
+		dev_err(wdev->dev, "%s: bus communication error: %d\n",
+			__func__, ret);
+	return ret;
+}
+
+static int read32_locked(struct wfx_dev *wdev, int reg, u32 *val)
+{
+	int ret;
+
+	wdev->hwbus_ops->lock(wdev->hwbus_priv);
+	ret = read32(wdev, reg, val);
+	_trace_io_read32(reg, *val);
+	wdev->hwbus_ops->unlock(wdev->hwbus_priv);
+	return ret;
+}
+
+static int write32_locked(struct wfx_dev *wdev, int reg, u32 val)
+{
+	int ret;
+
+	wdev->hwbus_ops->lock(wdev->hwbus_priv);
+	ret = write32(wdev, reg, val);
+	_trace_io_write32(reg, val);
+	wdev->hwbus_ops->unlock(wdev->hwbus_priv);
+	return ret;
+}
+
+static int write32_bits_locked(struct wfx_dev *wdev, int reg, u32 mask, u32 val)
+{
+	int ret;
+	u32 val_r, val_w;
+
+	WARN_ON(~mask & val);
+	val &= mask;
+	wdev->hwbus_ops->lock(wdev->hwbus_priv);
+	ret = read32(wdev, reg, &val_r);
+	_trace_io_read32(reg, val_r);
+	if (ret < 0)
+		goto err;
+	val_w = (val_r & ~mask) | val;
+	if (val_w != val_r) {
+		ret = write32(wdev, reg, val_w);
+		_trace_io_write32(reg, val_w);
+	}
+err:
+	wdev->hwbus_ops->unlock(wdev->hwbus_priv);
+	return ret;
+}
+
+static int indirect_read(struct wfx_dev *wdev, int reg, u32 addr,
+			 void *buf, size_t len)
+{
+	int ret;
+	int i;
+	u32 cfg;
+	u32 prefetch;
+
+	WARN_ON(len >= 0x2000);
+	WARN_ON(reg != WFX_REG_AHB_DPORT && reg != WFX_REG_SRAM_DPORT);
+
+	if (reg == WFX_REG_AHB_DPORT)
+		prefetch = CFG_PREFETCH_AHB;
+	else if (reg == WFX_REG_SRAM_DPORT)
+		prefetch = CFG_PREFETCH_SRAM;
+	else
+		return -ENODEV;
+
+	ret = write32(wdev, WFX_REG_BASE_ADDR, addr);
+	if (ret < 0)
+		goto err;
+
+	ret = read32(wdev, WFX_REG_CONFIG, &cfg);
+	if (ret < 0)
+		goto err;
+
+	ret = write32(wdev, WFX_REG_CONFIG, cfg | prefetch);
+	if (ret < 0)
+		goto err;
+
+	for (i = 0; i < 20; i++) {
+		ret = read32(wdev, WFX_REG_CONFIG, &cfg);
+		if (ret < 0)
+			goto err;
+		if (!(cfg & prefetch))
+			break;
+		usleep_range(200, 250);
+	}
+	if (i == 20) {
+		ret = -ETIMEDOUT;
+		goto err;
+	}
+
+	ret = wdev->hwbus_ops->copy_from_io(wdev->hwbus_priv, reg, buf, len);
+
+err:
+	if (ret < 0)
+		memset(buf, 0xFF, len); // Never return undefined value
+	return ret;
+}
+
+static int indirect_write(struct wfx_dev *wdev, int reg, u32 addr,
+			  const void *buf, size_t len)
+{
+	int ret;
+
+	WARN_ON(len >= 0x2000);
+	WARN_ON(reg != WFX_REG_AHB_DPORT && reg != WFX_REG_SRAM_DPORT);
+	ret = write32(wdev, WFX_REG_BASE_ADDR, addr);
+	if (ret < 0)
+		return ret;
+
+	return wdev->hwbus_ops->copy_to_io(wdev->hwbus_priv, reg, buf, len);
+}
+
+static int indirect_read_locked(struct wfx_dev *wdev, int reg, u32 addr,
+				void *buf, size_t len)
+{
+	int ret;
+
+	wdev->hwbus_ops->lock(wdev->hwbus_priv);
+	ret = indirect_read(wdev, reg, addr, buf, len);
+	_trace_io_ind_read(reg, addr, buf, len);
+	wdev->hwbus_ops->unlock(wdev->hwbus_priv);
+	return ret;
+}
+
+static int indirect_write_locked(struct wfx_dev *wdev, int reg, u32 addr,
+				 const void *buf, size_t len)
+{
+	int ret;
+
+	wdev->hwbus_ops->lock(wdev->hwbus_priv);
+	ret = indirect_write(wdev, reg, addr, buf, len);
+	_trace_io_ind_write(reg, addr, buf, len);
+	wdev->hwbus_ops->unlock(wdev->hwbus_priv);
+	return ret;
+}
+
+static int indirect_read32_locked(struct wfx_dev *wdev, int reg,
+				  u32 addr, u32 *val)
+{
+	int ret;
+	__le32 *tmp = kmalloc(sizeof(u32), GFP_KERNEL);
+
+	if (!tmp)
+		return -ENOMEM;
+	wdev->hwbus_ops->lock(wdev->hwbus_priv);
+	ret = indirect_read(wdev, reg, addr, tmp, sizeof(u32));
+	*val = le32_to_cpu(*tmp);
+	_trace_io_ind_read32(reg, addr, *val);
+	wdev->hwbus_ops->unlock(wdev->hwbus_priv);
+	kfree(tmp);
+	return ret;
+}
+
+static int indirect_write32_locked(struct wfx_dev *wdev, int reg,
+				   u32 addr, u32 val)
+{
+	int ret;
+	__le32 *tmp = kmalloc(sizeof(u32), GFP_KERNEL);
+
+	if (!tmp)
+		return -ENOMEM;
+	*tmp = cpu_to_le32(val);
+	wdev->hwbus_ops->lock(wdev->hwbus_priv);
+	ret = indirect_write(wdev, reg, addr, tmp, sizeof(u32));
+	_trace_io_ind_write32(reg, addr, val);
+	wdev->hwbus_ops->unlock(wdev->hwbus_priv);
+	kfree(tmp);
+	return ret;
+}
+
+int wfx_data_read(struct wfx_dev *wdev, void *buf, size_t len)
+{
+	int ret;
+
+	WARN((long)buf & 3, "%s: unaligned buffer", __func__);
+	wdev->hwbus_ops->lock(wdev->hwbus_priv);
+	ret = wdev->hwbus_ops->copy_from_io(wdev->hwbus_priv,
+					    WFX_REG_IN_OUT_QUEUE, buf, len);
+	_trace_io_read(WFX_REG_IN_OUT_QUEUE, buf, len);
+	wdev->hwbus_ops->unlock(wdev->hwbus_priv);
+	if (ret)
+		dev_err(wdev->dev, "%s: bus communication error: %d\n",
+			__func__, ret);
+	return ret;
+}
+
+int wfx_data_write(struct wfx_dev *wdev, const void *buf, size_t len)
+{
+	int ret;
+
+	WARN((long)buf & 3, "%s: unaligned buffer", __func__);
+	wdev->hwbus_ops->lock(wdev->hwbus_priv);
+	ret = wdev->hwbus_ops->copy_to_io(wdev->hwbus_priv,
+					  WFX_REG_IN_OUT_QUEUE, buf, len);
+	_trace_io_write(WFX_REG_IN_OUT_QUEUE, buf, len);
+	wdev->hwbus_ops->unlock(wdev->hwbus_priv);
+	if (ret)
+		dev_err(wdev->dev, "%s: bus communication error: %d\n",
+			__func__, ret);
+	return ret;
+}
+
+int sram_buf_read(struct wfx_dev *wdev, u32 addr, void *buf, size_t len)
+{
+	return indirect_read_locked(wdev, WFX_REG_SRAM_DPORT, addr, buf, len);
+}
+
+int ahb_buf_read(struct wfx_dev *wdev, u32 addr, void *buf, size_t len)
+{
+	return indirect_read_locked(wdev, WFX_REG_AHB_DPORT, addr, buf, len);
+}
+
+int sram_buf_write(struct wfx_dev *wdev, u32 addr, const void *buf, size_t len)
+{
+	return indirect_write_locked(wdev, WFX_REG_SRAM_DPORT, addr, buf, len);
+}
+
+int ahb_buf_write(struct wfx_dev *wdev, u32 addr, const void *buf, size_t len)
+{
+	return indirect_write_locked(wdev, WFX_REG_AHB_DPORT, addr, buf, len);
+}
+
+int sram_reg_read(struct wfx_dev *wdev, u32 addr, u32 *val)
+{
+	return indirect_read32_locked(wdev, WFX_REG_SRAM_DPORT, addr, val);
+}
+
+int ahb_reg_read(struct wfx_dev *wdev, u32 addr, u32 *val)
+{
+	return indirect_read32_locked(wdev, WFX_REG_AHB_DPORT, addr, val);
+}
+
+int sram_reg_write(struct wfx_dev *wdev, u32 addr, u32 val)
+{
+	return indirect_write32_locked(wdev, WFX_REG_SRAM_DPORT, addr, val);
+}
+
+int ahb_reg_write(struct wfx_dev *wdev, u32 addr, u32 val)
+{
+	return indirect_write32_locked(wdev, WFX_REG_AHB_DPORT, addr, val);
+}
+
+int config_reg_read(struct wfx_dev *wdev, u32 *val)
+{
+	return read32_locked(wdev, WFX_REG_CONFIG, val);
+}
+
+int config_reg_write(struct wfx_dev *wdev, u32 val)
+{
+	return write32_locked(wdev, WFX_REG_CONFIG, val);
+}
+
+int config_reg_write_bits(struct wfx_dev *wdev, u32 mask, u32 val)
+{
+	return write32_bits_locked(wdev, WFX_REG_CONFIG, mask, val);
+}
+
+int control_reg_read(struct wfx_dev *wdev, u32 *val)
+{
+	return read32_locked(wdev, WFX_REG_CONTROL, val);
+}
+
+int control_reg_write(struct wfx_dev *wdev, u32 val)
+{
+	return write32_locked(wdev, WFX_REG_CONTROL, val);
+}
+
+int control_reg_write_bits(struct wfx_dev *wdev, u32 mask, u32 val)
+{
+	return write32_bits_locked(wdev, WFX_REG_CONTROL, mask, val);
+}
+
+int igpr_reg_read(struct wfx_dev *wdev, int index, u32 *val)
+{
+	int ret;
+
+	*val = ~0; // Never return undefined value
+	ret = write32_locked(wdev, WFX_REG_SET_GEN_R_W, IGPR_RW | index << 24);
+	if (ret)
+		return ret;
+	ret = read32_locked(wdev, WFX_REG_SET_GEN_R_W, val);
+	if (ret)
+		return ret;
+	*val &= IGPR_VALUE;
+	return ret;
+}
+
+int igpr_reg_write(struct wfx_dev *wdev, int index, u32 val)
+{
+	return write32_locked(wdev, WFX_REG_SET_GEN_R_W, index << 24 | val);
+}
diff --git a/drivers/net/wireless/silabs/wfx/hwio.h b/drivers/net/wireless/silabs/wfx/hwio.h
new file mode 100644
index 000000000000..0b8e4f7157df
--- /dev/null
+++ b/drivers/net/wireless/silabs/wfx/hwio.h
@@ -0,0 +1,75 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Low-level API.
+ *
+ * Copyright (c) 2017-2020, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#ifndef WFX_HWIO_H
+#define WFX_HWIO_H
+
+#include <linux/types.h>
+
+struct wfx_dev;
+
+int wfx_data_read(struct wfx_dev *wdev, void *buf, size_t buf_len);
+int wfx_data_write(struct wfx_dev *wdev, const void *buf, size_t buf_len);
+
+int sram_buf_read(struct wfx_dev *wdev, u32 addr, void *buf, size_t len);
+int sram_buf_write(struct wfx_dev *wdev, u32 addr, const void *buf, size_t len);
+
+int ahb_buf_read(struct wfx_dev *wdev, u32 addr, void *buf, size_t len);
+int ahb_buf_write(struct wfx_dev *wdev, u32 addr, const void *buf, size_t len);
+
+int sram_reg_read(struct wfx_dev *wdev, u32 addr, u32 *val);
+int sram_reg_write(struct wfx_dev *wdev, u32 addr, u32 val);
+
+int ahb_reg_read(struct wfx_dev *wdev, u32 addr, u32 *val);
+int ahb_reg_write(struct wfx_dev *wdev, u32 addr, u32 val);
+
+#define CFG_ERR_SPI_FRAME          0x00000001 // only with SPI
+#define CFG_ERR_SDIO_BUF_MISMATCH  0x00000001 // only with SDIO
+#define CFG_ERR_BUF_UNDERRUN       0x00000002
+#define CFG_ERR_DATA_IN_TOO_LARGE  0x00000004
+#define CFG_ERR_HOST_NO_OUT_QUEUE  0x00000008
+#define CFG_ERR_BUF_OVERRUN        0x00000010
+#define CFG_ERR_DATA_OUT_TOO_LARGE 0x00000020
+#define CFG_ERR_HOST_NO_IN_QUEUE   0x00000040
+#define CFG_ERR_HOST_CRC_MISS      0x00000080 // only with SDIO
+#define CFG_SPI_IGNORE_CS          0x00000080 // only with SPI
+#define CFG_BYTE_ORDER_MASK        0x00000300 // only writable with SPI
+#define     CFG_BYTE_ORDER_BADC    0x00000000
+#define     CFG_BYTE_ORDER_DCBA    0x00000100
+#define     CFG_BYTE_ORDER_ABCD    0x00000200 // SDIO always use this value
+#define CFG_DIRECT_ACCESS_MODE     0x00000400
+#define CFG_PREFETCH_AHB           0x00000800
+#define CFG_DISABLE_CPU_CLK        0x00001000
+#define CFG_PREFETCH_SRAM          0x00002000
+#define CFG_CPU_RESET              0x00004000
+#define CFG_SDIO_DISABLE_IRQ       0x00008000 // only with SDIO
+#define CFG_IRQ_ENABLE_DATA        0x00010000
+#define CFG_IRQ_ENABLE_WRDY        0x00020000
+#define CFG_CLK_RISE_EDGE          0x00040000
+#define CFG_SDIO_DISABLE_CRC_CHK   0x00080000 // only with SDIO
+#define CFG_RESERVED               0x00F00000
+#define CFG_DEVICE_ID_MAJOR        0x07000000
+#define CFG_DEVICE_ID_RESERVED     0x78000000
+#define CFG_DEVICE_ID_TYPE         0x80000000
+int config_reg_read(struct wfx_dev *wdev, u32 *val);
+int config_reg_write(struct wfx_dev *wdev, u32 val);
+int config_reg_write_bits(struct wfx_dev *wdev, u32 mask, u32 val);
+
+#define CTRL_NEXT_LEN_MASK   0x00000FFF
+#define CTRL_WLAN_WAKEUP     0x00001000
+#define CTRL_WLAN_READY      0x00002000
+int control_reg_read(struct wfx_dev *wdev, u32 *val);
+int control_reg_write(struct wfx_dev *wdev, u32 val);
+int control_reg_write_bits(struct wfx_dev *wdev, u32 mask, u32 val);
+
+#define IGPR_RW          0x80000000
+#define IGPR_INDEX       0x7F000000
+#define IGPR_VALUE       0x00FFFFFF
+int igpr_reg_read(struct wfx_dev *wdev, int index, u32 *val);
+int igpr_reg_write(struct wfx_dev *wdev, int index, u32 val);
+
+#endif /* WFX_HWIO_H */