diff mbox

[OpenWrt-Devel,RFC,2/3] kernel: owl-loader for delayed Atheros ath9k fixup

Message ID c8a41648e928986e4d9838fbdd61210a4c1a1699.1457476521.git.chunkeey@googlemail.com
State RFC
Headers show

Commit Message

Christian Lamparter March 8, 2016, 11:14 p.m. UTC
Some devices (like the Cisco Meraki Z1 Cloud Managed Teleworker Gateway)
need to be able to initialize the PCIe wifi device. Normally, this is done
during the early stages of booting linux, because the necessary init code
is read from the memory mapped SPI and passed to pci_enable_ath9k_fixup.
However,this isn't possible for devices which have the init code for the
Atheros chip stored on NAND. Hence, this module can be used to initialze
the chip when the user-space is ready to extract the init code.

Signed-off-by: Christian Lamparter <chunkeey@googlemail.com>
---

Note: We tried several methods to get the AR9280 to work. Initially, we
tried just a request_firmware_nowait from the device's init (z1_setup
in mach-z1.c), but this isn't will not work. The issue is that openwrt
has no /sbin/hotplug helper [0] and procd doesn't scan the
/sys/class/firmware directory for already open request once it started.

We played around with different approaches, and also made a helper-script
to fulfill the outstanding requests on boot [1]. however we found no good
place for it in the start-up scripts. So that's why we developed
the owl-loader module, since it won't have to deal with any procd/hotplug
issues.

Note2: Yes, we'll fix the #include "../arch/mips/ath79/pci-ath9k-fixup.h"
(I think by moving the .h to somewhere in arch/mips/include/...). But first
we need to know if we can do this via the owl-loader... or not ;-) .

[0] <https://www.kernel.org/doc/pending/hotplug.txt> section:
"A note about race conditions (or "why bother with netlink?"):"
details on why it is a bad idea to have /sbin/hotplug

[1] <https://github.com/riptidewave93/Openwrt-Z1/commit/9a38c60a1206b4010fbfb626fc7b2ec69bbe232a>

---
 package/kernel/owl-loader/Makefile                 |  55 +++++++++
 package/kernel/owl-loader/src/Makefile             |   1 +
 package/kernel/owl-loader/src/owl-loader.c         | 133 +++++++++++++++++++++
 .../ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.c |   3 +-
 .../ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.h |   2 +-
 5 files changed, 192 insertions(+), 2 deletions(-)
 create mode 100644 package/kernel/owl-loader/Makefile
 create mode 100644 package/kernel/owl-loader/src/Makefile
 create mode 100644 package/kernel/owl-loader/src/owl-loader.c

Comments

John Crispin March 9, 2016, 5:05 a.m. UTC | #1
On 09/03/2016 00:14, Christian Lamparter wrote:
> Some devices (like the Cisco Meraki Z1 Cloud Managed Teleworker Gateway)
> need to be able to initialize the PCIe wifi device. Normally, this is done
> during the early stages of booting linux, because the necessary init code
> is read from the memory mapped SPI and passed to pci_enable_ath9k_fixup.
> However,this isn't possible for devices which have the init code for the
> Atheros chip stored on NAND. Hence, this module can be used to initialze
> the chip when the user-space is ready to extract the init code.
> 

mtd_read() does not work ? i dont see the problem here. ath9k gets
loaded as a ko and at the time when this happens nand is available so
you can just use the normal fw loader mechanism. e do this on most
targets already and there is code to read eeprom data from mtd in other
targets aswell that you can reuse.

	John

