summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--target/linux/patches/6.15.6/orinoco.patch12933
1 files changed, 12933 insertions, 0 deletions
diff --git a/target/linux/patches/6.15.6/orinoco.patch b/target/linux/patches/6.15.6/orinoco.patch
new file mode 100644
index 000000000..6401013fd
--- /dev/null
+++ b/target/linux/patches/6.15.6/orinoco.patch
@@ -0,0 +1,12933 @@
+diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/Kconfig linux-6.15.6/drivers/net/wireless/intersil/Kconfig
+--- linux-6.15.6.orig/drivers/net/wireless/intersil/Kconfig 2025-07-10 16:08:55.000000000 +0200
++++ linux-6.15.6/drivers/net/wireless/intersil/Kconfig 2025-08-03 00:20:27.566744589 +0200
+@@ -12,6 +12,7 @@
+
+ if WLAN_VENDOR_INTERSIL
+
++source "drivers/net/wireless/intersil/orinoco/Kconfig"
+ source "drivers/net/wireless/intersil/p54/Kconfig"
+
+ endif # WLAN_VENDOR_INTERSIL
+diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/Makefile linux-6.15.6/drivers/net/wireless/intersil/Makefile
+--- linux-6.15.6.orig/drivers/net/wireless/intersil/Makefile 2025-07-10 16:08:55.000000000 +0200
++++ linux-6.15.6/drivers/net/wireless/intersil/Makefile 2025-08-03 00:20:54.161967083 +0200
+@@ -1,2 +1,3 @@
+ # SPDX-License-Identifier: GPL-2.0-only
++obj-$(CONFIG_HERMES) += orinoco/
+ obj-$(CONFIG_P54_COMMON) += p54/
+diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/airport.c linux-6.15.6/drivers/net/wireless/intersil/orinoco/airport.c
+--- linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/airport.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-6.15.6/drivers/net/wireless/intersil/orinoco/airport.c 2025-08-03 15:01:59.313131673 +0200
+@@ -0,0 +1,268 @@
++/* airport.c
++ *
++ * A driver for "Hermes" chipset based Apple Airport wireless
++ * card.
++ *
++ * Copyright notice & release notes in file main.c
++ *
++ * Note specific to airport stub:
++ *
++ * 0.05 : first version of the new split driver
++ * 0.06 : fix possible hang on powerup, add sleep support
++ */
++
++#define DRIVER_NAME "airport"
++#define PFX DRIVER_NAME ": "
++
++#include <linux/module.h>
++#include <linux/kernel.h>
++#include <linux/init.h>
++#include <linux/delay.h>
++#include <linux/mod_devicetable.h>
++#include <asm/pmac_feature.h>
++
++#include "orinoco.h"
++
++#define AIRPORT_IO_LEN (0x1000) /* one page */
++
++struct airport {
++ struct macio_dev *mdev;
++ void __iomem *vaddr;
++ unsigned int irq;
++ int irq_requested;
++ int ndev_registered;
++};
++
++static int
++airport_suspend(struct macio_dev *mdev, pm_message_t state)
++{
++ struct orinoco_private *priv = dev_get_drvdata(&mdev->ofdev.dev);
++ struct net_device *dev = priv->ndev;
++ struct airport *card = priv->card;
++ unsigned long flags;
++ int err;
++
++ printk(KERN_DEBUG "%s: Airport entering sleep mode\n", dev->name);
++
++ err = orinoco_lock(priv, &flags);
++ if (err) {
++ printk(KERN_ERR "%s: hw_unavailable on PBOOK_SLEEP_NOW\n",
++ dev->name);
++ return 0;
++ }
++
++ orinoco_down(priv);
++ orinoco_unlock(priv, &flags);
++
++ disable_irq(card->irq);
++ pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE,
++ macio_get_of_node(mdev), 0, 0);
++
++ return 0;
++}
++
++static int
++airport_resume(struct macio_dev *mdev)
++{
++ struct orinoco_private *priv = dev_get_drvdata(&mdev->ofdev.dev);
++ struct net_device *dev = priv->ndev;
++ struct airport *card = priv->card;
++ unsigned long flags;
++ int err;
++
++ printk(KERN_DEBUG "%s: Airport waking up\n", dev->name);
++
++ pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE,
++ macio_get_of_node(mdev), 0, 1);
++ msleep(200);
++
++ enable_irq(card->irq);
++
++ priv->hw.ops->lock_irqsave(&priv->lock, &flags);
++ err = orinoco_up(priv);
++ priv->hw.ops->unlock_irqrestore(&priv->lock, &flags);
++
++ return err;
++}
++
++static int
++airport_detach(struct macio_dev *mdev)
++{
++ struct orinoco_private *priv = dev_get_drvdata(&mdev->ofdev.dev);
++ struct airport *card = priv->card;
++
++ if (card->ndev_registered)
++ orinoco_if_del(priv);
++ card->ndev_registered = 0;
++
++ if (card->irq_requested)
++ free_irq(card->irq, priv);
++ card->irq_requested = 0;
++
++ if (card->vaddr)
++ iounmap(card->vaddr);
++ card->vaddr = NULL;
++
++ macio_release_resource(mdev, 0);
++
++ pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE,
++ macio_get_of_node(mdev), 0, 0);
++ ssleep(1);
++
++ macio_set_drvdata(mdev, NULL);
++ free_orinocodev(priv);
++
++ return 0;
++}
++
++static int airport_hard_reset(struct orinoco_private *priv)
++{
++ /* It would be nice to power cycle the Airport for a real hard
++ * reset, but for some reason although it appears to
++ * re-initialize properly, it falls in a screaming heap
++ * shortly afterwards. */
++#if 0
++ struct airport *card = priv->card;
++
++ /* Vitally important. If we don't do this it seems we get an
++ * interrupt somewhere during the power cycle, since
++ * hw_unavailable is already set it doesn't get ACKed, we get
++ * into an interrupt loop and the PMU decides to turn us
++ * off. */
++ disable_irq(card->irq);
++
++ pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE,
++ macio_get_of_node(card->mdev), 0, 0);
++ ssleep(1);
++ pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE,
++ macio_get_of_node(card->mdev), 0, 1);
++ ssleep(1);
++
++ enable_irq(card->irq);
++ ssleep(1);
++#endif
++
++ return 0;
++}
++
++static int
++airport_attach(struct macio_dev *mdev, const struct of_device_id *match)
++{
++ struct orinoco_private *priv;
++ struct airport *card;
++ unsigned long phys_addr;
++ struct hermes *hw;
++
++ if (macio_resource_count(mdev) < 1 || macio_irq_count(mdev) < 1) {
++ printk(KERN_ERR PFX "Wrong interrupt/addresses in OF tree\n");
++ return -ENODEV;
++ }
++
++ /* Allocate space for private device-specific data */
++ priv = alloc_orinocodev(sizeof(*card), &mdev->ofdev.dev,
++ airport_hard_reset, NULL);
++ if (!priv) {
++ printk(KERN_ERR PFX "Cannot allocate network device\n");
++ return -ENODEV;
++ }
++ card = priv->card;
++
++ hw = &priv->hw;
++ card->mdev = mdev;
++
++ if (macio_request_resource(mdev, 0, DRIVER_NAME)) {
++ printk(KERN_ERR PFX "can't request IO resource !\n");
++ free_orinocodev(priv);
++ return -EBUSY;
++ }
++
++ macio_set_drvdata(mdev, priv);
++
++ /* Setup interrupts & base address */
++ card->irq = macio_irq(mdev, 0);
++ phys_addr = macio_resource_start(mdev, 0); /* Physical address */
++ printk(KERN_DEBUG PFX "Physical address %lx\n", phys_addr);
++ card->vaddr = ioremap(phys_addr, AIRPORT_IO_LEN);
++ if (!card->vaddr) {
++ printk(KERN_ERR PFX "ioremap() failed\n");
++ goto failed;
++ }
++
++ hermes_struct_init(hw, card->vaddr, HERMES_16BIT_REGSPACING);
++
++ /* Power up card */
++ pmac_call_feature(PMAC_FTR_AIRPORT_ENABLE,
++ macio_get_of_node(mdev), 0, 1);
++ ssleep(1);
++
++ /* Reset it before we get the interrupt */
++ hw->ops->init(hw);
++
++ if (request_irq(card->irq, orinoco_interrupt, 0, DRIVER_NAME, priv)) {
++ printk(KERN_ERR PFX "Couldn't get IRQ %d\n", card->irq);
++ goto failed;
++ }
++ card->irq_requested = 1;
++
++ /* Initialise the main driver */
++ if (orinoco_init(priv) != 0) {
++ printk(KERN_ERR PFX "orinoco_init() failed\n");
++ goto failed;
++ }
++
++ /* Register an interface with the stack */
++ if (orinoco_if_add(priv, phys_addr, card->irq, NULL) != 0) {
++ printk(KERN_ERR PFX "orinoco_if_add() failed\n");
++ goto failed;
++ }
++ card->ndev_registered = 1;
++ return 0;
++ failed:
++ airport_detach(mdev);
++ return -ENODEV;
++} /* airport_attach */
++
++
++static char version[] __initdata = DRIVER_NAME " " DRIVER_VERSION
++ " (Benjamin Herrenschmidt <benh@kernel.crashing.org>)";
++MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
++MODULE_DESCRIPTION("Driver for the Apple Airport wireless card.");
++MODULE_LICENSE("Dual MPL/GPL");
++
++static const struct of_device_id airport_match[] = {
++ {
++ .name = "radio",
++ },
++ {},
++};
++
++MODULE_DEVICE_TABLE(of, airport_match);
++
++static struct macio_driver airport_driver = {
++ .driver = {
++ .name = DRIVER_NAME,
++ .owner = THIS_MODULE,
++ .of_match_table = airport_match,
++ },
++ .probe = airport_attach,
++ .remove = airport_detach,
++ .suspend = airport_suspend,
++ .resume = airport_resume,
++};
++
++static int __init
++init_airport(void)
++{
++ printk(KERN_DEBUG "%s\n", version);
++
++ return macio_register_driver(&airport_driver);
++}
++
++static void __exit
++exit_airport(void)
++{
++ macio_unregister_driver(&airport_driver);
++}
++
++module_init(init_airport);
++module_exit(exit_airport);
+diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/cfg.c linux-6.15.6/drivers/net/wireless/intersil/orinoco/cfg.c
+--- linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/cfg.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-6.15.6/drivers/net/wireless/intersil/orinoco/cfg.c 2025-08-03 16:35:08.118093622 +0200
+@@ -0,0 +1,292 @@
++/* cfg80211 support
++ *
++ * See copyright notice in main.c
++ */
++#include <linux/ieee80211.h>
++#include <net/cfg80211.h>
++#include "hw.h"
++#include "main.h"
++#include "orinoco.h"
++
++#include "cfg.h"
++
++/* Supported bitrates. Must agree with hw.c */
++static struct ieee80211_rate orinoco_rates[] = {
++ { .bitrate = 10 },
++ { .bitrate = 20 },
++ { .bitrate = 55 },
++ { .bitrate = 110 },
++};
++
++static const void * const orinoco_wiphy_privid = &orinoco_wiphy_privid;
++
++/* Called after orinoco_private is allocated. */
++void orinoco_wiphy_init(struct wiphy *wiphy)
++{
++ struct orinoco_private *priv = wiphy_priv(wiphy);
++
++ wiphy->privid = orinoco_wiphy_privid;
++
++ set_wiphy_dev(wiphy, priv->dev);
++}
++
++/* Called after firmware is initialised */
++int orinoco_wiphy_register(struct wiphy *wiphy)
++{
++ struct orinoco_private *priv = wiphy_priv(wiphy);
++ int i, channels = 0;
++
++ if (priv->firmware_type == FIRMWARE_TYPE_AGERE)
++ wiphy->max_scan_ssids = 1;
++ else
++ wiphy->max_scan_ssids = 0;
++
++ wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION);
++
++ /* TODO: should we set if we only have demo ad-hoc?
++ * (priv->has_port3)
++ */
++ if (priv->has_ibss)
++ wiphy->interface_modes |= BIT(NL80211_IFTYPE_ADHOC);
++
++ if (!priv->broken_monitor || force_monitor)
++ wiphy->interface_modes |= BIT(NL80211_IFTYPE_MONITOR);
++
++ priv->band.bitrates = orinoco_rates;
++ priv->band.n_bitrates = ARRAY_SIZE(orinoco_rates);
++
++ /* Only support channels allowed by the card EEPROM */
++ for (i = 0; i < NUM_CHANNELS; i++) {
++ if (priv->channel_mask & (1 << i)) {
++ priv->channels[i].center_freq =
++ ieee80211_channel_to_frequency(i + 1,
++ NL80211_BAND_2GHZ);
++ channels++;
++ }
++ }
++ priv->band.channels = priv->channels;
++ priv->band.n_channels = channels;
++
++ wiphy->bands[NL80211_BAND_2GHZ] = &priv->band;
++ wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM;
++
++ i = 0;
++ if (priv->has_wep) {
++ priv->cipher_suites[i] = WLAN_CIPHER_SUITE_WEP40;
++ i++;
++
++ if (priv->has_big_wep) {
++ priv->cipher_suites[i] = WLAN_CIPHER_SUITE_WEP104;
++ i++;
++ }
++ }
++ if (priv->has_wpa) {
++ priv->cipher_suites[i] = WLAN_CIPHER_SUITE_TKIP;
++ i++;
++ }
++ wiphy->cipher_suites = priv->cipher_suites;
++ wiphy->n_cipher_suites = i;
++
++ wiphy->rts_threshold = priv->rts_thresh;
++ if (!priv->has_mwo)
++ wiphy->frag_threshold = priv->frag_thresh + 1;
++ wiphy->retry_short = priv->short_retry_limit;
++ wiphy->retry_long = priv->long_retry_limit;
++
++ return wiphy_register(wiphy);
++}
++
++static int orinoco_change_vif(struct wiphy *wiphy, struct net_device *dev,
++ enum nl80211_iftype type,
++ struct vif_params *params)
++{
++ struct orinoco_private *priv = wiphy_priv(wiphy);
++ int err = 0;
++ unsigned long lock;
++
++ if (orinoco_lock(priv, &lock) != 0)
++ return -EBUSY;
++
++ switch (type) {
++ case NL80211_IFTYPE_ADHOC:
++ if (!priv->has_ibss && !priv->has_port3)
++ err = -EINVAL;
++ break;
++
++ case NL80211_IFTYPE_STATION:
++ break;
++
++ case NL80211_IFTYPE_MONITOR:
++ if (priv->broken_monitor && !force_monitor) {
++ wiphy_warn(wiphy,
++ "Monitor mode support is buggy in this firmware, not enabling\n");
++ err = -EINVAL;
++ }
++ break;
++
++ default:
++ err = -EINVAL;
++ }
++
++ if (!err) {
++ priv->iw_mode = type;
++ set_port_type(priv);
++ err = orinoco_commit(priv);
++ }
++
++ orinoco_unlock(priv, &lock);
++
++ return err;
++}
++
++static int orinoco_scan(struct wiphy *wiphy,
++ struct cfg80211_scan_request *request)
++{
++ struct orinoco_private *priv = wiphy_priv(wiphy);
++ int err;
++
++ if (!request)
++ return -EINVAL;
++
++ if (priv->scan_request && priv->scan_request != request)
++ return -EBUSY;
++
++ priv->scan_request = request;
++
++ err = orinoco_hw_trigger_scan(priv, request->ssids);
++ /* On error the we aren't processing the request */
++ if (err)
++ priv->scan_request = NULL;
++
++ return err;
++}
++
++static int orinoco_set_monitor_channel(struct wiphy *wiphy,
++ struct net_device *dev,
++ struct cfg80211_chan_def *chandef)
++{
++ struct orinoco_private *priv = wiphy_priv(wiphy);
++ int err = 0;
++ unsigned long flags;
++ int channel;
++
++ if (!chandef->chan)
++ return -EINVAL;
++
++ if (cfg80211_get_chandef_type(chandef) != NL80211_CHAN_NO_HT)
++ return -EINVAL;
++
++ if (chandef->chan->band != NL80211_BAND_2GHZ)
++ return -EINVAL;
++
++ channel = ieee80211_frequency_to_channel(chandef->chan->center_freq);
++
++ if ((channel < 1) || (channel > NUM_CHANNELS) ||
++ !(priv->channel_mask & (1 << (channel - 1))))
++ return -EINVAL;
++
++ if (orinoco_lock(priv, &flags) != 0)
++ return -EBUSY;
++
++ priv->channel = channel;
++ if (priv->iw_mode == NL80211_IFTYPE_MONITOR) {
++ /* Fast channel change - no commit if successful */
++ struct hermes *hw = &priv->hw;
++ err = hw->ops->cmd_wait(hw, HERMES_CMD_TEST |
++ HERMES_TEST_SET_CHANNEL,
++ channel, NULL);
++ }
++ orinoco_unlock(priv, &flags);
++
++ return err;
++}
++
++static int orinoco_set_wiphy_params(struct wiphy *wiphy, u32 changed)
++{
++ struct orinoco_private *priv = wiphy_priv(wiphy);
++ int frag_value = -1;
++ int rts_value = -1;
++ int err = 0;
++
++ if (changed & WIPHY_PARAM_RETRY_SHORT) {
++ /* Setting short retry not supported */
++ err = -EINVAL;
++ }
++
++ if (changed & WIPHY_PARAM_RETRY_LONG) {
++ /* Setting long retry not supported */
++ err = -EINVAL;
++ }
++
++ if (changed & WIPHY_PARAM_FRAG_THRESHOLD) {
++ /* Set fragmentation */
++ if (priv->has_mwo) {
++ if (wiphy->frag_threshold == -1)
++ frag_value = 0;
++ else {
++ printk(KERN_WARNING "%s: Fixed fragmentation "
++ "is not supported on this firmware. "
++ "Using MWO robust instead.\n",
++ priv->ndev->name);
++ frag_value = 1;
++ }
++ } else {
++ if (wiphy->frag_threshold == -1)
++ frag_value = 2346;
++ else if ((wiphy->frag_threshold < 257) ||
++ (wiphy->frag_threshold > 2347))
++ err = -EINVAL;
++ else
++ /* cfg80211 value is 257-2347 (odd only)
++ * orinoco rid has range 256-2346 (even only) */
++ frag_value = wiphy->frag_threshold & ~0x1;
++ }
++ }
++
++ if (changed & WIPHY_PARAM_RTS_THRESHOLD) {
++ /* Set RTS.
++ *
++ * Prism documentation suggests default of 2432,
++ * and a range of 0-3000.
++ *
++ * Current implementation uses 2347 as the default and
++ * the upper limit.
++ */
++
++ if (wiphy->rts_threshold == -1)
++ rts_value = 2347;
++ else if (wiphy->rts_threshold > 2347)
++ err = -EINVAL;
++ else
++ rts_value = wiphy->rts_threshold;
++ }
++
++ if (!err) {
++ unsigned long flags;
++
++ if (orinoco_lock(priv, &flags) != 0)
++ return -EBUSY;
++
++ if (frag_value >= 0) {
++ if (priv->has_mwo)
++ priv->mwo_robust = frag_value;
++ else
++ priv->frag_thresh = frag_value;
++ }
++ if (rts_value >= 0)
++ priv->rts_thresh = rts_value;
++
++ err = orinoco_commit(priv);
++
++ orinoco_unlock(priv, &flags);
++ }
++
++ return err;
++}
++
++const struct cfg80211_ops orinoco_cfg_ops = {
++ .change_virtual_intf = orinoco_change_vif,
++ .set_monitor_channel = orinoco_set_monitor_channel,
++ .scan = orinoco_scan,
++ .set_wiphy_params = orinoco_set_wiphy_params,
++};
+diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/cfg.h linux-6.15.6/drivers/net/wireless/intersil/orinoco/cfg.h
+--- linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/cfg.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-6.15.6/drivers/net/wireless/intersil/orinoco/cfg.h 2025-08-03 15:01:59.313131673 +0200
+@@ -0,0 +1,15 @@
++/* cfg80211 support.
++ *
++ * See copyright notice in main.c
++ */
++#ifndef ORINOCO_CFG_H
++#define ORINOCO_CFG_H
++
++#include <net/cfg80211.h>
++
++extern const struct cfg80211_ops orinoco_cfg_ops;
++
++void orinoco_wiphy_init(struct wiphy *wiphy);
++int orinoco_wiphy_register(struct wiphy *wiphy);
++
++#endif /* ORINOCO_CFG_H */
+diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/fw.c linux-6.15.6/drivers/net/wireless/intersil/orinoco/fw.c
+--- linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/fw.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-6.15.6/drivers/net/wireless/intersil/orinoco/fw.c 2025-08-03 15:01:59.313131673 +0200
+@@ -0,0 +1,387 @@
++/* Firmware file reading and download helpers
++ *
++ * See copyright notice in main.c
++ */
++#include <linux/kernel.h>
++#include <linux/slab.h>
++#include <linux/firmware.h>
++#include <linux/device.h>
++#include <linux/module.h>
++
++#include "hermes.h"
++#include "hermes_dld.h"
++#include "orinoco.h"
++
++#include "fw.h"
++
++/* End markers (for Symbol firmware only) */
++#define TEXT_END 0x1A /* End of text header */
++
++struct fw_info {
++ char *pri_fw;
++ char *sta_fw;
++ char *ap_fw;
++ u32 pda_addr;
++ u16 pda_size;
++};
++
++static const struct fw_info orinoco_fw[] = {
++ { NULL, "agere_sta_fw.bin", "agere_ap_fw.bin", 0x00390000, 1000 },
++ { NULL, "prism_sta_fw.bin", "prism_ap_fw.bin", 0, 1024 },
++ { "symbol_sp24t_prim_fw", "symbol_sp24t_sec_fw", NULL, 0x00003100, 512 }
++};
++MODULE_FIRMWARE("agere_sta_fw.bin");
++MODULE_FIRMWARE("agere_ap_fw.bin");
++MODULE_FIRMWARE("prism_sta_fw.bin");
++MODULE_FIRMWARE("prism_ap_fw.bin");
++MODULE_FIRMWARE("symbol_sp24t_prim_fw");
++MODULE_FIRMWARE("symbol_sp24t_sec_fw");
++
++/* Structure used to access fields in FW
++ * Make sure LE decoding macros are used
++ */
++struct orinoco_fw_header {
++ char hdr_vers[6]; /* ASCII string for header version */
++ __le16 headersize; /* Total length of header */
++ __le32 entry_point; /* NIC entry point */
++ __le32 blocks; /* Number of blocks to program */
++ __le32 block_offset; /* Offset of block data from eof header */
++ __le32 pdr_offset; /* Offset to PDR data from eof header */
++ __le32 pri_offset; /* Offset to primary plug data */
++ __le32 compat_offset; /* Offset to compatibility data*/
++ char signature[]; /* FW signature length headersize-20 */
++} __packed;
++
++/* Check the range of various header entries. Return a pointer to a
++ * description of the problem, or NULL if everything checks out. */
++static const char *validate_fw(const struct orinoco_fw_header *hdr, size_t len)
++{
++ u16 hdrsize;
++
++ if (len < sizeof(*hdr))
++ return "image too small";
++ if (memcmp(hdr->hdr_vers, "HFW", 3) != 0)
++ return "format not recognised";
++
++ hdrsize = le16_to_cpu(hdr->headersize);
++ if (hdrsize > len)
++ return "bad headersize";
++ if ((hdrsize + le32_to_cpu(hdr->block_offset)) > len)
++ return "bad block offset";
++ if ((hdrsize + le32_to_cpu(hdr->pdr_offset)) > len)
++ return "bad PDR offset";
++ if ((hdrsize + le32_to_cpu(hdr->pri_offset)) > len)
++ return "bad PRI offset";
++ if ((hdrsize + le32_to_cpu(hdr->compat_offset)) > len)
++ return "bad compat offset";
++
++ /* TODO: consider adding a checksum or CRC to the firmware format */
++ return NULL;
++}
++
++#if defined(CONFIG_HERMES_CACHE_FW_ON_INIT) || defined(CONFIG_PM_SLEEP)
++static inline const struct firmware *
++orinoco_cached_fw_get(struct orinoco_private *priv, bool primary)
++{
++ if (primary)
++ return priv->cached_pri_fw;
++ else
++ return priv->cached_fw;
++}
++#else
++#define orinoco_cached_fw_get(priv, primary) (NULL)
++#endif
++
++/* Download either STA or AP firmware into the card. */
++static int
++orinoco_dl_firmware(struct orinoco_private *priv,
++ const struct fw_info *fw,
++ int ap)
++{
++ /* Plug Data Area (PDA) */
++ __le16 *pda;
++
++ struct hermes *hw = &priv->hw;
++ const struct firmware *fw_entry;
++ const struct orinoco_fw_header *hdr;
++ const unsigned char *first_block;
++ const void *end;
++ const char *firmware;
++ const char *fw_err;
++ struct device *dev = priv->dev;
++ int err = 0;
++
++ pda = kzalloc(fw->pda_size, GFP_KERNEL);
++ if (!pda)
++ return -ENOMEM;
++
++ if (ap)
++ firmware = fw->ap_fw;
++ else
++ firmware = fw->sta_fw;
++
++ dev_dbg(dev, "Attempting to download firmware %s\n", firmware);
++
++ /* Read current plug data */
++ err = hw->ops->read_pda(hw, pda, fw->pda_addr, fw->pda_size);
++ dev_dbg(dev, "Read PDA returned %d\n", err);
++ if (err)
++ goto free;
++
++ if (!orinoco_cached_fw_get(priv, false)) {
++ err = request_firmware(&fw_entry, firmware, priv->dev);
++
++ if (err) {
++ dev_err(dev, "Cannot find firmware %s\n", firmware);
++ err = -ENOENT;
++ goto free;
++ }
++ } else
++ fw_entry = orinoco_cached_fw_get(priv, false);
++
++ hdr = (const struct orinoco_fw_header *) fw_entry->data;
++
++ fw_err = validate_fw(hdr, fw_entry->size);
++ if (fw_err) {
++ dev_warn(dev, "Invalid firmware image detected (%s). "
++ "Aborting download\n", fw_err);
++ err = -EINVAL;
++ goto abort;
++ }
++
++ /* Enable aux port to allow programming */
++ err = hw->ops->program_init(hw, le32_to_cpu(hdr->entry_point));
++ dev_dbg(dev, "Program init returned %d\n", err);
++ if (err != 0)
++ goto abort;
++
++ /* Program data */
++ first_block = (fw_entry->data +
++ le16_to_cpu(hdr->headersize) +
++ le32_to_cpu(hdr->block_offset));
++ end = fw_entry->data + fw_entry->size;
++
++ err = hermes_program(hw, first_block, end);
++ dev_dbg(dev, "Program returned %d\n", err);
++ if (err != 0)
++ goto abort;
++
++ /* Update production data */
++ first_block = (fw_entry->data +
++ le16_to_cpu(hdr->headersize) +
++ le32_to_cpu(hdr->pdr_offset));
++
++ err = hermes_apply_pda_with_defaults(hw, first_block, end, pda,
++ &pda[fw->pda_size / sizeof(*pda)]);
++ dev_dbg(dev, "Apply PDA returned %d\n", err);
++ if (err)
++ goto abort;
++
++ /* Tell card we've finished */
++ err = hw->ops->program_end(hw);
++ dev_dbg(dev, "Program end returned %d\n", err);
++ if (err != 0)
++ goto abort;
++
++ /* Check if we're running */
++ dev_dbg(dev, "hermes_present returned %d\n", hermes_present(hw));
++
++abort:
++ /* If we requested the firmware, release it. */
++ if (!orinoco_cached_fw_get(priv, false))
++ release_firmware(fw_entry);
++
++free:
++ kfree(pda);
++ return err;
++}
++
++/*
++ * Process a firmware image - stop the card, load the firmware, reset
++ * the card and make sure it responds. For the secondary firmware take
++ * care of the PDA - read it and then write it on top of the firmware.
++ */
++static int
++symbol_dl_image(struct orinoco_private *priv, const struct fw_info *fw,
++ const unsigned char *image, const void *end,
++ int secondary)
++{
++ struct hermes *hw = &priv->hw;
++ int ret = 0;
++ const unsigned char *ptr;
++ const unsigned char *first_block;
++
++ /* Plug Data Area (PDA) */
++ __le16 *pda = NULL;
++
++ /* Binary block begins after the 0x1A marker */
++ ptr = image;
++ while (*ptr++ != TEXT_END);
++ first_block = ptr;
++
++ /* Read the PDA from EEPROM */
++ if (secondary) {
++ pda = kzalloc(fw->pda_size, GFP_KERNEL);
++ if (!pda)
++ return -ENOMEM;
++
++ ret = hw->ops->read_pda(hw, pda, fw->pda_addr, fw->pda_size);
++ if (ret)
++ goto free;
++ }
++
++ /* Stop the firmware, so that it can be safely rewritten */
++ if (priv->stop_fw) {
++ ret = priv->stop_fw(priv, 1);
++ if (ret)
++ goto free;
++ }
++
++ /* Program the adapter with new firmware */
++ ret = hermes_program(hw, first_block, end);
++ if (ret)
++ goto free;
++
++ /* Write the PDA to the adapter */
++ if (secondary) {
++ size_t len = hermes_blocks_length(first_block, end);
++ ptr = first_block + len;
++ ret = hermes_apply_pda(hw, ptr, end, pda,
++ &pda[fw->pda_size / sizeof(*pda)]);
++ kfree(pda);
++ if (ret)
++ return ret;
++ }
++
++ /* Run the firmware */
++ if (priv->stop_fw) {
++ ret = priv->stop_fw(priv, 0);
++ if (ret)
++ return ret;
++ }
++
++ /* Reset hermes chip and make sure it responds */
++ ret = hw->ops->init(hw);
++
++ /* hermes_reset() should return 0 with the secondary firmware */
++ if (secondary && ret != 0)
++ return -ENODEV;
++
++ /* And this should work with any firmware */
++ if (!hermes_present(hw))
++ return -ENODEV;
++
++ return 0;
++
++free:
++ kfree(pda);
++ return ret;
++}
++
++
++/*
++ * Download the firmware into the card, this also does a PCMCIA soft
++ * reset on the card, to make sure it's in a sane state.
++ */
++static int
++symbol_dl_firmware(struct orinoco_private *priv,
++ const struct fw_info *fw)
++{
++ struct device *dev = priv->dev;
++ int ret;
++ const struct firmware *fw_entry;
++
++ if (!orinoco_cached_fw_get(priv, true)) {
++ if (request_firmware(&fw_entry, fw->pri_fw, priv->dev) != 0) {
++ dev_err(dev, "Cannot find firmware: %s\n", fw->pri_fw);
++ return -ENOENT;
++ }
++ } else
++ fw_entry = orinoco_cached_fw_get(priv, true);
++
++ /* Load primary firmware */
++ ret = symbol_dl_image(priv, fw, fw_entry->data,
++ fw_entry->data + fw_entry->size, 0);
++
++ if (!orinoco_cached_fw_get(priv, true))
++ release_firmware(fw_entry);
++ if (ret) {
++ dev_err(dev, "Primary firmware download failed\n");
++ return ret;
++ }
++
++ if (!orinoco_cached_fw_get(priv, false)) {
++ if (request_firmware(&fw_entry, fw->sta_fw, priv->dev) != 0) {
++ dev_err(dev, "Cannot find firmware: %s\n", fw->sta_fw);
++ return -ENOENT;
++ }
++ } else
++ fw_entry = orinoco_cached_fw_get(priv, false);
++
++ /* Load secondary firmware */
++ ret = symbol_dl_image(priv, fw, fw_entry->data,
++ fw_entry->data + fw_entry->size, 1);
++ if (!orinoco_cached_fw_get(priv, false))
++ release_firmware(fw_entry);
++ if (ret)
++ dev_err(dev, "Secondary firmware download failed\n");
++
++ return ret;
++}
++
++int orinoco_download(struct orinoco_private *priv)
++{
++ int err = 0;
++ /* Reload firmware */
++ switch (priv->firmware_type) {
++ case FIRMWARE_TYPE_AGERE:
++ /* case FIRMWARE_TYPE_INTERSIL: */
++ err = orinoco_dl_firmware(priv,
++ &orinoco_fw[priv->firmware_type], 0);
++ break;
++
++ case FIRMWARE_TYPE_SYMBOL:
++ err = symbol_dl_firmware(priv,
++ &orinoco_fw[priv->firmware_type]);
++ break;
++ case FIRMWARE_TYPE_INTERSIL:
++ break;
++ }
++ /* TODO: if we fail we probably need to reinitialise
++ * the driver */
++
++ return err;
++}
++
++#if defined(CONFIG_HERMES_CACHE_FW_ON_INIT) || defined(CONFIG_PM_SLEEP)
++void orinoco_cache_fw(struct orinoco_private *priv, int ap)
++{
++ const struct firmware *fw_entry = NULL;
++ const char *pri_fw;
++ const char *fw;
++
++ pri_fw = orinoco_fw[priv->firmware_type].pri_fw;
++ if (ap)
++ fw = orinoco_fw[priv->firmware_type].ap_fw;
++ else
++ fw = orinoco_fw[priv->firmware_type].sta_fw;
++
++ if (pri_fw) {
++ if (request_firmware(&fw_entry, pri_fw, priv->dev) == 0)
++ priv->cached_pri_fw = fw_entry;
++ }
++
++ if (fw) {
++ if (request_firmware(&fw_entry, fw, priv->dev) == 0)
++ priv->cached_fw = fw_entry;
++ }
++}
++
++void orinoco_uncache_fw(struct orinoco_private *priv)
++{
++ release_firmware(priv->cached_pri_fw);
++ release_firmware(priv->cached_fw);
++ priv->cached_pri_fw = NULL;
++ priv->cached_fw = NULL;
++}
++#endif
+diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/fw.h linux-6.15.6/drivers/net/wireless/intersil/orinoco/fw.h
+--- linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/fw.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-6.15.6/drivers/net/wireless/intersil/orinoco/fw.h 2025-08-03 15:01:59.313131673 +0200
+@@ -0,0 +1,21 @@
++/* Firmware file reading and download helpers
++ *
++ * See copyright notice in main.c
++ */
++#ifndef _ORINOCO_FW_H_
++#define _ORINOCO_FW_H_
++
++/* Forward declations */
++struct orinoco_private;
++
++int orinoco_download(struct orinoco_private *priv);
++
++#if defined(CONFIG_HERMES_CACHE_FW_ON_INIT) || defined(CONFIG_PM_SLEEP)
++void orinoco_cache_fw(struct orinoco_private *priv, int ap);
++void orinoco_uncache_fw(struct orinoco_private *priv);
++#else
++#define orinoco_cache_fw(priv, ap) do { } while (0)
++#define orinoco_uncache_fw(priv) do { } while (0)
++#endif
++
++#endif /* _ORINOCO_FW_H_ */
+diff -Nur linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/hermes.c linux-6.15.6/drivers/net/wireless/intersil/orinoco/hermes.c
+--- linux-6.15.6.orig/drivers/net/wireless/intersil/orinoco/hermes.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-6.15.6/drivers/net/wireless/intersil/orinoco/hermes.c 2025-08-03 22:42:47.279513981 +0200
+@@ -0,0 +1,778 @@
++/* hermes.c
++ *
++ * Driver core for the "Hermes" wireless MAC controller, as used in
++ * the Lucent Orinoco and Cabletron RoamAbout cards. It should also
++ * work on the hfa3841 and hfa3842 MAC controller chips used in the
++ * Prism II chipsets.
++ *
++ * This is not a complete driver, just low-level access routines for
++ * the MAC controller itself.
++ *
++ * Based on the prism2 driver from Absolute Value Systems' linux-wlan
++ * project, the Linux wvlan_cs driver, Lucent's HCF-Light
++ * (wvlan_hcf.c) library, and the NetBSD wireless driver (in no
++ * particular order).
++ *
++ * Copyright (C) 2000, David Gibson, Linuxcare Australia.
++ * (C) Copyright David Gibson, IBM Corp. 2001-2003.
++ *
++ * The contents of this file are subject to the Mozilla Public License
++ * Version 1.1 (the "License"); you may not use this file except in
++ * compliance with the License. You may obtain a copy of the License
++ * at http://www.mozilla.org/MPL/
++ *
++ * Software distributed under the License is distributed on an "AS IS"
++ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
++ * the License for the specific language governing rights and
++ * limitations under the License.
++ *
++ * Alternatively, the contents of this file may be used under the
++ * terms of the GNU General Public License version 2 (the "GPL"), in
++ * which case the provisions of the GPL are applicable instead of the
++ * above. If you wish to allow the use of your version of this file
++ * only under the terms of the GPL and not to allow others to use your
++ * version of this file under the MPL, indicate your decision by
++ * deleting the provisions above and replace them with the notice and
++ * other provisions required by the GPL. If you do not delete the
++ * provisions above, a recipient may use your version of this file
++ * under either the MPL or the GPL.
++ */
++
++#include <linux/net.h>
++#include <linux/module.h>
++#include <linux/kernel.h>
++#include <linux/delay.h>
++
++#include "hermes.h"
++
++/* These are maximum timeouts. Most often, card wil react much faster */
++#define CMD_BUSY_TIMEOUT (100) /* In iterations of ~1us */
++#define CMD_INIT_TIMEOUT (50000) /* in iterations of ~10us */
++#define CMD_COMPL_TIMEOUT (20000) /* in iterations of ~10us */
++#define ALLOC_COMPL_TIMEOUT (1000) /* in iterations of ~10us */
++
++/*
++ * AUX port access. To unlock the AUX port write the access keys to the
++ * PARAM0-2 registers, then write HERMES_AUX_ENABLE to the HERMES_CONTROL
++ * register. Then read it and make sure it's HERMES_AUX_ENABLED.
++ */
++#define HERMES_AUX_ENABLE 0x8000 /* Enable auxiliary port access */
++#define HERMES_AUX_DISABLE 0x4000 /* Disable to auxiliary port access */
++#define HERMES_AUX_ENABLED 0xC000 /* Auxiliary port is open */
++#define HERMES_AUX_DISABLED 0x0000 /* Auxiliary port is closed */
++
++#define HERMES_AUX_PW0 0xFE01
++#define HERMES_AUX_PW1 0xDC23
++#define HERMES_AUX_PW2 0xBA45
++
++/* HERMES_CMD_DOWNLD */
++#define HERMES_PROGRAM_DISABLE (0x0000 | HERMES_CMD_DOWNLD)
++#define HERMES_PROGRAM_ENABLE_VOLATILE (0x0100 | HERMES_CMD_DOWNLD)
++#define HERMES_PROGRAM_ENABLE_NON_VOLATILE (0x0200 | HERMES_CMD_DOWNLD)
++#define HERMES_PROGRAM_NON_VOLATILE (0x0300 | HERMES_CMD_DOWNLD)
++
++/*
++ * Debugging helpers
++ */
++
++#define DMSG(stuff...) do {printk(KERN_DEBUG "hermes @ %p: " , hw->iobase); \
++ printk(stuff); } while (0)
++
++#undef HERMES_DEBUG
++#ifdef HERMES_DEBUG
++
++#define DEBUG(lvl, stuff...) if ((lvl) <= HERMES_DEBUG) DMSG(stuff)
++
++#else /* ! HERMES_DEBUG */
++
++#define DEBUG(lvl, stuff...) do { } while (0)
++
++#endif /* ! HERMES_DEBUG */
++
++static const struct hermes_ops hermes_ops_local;
++
++/*
++ * Internal functions
++ */
++
++/* Issue a command to the chip. Waiting for it to complete is the caller's
++ problem.
++
++ Returns -EBUSY if the command register is busy, 0 on success.
++
++ Callable from any context.
++*/
++static int hermes_issue_cmd(struct hermes *hw, u16 cmd, u16 param0,
++ u16 param1, u16 param2)
++{
++ int k = CMD_BUSY_TIMEOUT;
++ u16 reg;
++
++ /* First wait for the command register to unbusy */
++ reg = hermes_read_regn(hw, CMD);
++ while ((reg & HERMES_CMD_BUSY) && k) {
++ k--;
++ udelay(1);
++ reg = hermes_read_regn(hw, CMD);
++ }
++ if (reg & HERMES_CMD_BUSY)
++ return -EBUSY;
++
++ hermes_write_regn(hw, PARAM2, param2);
++ hermes_write_regn(hw, PARAM1, param1);
++ hermes_write_regn(hw, PARAM0, param0);
++ hermes_write_regn(hw, CMD, cmd);
++
++ return 0;
++}
++
++/*
++ * Function definitions
++ */
++
++/* For doing cmds that wipe the magic constant in SWSUPPORT0 */
++static int hermes_doicmd_wait(struct hermes *hw, u16 cmd,
++ u16 parm0, u16 parm1, u16 parm2,
++ struct hermes_response *resp)
++{
++ int err = 0;
++ int k;
++ u16 status, reg;
++
++ err = hermes_issue_cmd(hw, cmd, parm0, parm1, parm2);
++ if (err)
++ return err;
++
++ reg = hermes_read_regn(hw, EVSTAT);
++ k = CMD_INIT_TIMEOUT;
++ while ((!(reg & HERMES_EV_CMD)) && k) {
++ k--;
++ udelay(10);
++ reg = hermes_