mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-25 02:09:42 +00:00
![Lukas Wunner](/assets/img/avatar_default.png)
The EFI firmware on Macs contains a full-fledged network stack for downloading OS X images from osrecovery.apple.com. Unfortunately on Macs introduced 2011 and 2012, EFI brings up the Broadcom 4331 wireless card on every boot and leaves it enabled even after ExitBootServices has been called. The card continues to assert its IRQ line, causing spurious interrupts if the IRQ is shared. It also corrupts memory by DMAing received packets, allowing for remote code execution over the air. This only stops when a driver is loaded for the wireless card, which may be never if the driver is not installed or blacklisted. The issue seems to be constrained to the Broadcom 4331. Chris Milsted has verified that the newer Broadcom 4360 built into the MacBookPro11,3 (2013/2014) does not exhibit this behaviour. The chances that Apple will ever supply a firmware fix for the older machines appear to be zero. The solution is to reset the card on boot by writing to a reset bit in its mmio space. This must be done as an early quirk and not as a plain vanilla PCI quirk to successfully combat memory corruption by DMAed packets: Matthew Garrett found out in 2012 that the packets are written to EfiBootServicesData memory (http://mjg59.dreamwidth.org/11235.html). This type of memory is made available to the page allocator by efi_free_boot_services(). Plain vanilla PCI quirks run much later, in subsys initcall level. In-between a time window would be open for memory corruption. Random crashes occurring in this time window and attributed to DMAed packets have indeed been observed in the wild by Chris Bainbridge. When Matthew Garrett analyzed the memory corruption issue in 2012, he sought to fix it with a grub quirk which transitions the card to D3hot: http://git.savannah.gnu.org/cgit/grub.git/commit/?id=9d34bb85da56 This approach does not help users with other bootloaders and while it may prevent DMAed packets, it does not cure the spurious interrupts emanating from the card. Unfortunately the card's mmio space is inaccessible in D3hot, so to reset it, we have to undo the effect of Matthew's grub patch and transition the card back to D0. Note that the quirk takes a few shortcuts to reduce the amount of code: The size of BAR 0 and the location of the PM capability is identical on all affected machines and therefore hardcoded. Only the address of BAR 0 differs between models. Also, it is assumed that the BCMA core currently mapped is the 802.11 core. The EFI driver seems to always take care of this. Michael Büsch, Bjorn Helgaas and Matt Fleming contributed feedback towards finding the best solution to this problem. The following should be a comprehensive list of affected models: iMac13,1 2012 21.5" [Root Port 00:1c.3 = 8086:1e16] iMac13,2 2012 27" [Root Port 00:1c.3 = 8086:1e16] Macmini5,1 2011 i5 2.3 GHz [Root Port 00:1c.1 = 8086:1c12] Macmini5,2 2011 i5 2.5 GHz [Root Port 00:1c.1 = 8086:1c12] Macmini5,3 2011 i7 2.0 GHz [Root Port 00:1c.1 = 8086:1c12] Macmini6,1 2012 i5 2.5 GHz [Root Port 00:1c.1 = 8086:1e12] Macmini6,2 2012 i7 2.3 GHz [Root Port 00:1c.1 = 8086:1e12] MacBookPro8,1 2011 13" [Root Port 00:1c.1 = 8086:1c12] MacBookPro8,2 2011 15" [Root Port 00:1c.1 = 8086:1c12] MacBookPro8,3 2011 17" [Root Port 00:1c.1 = 8086:1c12] MacBookPro9,1 2012 15" [Root Port 00:1c.1 = 8086:1e12] MacBookPro9,2 2012 13" [Root Port 00:1c.1 = 8086:1e12] MacBookPro10,1 2012 15" [Root Port 00:1c.1 = 8086:1e12] MacBookPro10,2 2012 13" [Root Port 00:1c.1 = 8086:1e12] For posterity, spurious interrupts caused by the Broadcom 4331 wireless card resulted in splats like this (stacktrace omitted): irq 17: nobody cared (try booting with the "irqpoll" option) handlers: [<ffffffff81374370>] pcie_isr [<ffffffffc0704550>] sdhci_irq [sdhci] threaded [<ffffffffc07013c0>] sdhci_thread_irq [sdhci] [<ffffffffc0a0b960>] azx_interrupt [snd_hda_codec] Disabling IRQ #17 Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=79301 Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=111781 Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=728916 Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=895951#c16 Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1009819 Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1098621 Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1149632#c5 Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1279130 Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1332732 Tested-by: Konstantin Simanov <k.simanov@stlk.ru> # [MacBookPro8,1] Tested-by: Lukas Wunner <lukas@wunner.de> # [MacBookPro9,1] Tested-by: Bryan Paradis <bryan.paradis@gmail.com> # [MacBookPro9,2] Tested-by: Andrew Worsley <amworsley@gmail.com> # [MacBookPro10,1] Tested-by: Chris Bainbridge <chris.bainbridge@gmail.com> # [MacBookPro10,2] Signed-off-by: Lukas Wunner <lukas@wunner.de> Acked-by: Rafał Miłecki <zajec5@gmail.com> Acked-by: Matt Fleming <matt@codeblueprint.co.uk> Cc: Andy Lutomirski <luto@kernel.org> Cc: Bjorn Helgaas <bhelgaas@google.com> Cc: Borislav Petkov <bp@alien8.de> Cc: Brian Gerst <brgerst@gmail.com> Cc: Chris Milsted <cmilsted@redhat.com> Cc: Denys Vlasenko <dvlasenk@redhat.com> Cc: H. Peter Anvin <hpa@zytor.com> Cc: Josh Poimboeuf <jpoimboe@redhat.com> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Matthew Garrett <mjg59@srcf.ucam.org> Cc: Michael Buesch <m@bues.ch> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Yinghai Lu <yinghai@kernel.org> Cc: b43-dev@lists.infradead.org Cc: linux-pci@vger.kernel.org Cc: linux-wireless@vger.kernel.org Cc: stable@vger.kernel.org Cc: stable@vger.kernel.org # 123456789abc: x86/quirks: Apply nvidia_bugs quirk only on root bus Cc: stable@vger.kernel.org # 123456789abc: x86/quirks: Reintroduce scanning of secondary buses Link: http://lkml.kernel.org/r/48d0972ac82a53d460e5fce77a07b2560db95203.1465690253.git.lukas@wunner.de [ Did minor readability edits. ] Signed-off-by: Ingo Molnar <mingo@kernel.org>
212 lines
6.1 KiB
C
212 lines
6.1 KiB
C
#ifndef LINUX_BCMA_PRIVATE_H_
|
|
#define LINUX_BCMA_PRIVATE_H_
|
|
|
|
#ifndef pr_fmt
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
#endif
|
|
|
|
#include <linux/bcma/bcma.h>
|
|
#include <linux/delay.h>
|
|
|
|
#define bcma_err(bus, fmt, ...) \
|
|
pr_err("bus%d: " fmt, (bus)->num, ##__VA_ARGS__)
|
|
#define bcma_warn(bus, fmt, ...) \
|
|
pr_warn("bus%d: " fmt, (bus)->num, ##__VA_ARGS__)
|
|
#define bcma_info(bus, fmt, ...) \
|
|
pr_info("bus%d: " fmt, (bus)->num, ##__VA_ARGS__)
|
|
#define bcma_debug(bus, fmt, ...) \
|
|
pr_debug("bus%d: " fmt, (bus)->num, ##__VA_ARGS__)
|
|
|
|
struct bcma_bus;
|
|
|
|
/* main.c */
|
|
bool bcma_wait_value(struct bcma_device *core, u16 reg, u32 mask, u32 value,
|
|
int timeout);
|
|
void bcma_prepare_core(struct bcma_bus *bus, struct bcma_device *core);
|
|
void bcma_init_bus(struct bcma_bus *bus);
|
|
void bcma_unregister_cores(struct bcma_bus *bus);
|
|
int bcma_bus_register(struct bcma_bus *bus);
|
|
void bcma_bus_unregister(struct bcma_bus *bus);
|
|
int __init bcma_bus_early_register(struct bcma_bus *bus);
|
|
#ifdef CONFIG_PM
|
|
int bcma_bus_suspend(struct bcma_bus *bus);
|
|
int bcma_bus_resume(struct bcma_bus *bus);
|
|
#endif
|
|
struct device *bcma_bus_get_host_dev(struct bcma_bus *bus);
|
|
|
|
/* scan.c */
|
|
void bcma_detect_chip(struct bcma_bus *bus);
|
|
int bcma_bus_scan(struct bcma_bus *bus);
|
|
|
|
/* sprom.c */
|
|
int bcma_sprom_get(struct bcma_bus *bus);
|
|
|
|
/* driver_chipcommon.c */
|
|
void bcma_core_chipcommon_early_init(struct bcma_drv_cc *cc);
|
|
void bcma_core_chipcommon_init(struct bcma_drv_cc *cc);
|
|
void bcma_chipco_bcm4331_ext_pa_lines_ctl(struct bcma_drv_cc *cc, bool enable);
|
|
|
|
/* driver_chipcommon_b.c */
|
|
int bcma_core_chipcommon_b_init(struct bcma_drv_cc_b *ccb);
|
|
void bcma_core_chipcommon_b_free(struct bcma_drv_cc_b *ccb);
|
|
|
|
/* driver_chipcommon_pmu.c */
|
|
void bcma_pmu_early_init(struct bcma_drv_cc *cc);
|
|
void bcma_pmu_init(struct bcma_drv_cc *cc);
|
|
u32 bcma_pmu_get_alp_clock(struct bcma_drv_cc *cc);
|
|
u32 bcma_pmu_get_cpu_clock(struct bcma_drv_cc *cc);
|
|
|
|
/**************************************************
|
|
* driver_chipcommon_sflash.c
|
|
**************************************************/
|
|
|
|
#ifdef CONFIG_BCMA_PFLASH
|
|
extern struct platform_device bcma_pflash_dev;
|
|
int bcma_pflash_init(struct bcma_drv_cc *cc);
|
|
#else
|
|
static inline int bcma_pflash_init(struct bcma_drv_cc *cc)
|
|
{
|
|
bcma_err(cc->core->bus, "Parallel flash not supported\n");
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_BCMA_PFLASH */
|
|
|
|
#ifdef CONFIG_BCMA_SFLASH
|
|
/* driver_chipcommon_sflash.c */
|
|
int bcma_sflash_init(struct bcma_drv_cc *cc);
|
|
extern struct platform_device bcma_sflash_dev;
|
|
#else
|
|
static inline int bcma_sflash_init(struct bcma_drv_cc *cc)
|
|
{
|
|
bcma_err(cc->core->bus, "Serial flash not supported\n");
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_BCMA_SFLASH */
|
|
|
|
#ifdef CONFIG_BCMA_NFLASH
|
|
/* driver_chipcommon_nflash.c */
|
|
int bcma_nflash_init(struct bcma_drv_cc *cc);
|
|
extern struct platform_device bcma_nflash_dev;
|
|
#else
|
|
static inline int bcma_nflash_init(struct bcma_drv_cc *cc)
|
|
{
|
|
bcma_err(cc->core->bus, "NAND flash not supported\n");
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_BCMA_NFLASH */
|
|
|
|
#ifdef CONFIG_BCMA_HOST_PCI
|
|
/* host_pci.c */
|
|
extern int __init bcma_host_pci_init(void);
|
|
extern void __exit bcma_host_pci_exit(void);
|
|
#endif /* CONFIG_BCMA_HOST_PCI */
|
|
|
|
/* host_soc.c */
|
|
#if defined(CONFIG_BCMA_HOST_SOC) && defined(CONFIG_OF)
|
|
extern int __init bcma_host_soc_register_driver(void);
|
|
extern void __exit bcma_host_soc_unregister_driver(void);
|
|
#else
|
|
static inline int __init bcma_host_soc_register_driver(void)
|
|
{
|
|
return 0;
|
|
}
|
|
static inline void __exit bcma_host_soc_unregister_driver(void)
|
|
{
|
|
}
|
|
#endif /* CONFIG_BCMA_HOST_SOC && CONFIG_OF */
|
|
|
|
/* driver_pci.c */
|
|
#ifdef CONFIG_BCMA_DRIVER_PCI
|
|
u32 bcma_pcie_read(struct bcma_drv_pci *pc, u32 address);
|
|
void bcma_core_pci_early_init(struct bcma_drv_pci *pc);
|
|
void bcma_core_pci_init(struct bcma_drv_pci *pc);
|
|
void bcma_core_pci_up(struct bcma_drv_pci *pc);
|
|
void bcma_core_pci_down(struct bcma_drv_pci *pc);
|
|
#else
|
|
static inline void bcma_core_pci_early_init(struct bcma_drv_pci *pc)
|
|
{
|
|
WARN_ON(pc->core->bus->hosttype == BCMA_HOSTTYPE_PCI);
|
|
}
|
|
static inline void bcma_core_pci_init(struct bcma_drv_pci *pc)
|
|
{
|
|
/* Initialization is required for PCI hosted bus */
|
|
WARN_ON(pc->core->bus->hosttype == BCMA_HOSTTYPE_PCI);
|
|
}
|
|
#endif
|
|
|
|
/* driver_pcie2.c */
|
|
#ifdef CONFIG_BCMA_DRIVER_PCI
|
|
void bcma_core_pcie2_init(struct bcma_drv_pcie2 *pcie2);
|
|
void bcma_core_pcie2_up(struct bcma_drv_pcie2 *pcie2);
|
|
#else
|
|
static inline void bcma_core_pcie2_init(struct bcma_drv_pcie2 *pcie2)
|
|
{
|
|
/* Initialization is required for PCI hosted bus */
|
|
WARN_ON(pcie2->core->bus->hosttype == BCMA_HOSTTYPE_PCI);
|
|
}
|
|
#endif
|
|
|
|
extern int bcma_chipco_watchdog_register(struct bcma_drv_cc *cc);
|
|
|
|
#ifdef CONFIG_BCMA_DRIVER_PCI_HOSTMODE
|
|
bool bcma_core_pci_is_in_hostmode(struct bcma_drv_pci *pc);
|
|
void bcma_core_pci_hostmode_init(struct bcma_drv_pci *pc);
|
|
#else
|
|
static inline bool bcma_core_pci_is_in_hostmode(struct bcma_drv_pci *pc)
|
|
{
|
|
return false;
|
|
}
|
|
static inline void bcma_core_pci_hostmode_init(struct bcma_drv_pci *pc)
|
|
{
|
|
}
|
|
#endif /* CONFIG_BCMA_DRIVER_PCI_HOSTMODE */
|
|
|
|
/**************************************************
|
|
* driver_mips.c
|
|
**************************************************/
|
|
|
|
#ifdef CONFIG_BCMA_DRIVER_MIPS
|
|
unsigned int bcma_core_mips_irq(struct bcma_device *dev);
|
|
void bcma_core_mips_early_init(struct bcma_drv_mips *mcore);
|
|
void bcma_core_mips_init(struct bcma_drv_mips *mcore);
|
|
#else
|
|
static inline unsigned int bcma_core_mips_irq(struct bcma_device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
static inline void bcma_core_mips_early_init(struct bcma_drv_mips *mcore)
|
|
{
|
|
}
|
|
static inline void bcma_core_mips_init(struct bcma_drv_mips *mcore)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
/**************************************************
|
|
* driver_gmac_cmn.c
|
|
**************************************************/
|
|
|
|
#ifdef CONFIG_BCMA_DRIVER_GMAC_CMN
|
|
void bcma_core_gmac_cmn_init(struct bcma_drv_gmac_cmn *gc);
|
|
#else
|
|
static inline void bcma_core_gmac_cmn_init(struct bcma_drv_gmac_cmn *gc)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_BCMA_DRIVER_GPIO
|
|
/* driver_gpio.c */
|
|
int bcma_gpio_init(struct bcma_drv_cc *cc);
|
|
int bcma_gpio_unregister(struct bcma_drv_cc *cc);
|
|
#else
|
|
static inline int bcma_gpio_init(struct bcma_drv_cc *cc)
|
|
{
|
|
return -ENOTSUPP;
|
|
}
|
|
static inline int bcma_gpio_unregister(struct bcma_drv_cc *cc)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_BCMA_DRIVER_GPIO */
|
|
|
|
#endif
|