> Signed-off-by: Christian Lamparter <chunkeey@googlemail.com>
> ---
> 
> Note: We tried several methods to get the AR9280 to work. Initially, we
> tried just a request_firmware_nowait from the device's init (z1_setup
> in mach-z1.c), but this isn't will not work. The issue is that openwrt
> has no /sbin/hotplug helper [0] and procd doesn't scan the
> /sys/class/firmware directory for already open request once it started.
> 
> We played around with different approaches, and also made a helper-script
> to fulfill the outstanding requests on boot [1]. however we found no good
> place for it in the start-up scripts. So that's why we developed
> the owl-loader module, since it won't have to deal with any procd/hotplug
> issues.
> 
> Note2: Yes, we'll fix the #include "../arch/mips/ath79/pci-ath9k-fixup.h"
> (I think by moving the .h to somewhere in arch/mips/include/...). But first
> we need to know if we can do this via the owl-loader... or not ;-) .
> 
> [0] <https://www.kernel.org/doc/pending/hotplug.txt> section:
> "A note about race conditions (or "why bother with netlink?"):"
> details on why it is a bad idea to have /sbin/hotplug
> 
> [1] <https://github.com/riptidewave93/Openwrt-Z1/commit/9a38c60a1206b4010fbfb626fc7b2ec69bbe232a>
> 
> ---
>  package/kernel/owl-loader/Makefile                 |  55 +++++++++
>  package/kernel/owl-loader/src/Makefile             |   1 +
>  package/kernel/owl-loader/src/owl-loader.c         | 133 +++++++++++++++++++++
>  .../ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.c |   3 +-
>  .../ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.h |   2 +-
>  5 files changed, 192 insertions(+), 2 deletions(-)
>  create mode 100644 package/kernel/owl-loader/Makefile
>  create mode 100644 package/kernel/owl-loader/src/Makefile
>  create mode 100644 package/kernel/owl-loader/src/owl-loader.c
> 
> diff --git a/package/kernel/owl-loader/Makefile b/package/kernel/owl-loader/Makefile
> new file mode 100644
> index 0000000..7e62fc5
> --- /dev/null
> +++ b/package/kernel/owl-loader/Makefile
> @@ -0,0 +1,55 @@
> +#
> +# Copyright (C) 2016 OpenWrt.org
> +#
> +# This is free software, licensed under the GNU General Public License v2.
> +# See /LICENSE for more information.
> +#
> +
> +include $(TOPDIR)/rules.mk
> +include $(INCLUDE_DIR)/kernel.mk
> +
> +PKG_NAME:=owl-loader
> +PKG_RELEASE:=1
> +
> +include $(INCLUDE_DIR)/package.mk
> +
> +define KernelPackage/owl-loader
> +  SUBMENU:=Network Support
> +  TITLE:=Owl loader for Atheros PCIe Wifi support
> +  DEPENDS:=@PCI_SUPPORT @TARGET_ar71xx +kmod-ath9k
> +  FILES:=$(PKG_BUILD_DIR)/owl-loader.ko
> +  AUTOLOAD:=$(call AutoProbe,owl-loader)
> +  KCONFIG:=
> +endef
> +
> +define KernelPackage/owl-loader/description
> +  Kernel module to initialize Owl Emulation Devices.
> +  This is necessary for the Cisco Meraki Z1.
> +endef
> +
> +EXTRA_KCONFIG:= \
> +	CONFIG_OWL_LOADER=m
> +
> +EXTRA_CFLAGS:= \
> +	$(patsubst CONFIG_%, -DCONFIG_%=1, $(patsubst %=m,%,$(filter %=m,$(EXTRA_KCONFIG)))) \
> +	$(patsubst CONFIG_%, -DCONFIG_%=1, $(patsubst %=y,%,$(filter %=y,$(EXTRA_KCONFIG)))) \
> +
> +MAKE_OPTS:= \
> +	ARCH="$(LINUX_KARCH)" \
> +	CROSS_COMPILE="$(TARGET_CROSS)" \
> +	SUBDIRS="$(PKG_BUILD_DIR)" \
> +	EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \
> +	$(EXTRA_KCONFIG)
> +
> +define Build/Prepare
> +	mkdir -p $(PKG_BUILD_DIR)
> +	$(CP) ./src/* $(PKG_BUILD_DIR)/
> +endef
> +
> +define Build/Compile
> +	$(MAKE) -C "$(LINUX_DIR)" \
> +		$(MAKE_OPTS) \
> +		modules
> +endef
> +
> +$(eval $(call KernelPackage,owl-loader))
> diff --git a/package/kernel/owl-loader/src/Makefile b/package/kernel/owl-loader/src/Makefile
> new file mode 100644
> index 0000000..6b58276
> --- /dev/null
> +++ b/package/kernel/owl-loader/src/Makefile
> @@ -0,0 +1 @@
> +obj-${CONFIG_OWL_LOADER}	+= owl-loader.o
> diff --git a/package/kernel/owl-loader/src/owl-loader.c b/package/kernel/owl-loader/src/owl-loader.c
> new file mode 100644
> index 0000000..36c638e
> --- /dev/null
> +++ b/package/kernel/owl-loader/src/owl-loader.c
> @@ -0,0 +1,133 @@
> +/*
> + * Initialize Owl Emulation Devices (PCIID: 168c:ff1c)
> + *
> + * Copyright (C) 2016 Christian Lamparter <chunkeey@googlemail.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published
> + * by the Free Software Foundation.
> + *
> + * Some devices (like the Cisco Meraki Z1 Cloud Managed Teleworker Gateway)
> + * need to be able to initialize the PCIe wifi device. Normally, this is done
> + * during the early stages of booting linux, because the necessary init code
> + * is read from the memory mapped SPI and passed to pci_enable_ath9k_fixup.
> + * However,this isn't possible for devices which have the init code for the
> + * Atheros chip stored on NAND. Hence, this module can be used to initialze
> + * the chip when the user-space is ready to extract the init code.
> + */
> +#include <linux/module.h>
> +#include <linux/version.h>
> +#include <linux/completion.h>
> +#include <linux/firmware.h>
> +#include <linux/pci.h>
> +#include <linux/platform_device.h>
> +#include <linux/ath9k_platform.h>
> +
> +#include "../arch/mips/ath79/pci-ath9k-fixup.h"
> +
> +struct owl_ctx {
> +	struct completion eeprom_load;
> +};
> +
> +static void owl_fw_cb(const struct firmware *fw, void *context)
> +{
> +	struct pci_dev *pdev = (struct pci_dev *) context;
> +	struct owl_ctx *ctx = (struct owl_ctx *) pci_get_drvdata(pdev);
> +	struct ath9k_platform_data *pdata = dev_get_platdata(&pdev->dev);
> +	struct pci_bus *bus;
> +
> +	complete(&ctx->eeprom_load);
> +
> +	if (!fw) {
> +		dev_err(&pdev->dev, "no '%s' eeprom file received.",
> +		       pdata->eeprom_name);
> +		goto release;
> +	}
> +
> +	if (fw->size > sizeof(pdata->eeprom_data)) {
> +		dev_err(&pdev->dev, "loaded data is too big.");
> +		goto release;
> +	}
> +
> +	pci_lock_rescan_remove();
> +	bus = pdev->bus;
> +
> +	memcpy(pdata->eeprom_data, fw->data, sizeof(pdata->eeprom_data));
> +	/* eeprom has been successfully loaded - pass the data to ath9k
> +	 * but remove the eeprom_name, so it doesn't try to load it too.
> +	 */
> +	pdata->eeprom_name = NULL;
> +
> +	pci_enable_ath9k_fixup(0, pdata->eeprom_data);
> +	pci_stop_and_remove_bus_device(pdev);
> +	/* the device should come back with the proper
> +	 * ProductId. But we have to initiate a rescan.
> +	 */
> +	pci_rescan_bus(bus);
> +	pci_unlock_rescan_remove();
> +
> +release:
> +	release_firmware(fw);
> +}
> +
> +static int owl_probe(struct pci_dev *pdev,
> +		    const struct pci_device_id *id)
> +{
> +	struct owl_ctx *ctx;
> +	struct ath9k_platform_data *pdata;
> +	int err = 0;
> +
> +	if (pcim_enable_device(pdev))
> +		return -EIO;
> +
> +	/* we now have a valid dev->platform_data */
> +	pdata = dev_get_platdata(&pdev->dev);
> +	if (!pdata || !pdata->eeprom_data) {
> +		dev_err(&pdev->dev, "platform data missing or no eeprom file defined.");
> +		return -ENODEV;
> +	}
> +
> +	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
> +	if (!ctx) {
> +		dev_err(&pdev->dev, "failed to alloc device context.");
> +		return -ENOMEM;
> +	}
> +	init_completion(&ctx->eeprom_load);
> +
> +	pci_set_drvdata(pdev, ctx);
> +	err = request_firmware_nowait(THIS_MODULE, true, pdata->eeprom_name,
> +				      &pdev->dev, GFP_KERNEL, pdev, owl_fw_cb);
> +	if (err) {
> +		dev_err(&pdev->dev, "failed to request caldata (%d).", err);
> +		kfree(ctx);
> +	}
> +	return err;
> +}
> +
> +static void owl_remove(struct pci_dev *pdev)
> +{
> +	struct owl_ctx *ctx = pci_get_drvdata(pdev);
> +
> +	if (ctx) {
> +		wait_for_completion(&ctx->eeprom_load);
> +		pci_set_drvdata(pdev, NULL);
> +		kfree(ctx);
> +	}
> +}
> +
> +static const struct pci_device_id owl_pci_table[] = {
> +	/* PCIe Owl Emulation */
> +	{ PCI_VDEVICE(ATHEROS, 0xff1c) }, /* * PCI-E */
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(pci, owl_pci_table);
> +
> +static struct pci_driver owl_driver = {
> +	.name		= "owl-loader",
> +	.id_table	= owl_pci_table,
> +	.probe		= owl_probe,
> +	.remove		= owl_remove,
> +};
> +module_pci_driver(owl_driver);
> +
> +MODULE_LICENSE("GPL");
> diff --git a/target/linux/ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.c b/target/linux/ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.c
> index 2202351..10a7be0 100644
> --- a/target/linux/ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.c
> +++ b/target/linux/ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.c
> @@ -115,7 +115,7 @@ static void ath9k_pci_fixup(struct pci_dev *dev)
>  }
>  DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_ATHEROS, PCI_ANY_ID, ath9k_pci_fixup);
>  
> -void __init pci_enable_ath9k_fixup(unsigned slot, u16 *cal_data)
> +void pci_enable_ath9k_fixup(unsigned slot, u16 *cal_data)
>  {
>  	if (ath9k_num_fixups >= ARRAY_SIZE(ath9k_fixups))
>  		return;
> @@ -124,3 +124,4 @@ void __init pci_enable_ath9k_fixup(unsigned slot, u16 *cal_data)
>  	ath9k_fixups[ath9k_num_fixups].cal_data = cal_data;
>  	ath9k_num_fixups++;
>  }
> +EXPORT_SYMBOL_GPL(pci_enable_ath9k_fixup);
> diff --git a/target/linux/ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.h b/target/linux/ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.h
> index 5794941..4f3ad85 100644
> --- a/target/linux/ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.h
> +++ b/target/linux/ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.h
> @@ -1,6 +1,6 @@
>  #ifndef _PCI_ATH9K_FIXUP
>  #define _PCI_ATH9K_FIXUP
>  
> -void pci_enable_ath9k_fixup(unsigned slot, u16 *cal_data) __init;
> +void pci_enable_ath9k_fixup(unsigned slot, u16 *cal_data);
>  
>  #endif /* _PCI_ATH9K_FIXUP */
>
Martin Blumenstingl March 9, 2016, 6:59 a.m. UTC | #2
On Wed, Mar 9, 2016 at 12:14 AM, Christian Lamparter
<chunkeey@googlemail.com> wrote:
> Some devices (like the Cisco Meraki Z1 Cloud Managed Teleworker Gateway)
> need to be able to initialize the PCIe wifi device. Normally, this is done
> during the early stages of booting linux, because the necessary init code
> is read from the memory mapped SPI and passed to pci_enable_ath9k_fixup.
> However,this isn't possible for devices which have the init code for the
> Atheros chip stored on NAND. Hence, this module can be used to initialze
> the chip when the user-space is ready to extract the init code.
I assume that you are speaking of UBI when you said "NAND".
This case is tricky indeed, because the UBI initialization starts as
late_initcall. Additionally there is some event-handling inside
UBI/ubifs which makes it hard to read from an ubi volume from another
kernel driver.

Mathias and I have already experimented with reading calibration data
from an UBI volume (we started with MAC addresses, but it will be the
same for the ath9k caldata).
It sounds easy at first sight, but it turns out to be tricky.

I would like to find a generic solution for this (which does not
depend on the ar71xx target) because we probably need the same code
for the lantiq target as well.


Martin
Christian Lamparter March 9, 2016, 5:30 p.m. UTC | #3
On Wednesday, March 09, 2016 07:59:28 AM Martin Blumenstingl wrote:
> On Wed, Mar 9, 2016 at 12:14 AM, Christian Lamparter
> <chunkeey@googlemail.com> wrote:
> > Some devices (like the Cisco Meraki Z1 Cloud Managed Teleworker Gateway)
> > need to be able to initialize the PCIe wifi device. Normally, this is done
> > during the early stages of booting linux, because the necessary init code
> > is read from the memory mapped SPI and passed to pci_enable_ath9k_fixup.
> > However,this isn't possible for devices which have the init code for the
> > Atheros chip stored on NAND. Hence, this module can be used to initialze
> > the chip when the user-space is ready to extract the init code.
> I assume that you are speaking of UBI when you said "NAND".
> This case is tricky indeed, because the UBI initialization starts as
> late_initcall. Additionally there is some event-handling inside
> UBI/ubifs which makes it hard to read from an ubi volume from another
> kernel driver.
Yes, handling UBI is the tricky part. We depend on user-space's 
10-ath9k-eeprom script to extract the caldata partition. But this
is what all other ar71xx generic (c-55) and NAND devices do as well.

> Mathias and I have already experimented with reading calibration data
> from an UBI volume (we started with MAC addresses, but it will be the
> same for the ath9k caldata).
> It sounds easy at first sight, but it turns out to be tricky.
>
> I would like to find a generic solution for this (which does not
> depend on the ar71xx target) because we probably need the same code
> for the lantiq target as well.
Except for the call to "pci_enable_ath9k_fixup" [0] (which supplies
the caldata to the ath9k pci fixup routine). The owl-loader should be
platform-independent. it registers a pci driver for the dodgy pci-ids
and does little else than downloading the caldata and remove the old
device and force a pci rescan.

I've looked into lantiq (and BCM63XX) target a bit.

BCM63XX has already a function called pci_enable_ath9k_fixup, but uses
a slightly different signature. Instead of a pointer to the caldata is
supplies an offset.... And BCM63XX also uses it for fixing rt2x00 device
as well.

lantiq on the other hand has copied the func from ar71xx and adapted 
and renamed it, but kept the signature:
ltq_pci_ath_fixup(unsigned slot, u16 *cal_data)
it's in arch/mips/lantiq/xway/pci-ath-fixup.c.


So far, these are all for MIPS, or does anyone know of a different 
architecture or device that need a pci_fixup? I'm not sure how to
handle the rt2x00 though. Because otherwise we could just have
something like register_pci_fixup(slot, cal_data) for now.

Regards,
Christian

[0] <https://dev.openwrt.org/browser/trunk/target/linux/ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.c>
Martin Blumenstingl March 9, 2016, 10:29 p.m. UTC | #4
On Wed, Mar 9, 2016 at 6:30 PM, Christian Lamparter
<chunkeey@googlemail.com> wrote:
> lantiq on the other hand has copied the func from ar71xx and adapted
> and renamed it, but kept the signature:
> ltq_pci_ath_fixup(unsigned slot, u16 *cal_data)
> it's in arch/mips/lantiq/xway/pci-ath-fixup.c.
Maybe we could also move pci-ath-fixup.c to a separate package (or
even the same as owl-loader)?

Seems like there are only two differences between the lantiq and the
ar71xx code:
1. pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, base); vs per-SoC
value in ar71xx
2. swab32(val) when writing the caldata


Martin
Christian Lamparter March 10, 2016, 11:22 a.m. UTC | #5
On Wednesday, March 09, 2016 11:29:54 PM Martin Blumenstingl wrote:
> On Wed, Mar 9, 2016 at 6:30 PM, Christian Lamparter
> <chunkeey@googlemail.com> wrote:
> > lantiq on the other hand has copied the func from ar71xx and adapted
> > and renamed it, but kept the signature:
> > ltq_pci_ath_fixup(unsigned slot, u16 *cal_data)
> > it's in arch/mips/lantiq/xway/pci-ath-fixup.c.
> Maybe we could also move pci-ath-fixup.c to a separate package (or
> even the same as owl-loader)?
>
> Seems like there are only two differences between the lantiq and the
> ar71xx code:
> 1. pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, base); vs per-SoC
> value in ar71xx
> 2. swab32(val) when writing the caldata
Ok, if this common arch-specific pci_ath9k_fixup isn't a thing.
Then what about moving this to ath9k? As this code is duplicated
for several archs in the same way (ar71xx, bcm63xx, lantiq and
also a cut-down version can be found for mpc85xx [0]).

We simply move the platform-specific "base" into ath9k_platform_data
and pass it to the driver and have a bools to control whenever the
vals need to be swapped or not. 

[0] <https://dev.openwrt.org/browser/trunk/target/linux/mpc85xx/files/arch/powerpc/platforms/85xx/tl_wdr4900_v1.c#L71>
Martin Blumenstingl March 10, 2016, 7:23 p.m. UTC | #6
On Thu, Mar 10, 2016 at 12:22 PM, Christian Lamparter
<chunkeey@googlemail.com> wrote:
>> Seems like there are only two differences between the lantiq and the
>> ar71xx code:
>> 1. pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, base); vs per-SoC
>> value in ar71xx
>> 2. swab32(val) when writing the caldata
> Ok, if this common arch-specific pci_ath9k_fixup isn't a thing.
> Then what about moving this to ath9k? As this code is duplicated
> for several archs in the same way (ar71xx, bcm63xx, lantiq and
> also a cut-down version can be found for mpc85xx [0]).
>
> We simply move the platform-specific "base" into ath9k_platform_data
> and pass it to the driver and have a bools to control whenever the
> vals need to be swapped or not.
sounds fine to me

@Felix: I added you because you are the mac80211 package maintainer.
would be great if you could comment on this patch and the whole idea
in this thread.
diff mbox

Patch

diff --git a/package/kernel/owl-loader/Makefile b/package/kernel/owl-loader/Makefile
new file mode 100644
index 0000000..7e62fc5
--- /dev/null
+++ b/package/kernel/owl-loader/Makefile
@@ -0,0 +1,55 @@ 
+#
+# Copyright (C) 2016 OpenWrt.org
+#
+# This is free software, licensed under the GNU General Public License v2.
+# See /LICENSE for more information.
+#
+
+include $(TOPDIR)/rules.mk
+include $(INCLUDE_DIR)/kernel.mk
+
+PKG_NAME:=owl-loader
+PKG_RELEASE:=1
+
+include $(INCLUDE_DIR)/package.mk
+
+define KernelPackage/owl-loader
+  SUBMENU:=Network Support
+  TITLE:=Owl loader for Atheros PCIe Wifi support
+  DEPENDS:=@PCI_SUPPORT @TARGET_ar71xx +kmod-ath9k
+  FILES:=$(PKG_BUILD_DIR)/owl-loader.ko
+  AUTOLOAD:=$(call AutoProbe,owl-loader)
+  KCONFIG:=
+endef
+
+define KernelPackage/owl-loader/description
+  Kernel module to initialize Owl Emulation Devices.
+  This is necessary for the Cisco Meraki Z1.
+endef
+
+EXTRA_KCONFIG:= \
+	CONFIG_OWL_LOADER=m
+
+EXTRA_CFLAGS:= \
+	$(patsubst CONFIG_%, -DCONFIG_%=1, $(patsubst %=m,%,$(filter %=m,$(EXTRA_KCONFIG)))) \
+	$(patsubst CONFIG_%, -DCONFIG_%=1, $(patsubst %=y,%,$(filter %=y,$(EXTRA_KCONFIG)))) \
+
+MAKE_OPTS:= \
+	ARCH="$(LINUX_KARCH)" \
+	CROSS_COMPILE="$(TARGET_CROSS)" \
+	SUBDIRS="$(PKG_BUILD_DIR)" \
+	EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \
+	$(EXTRA_KCONFIG)
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+	$(CP) ./src/* $(PKG_BUILD_DIR)/
+endef
+
+define Build/Compile
+	$(MAKE) -C "$(LINUX_DIR)" \
+		$(MAKE_OPTS) \
+		modules
+endef
+
+$(eval $(call KernelPackage,owl-loader))
diff --git a/package/kernel/owl-loader/src/Makefile b/package/kernel/owl-loader/src/Makefile
new file mode 100644
index 0000000..6b58276
--- /dev/null
+++ b/package/kernel/owl-loader/src/Makefile
@@ -0,0 +1 @@ 
+obj-${CONFIG_OWL_LOADER}	+= owl-loader.o
diff --git a/package/kernel/owl-loader/src/owl-loader.c b/package/kernel/owl-loader/src/owl-loader.c
new file mode 100644
index 0000000..36c638e
--- /dev/null
+++ b/package/kernel/owl-loader/src/owl-loader.c
@@ -0,0 +1,133 @@ 
+/*
+ * Initialize Owl Emulation Devices (PCIID: 168c:ff1c)
+ *
+ * Copyright (C) 2016 Christian Lamparter <chunkeey@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ * Some devices (like the Cisco Meraki Z1 Cloud Managed Teleworker Gateway)
+ * need to be able to initialize the PCIe wifi device. Normally, this is done
+ * during the early stages of booting linux, because the necessary init code
+ * is read from the memory mapped SPI and passed to pci_enable_ath9k_fixup.
+ * However,this isn't possible for devices which have the init code for the
+ * Atheros chip stored on NAND. Hence, this module can be used to initialze
+ * the chip when the user-space is ready to extract the init code.
+ */
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/completion.h>
+#include <linux/firmware.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/ath9k_platform.h>
+
+#include "../arch/mips/ath79/pci-ath9k-fixup.h"
+
+struct owl_ctx {
+	struct completion eeprom_load;
+};
+
+static void owl_fw_cb(const struct firmware *fw, void *context)
+{
+	struct pci_dev *pdev = (struct pci_dev *) context;
+	struct owl_ctx *ctx = (struct owl_ctx *) pci_get_drvdata(pdev);
+	struct ath9k_platform_data *pdata = dev_get_platdata(&pdev->dev);
+	struct pci_bus *bus;
+
+	complete(&ctx->eeprom_load);
+
+	if (!fw) {
+		dev_err(&pdev->dev, "no '%s' eeprom file received.",
+		       pdata->eeprom_name);
+		goto release;
+	}
+
+	if (fw->size > sizeof(pdata->eeprom_data)) {
+		dev_err(&pdev->dev, "loaded data is too big.");
+		goto release;
+	}
+
+	pci_lock_rescan_remove();
+	bus = pdev->bus;
+
+	memcpy(pdata->eeprom_data, fw->data, sizeof(pdata->eeprom_data));
+	/* eeprom has been successfully loaded - pass the data to ath9k
+	 * but remove the eeprom_name, so it doesn't try to load it too.
+	 */
+	pdata->eeprom_name = NULL;
+
+	pci_enable_ath9k_fixup(0, pdata->eeprom_data);
+	pci_stop_and_remove_bus_device(pdev);
+	/* the device should come back with the proper
+	 * ProductId. But we have to initiate a rescan.
+	 */
+	pci_rescan_bus(bus);
+	pci_unlock_rescan_remove();
+
+release:
+	release_firmware(fw);
+}
+
+static int owl_probe(struct pci_dev *pdev,
+		    const struct pci_device_id *id)
+{
+	struct owl_ctx *ctx;
+	struct ath9k_platform_data *pdata;
+	int err = 0;
+
+	if (pcim_enable_device(pdev))
+		return -EIO;
+
+	/* we now have a valid dev->platform_data */
+	pdata = dev_get_platdata(&pdev->dev);
+	if (!pdata || !pdata->eeprom_data) {
+		dev_err(&pdev->dev, "platform data missing or no eeprom file defined.");
+		return -ENODEV;
+	}
+
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx) {
+		dev_err(&pdev->dev, "failed to alloc device context.");
+		return -ENOMEM;
+	}
+	init_completion(&ctx->eeprom_load);
+
+	pci_set_drvdata(pdev, ctx);
+	err = request_firmware_nowait(THIS_MODULE, true, pdata->eeprom_name,
+				      &pdev->dev, GFP_KERNEL, pdev, owl_fw_cb);
+	if (err) {
+		dev_err(&pdev->dev, "failed to request caldata (%d).", err);
+		kfree(ctx);
+	}
+	return err;
+}
+
+static void owl_remove(struct pci_dev *pdev)
+{
+	struct owl_ctx *ctx = pci_get_drvdata(pdev);
+
+	if (ctx) {
+		wait_for_completion(&ctx->eeprom_load);
+		pci_set_drvdata(pdev, NULL);
+		kfree(ctx);
+	}
+}
+
+static const struct pci_device_id owl_pci_table[] = {
+	/* PCIe Owl Emulation */
+	{ PCI_VDEVICE(ATHEROS, 0xff1c) }, /* * PCI-E */
+	{ },
+};
+MODULE_DEVICE_TABLE(pci, owl_pci_table);
+
+static struct pci_driver owl_driver = {
+	.name		= "owl-loader",
+	.id_table	= owl_pci_table,
+	.probe		= owl_probe,
+	.remove		= owl_remove,
+};
+module_pci_driver(owl_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/target/linux/ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.c b/target/linux/ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.c
index 2202351..10a7be0 100644
--- a/target/linux/ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.c
+++ b/target/linux/ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.c
@@ -115,7 +115,7 @@  static void ath9k_pci_fixup(struct pci_dev *dev)
 }
 DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_ATHEROS, PCI_ANY_ID, ath9k_pci_fixup);
 
-void __init pci_enable_ath9k_fixup(unsigned slot, u16 *cal_data)
+void pci_enable_ath9k_fixup(unsigned slot, u16 *cal_data)
 {
 	if (ath9k_num_fixups >= ARRAY_SIZE(ath9k_fixups))
 		return;
@@ -124,3 +124,4 @@  void __init pci_enable_ath9k_fixup(unsigned slot, u16 *cal_data)
 	ath9k_fixups[ath9k_num_fixups].cal_data = cal_data;
 	ath9k_num_fixups++;
 }
+EXPORT_SYMBOL_GPL(pci_enable_ath9k_fixup);
diff --git a/target/linux/ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.h b/target/linux/ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.h
index 5794941..4f3ad85 100644
--- a/target/linux/ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.h
+++ b/target/linux/ar71xx/files/arch/mips/ath79/pci-ath9k-fixup.h
@@ -1,6 +1,6 @@ 
 #ifndef _PCI_ATH9K_FIXUP
 #define _PCI_ATH9K_FIXUP
 
-void pci_enable_ath9k_fixup(unsigned slot, u16 *cal_data) __init;
+void pci_enable_ath9k_fixup(unsigned slot, u16 *cal_data);
 
 #endif /* _PCI_ATH9K_FIXUP */