PPC64: EEH Recovery
PATCH 1/1 PPC64: EEH Recovery
Hi Paul,
The patch below implements hotplug style EEH error recovery. Its split into two pieces: a part that needs to be applied to the PPC64 arch tree, and a part that needs to be applied to the RPA PHP hotplug tree. The PPC64 part needs to go in first.
Assuming this doesn't generate a round of discussion, please forward upstream to akpm/torvalds.
===== arch/ppc64/kernel/eeh.c 1.40 vs edited =====
arch/ppc64/kernel/eeh.c +227 -39
@@ -17,21 +17,19 @@* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <linux/bootmem.h> +#include <linux/delay.h> #include <linux/init.h> #include <linux/list.h> -#include <linux/mm.h> #include <linux/notifier.h> #include <linux/pci.h> #include <linux/proc_fs.h> #include <linux/rbtree.h> #include <linux/seq_file.h> -#include <linux/spinlock.h> +#include <asm/atomic.h> #include <asm/eeh.h> #include <asm/io.h> #include <asm/machdep.h> #include <asm/rtas.h> -#include <asm/atomic.h> #include "pci.h" #undef DEBUG
@@ -89,7 +87,6 @@ static struct notifier_block *eeh_notifi* attempts we allow before panicking. */ #define EEH_MAX_FAILS 1000 -static atomic_t eeh_fail_count;/* RTAS tokens */ static int ibm_set_eeh_option;
@@ -223,9 +220,9 @@ pci_addr_cache_insert(struct pci_dev *dewhile (*p) { parent = *p; piar = rb_entry(parent, struct pci_io_addr_range, rb_node); -if (alo < piar->addr_lo) { +if (ahi < piar->addr_lo) { p = &parent->rb_left; - }else if (ahi > piar->addr_hi) { + }else if (alo > piar->addr_hi) { p = &parent->rb_right; }else { if (dev != piar->pcidev ||
@@ -243,6 +240,11 @@ pci_addr_cache_insert(struct pci_dev *de piar->addr_hi = ahi; piar->pcidev = dev; piar->flags = flags; + +#ifdef DEBUG +printk (KERN_DEBUG "PIAR: insert range=[%lx:%lx] dev=%s\n" , + alo, ahi,pci_name (dev)); +#endif rb_link_node (&piar->rb_node, parent, p);rb_insert_color (&piar->rb_node, &pci_io_addr_cache_root.rb_root);
@@ -377,6 +379,9 @@ void __init pci_addr_cache_build(void)continue ; }pci_addr_cache_insert_device (dev); + +/* Save the BAR's; firmware doesn't restore these after EEH reset */ +pci_save_state (dev); }#ifdef DEBUG
@@ -388,6 +393,32 @@ void __init pci_addr_cache_build(void)/* --------------------------------------------------------------- */ /* Above lies the PCI Address Cache. Below lies the EEH event infrastructure */ +void eeh_slot_error_detail (struct device_node *dn,int severity) +{ +unsigned long flags; +int rc; + +if (!dn)return ; + +/* Log the error with the rtas logger */ +spin_lock_irqsave (&slot_errbuf_lock, flags); +memset (slot_errbuf,0 , eeh_error_buf_size); + + rc =rtas_call (ibm_slot_error_detail, +8 ,1 ,NULL , dn->eeh_config_addr, +BUID_HI (dn->phb->buid), +BUID_LO (dn->phb->buid),NULL ,0 , +virt_to_phys (slot_errbuf), + eeh_error_buf_size, + severity); + +if (rc ==0 ) +log_error (slot_errbuf,ERR_TYPE_RTAS_LOG ,0 ); +spin_unlock_irqrestore (&slot_errbuf_lock, flags); +} + +EXPORT_SYMBOL (eeh_slot_error_detail); +/** * eeh_register_notifier - Register to find out about EEH events. * @nb: notifier block to callback on events
@@ -462,11 +493,9 @@ static void eeh_event_handler(void *dumm"%s %s\n" , event->reset_state, pci_name(event->dev), pci_pretty_name(event->dev)); -atomic_set (&eeh_fail_count,0 ); -notifier_call_chain (&eeh_notifier_chain, -EEH_NOTIFY_FREEZE , event); -__get_cpu_var (slot_resets)++; +notifier_call_chain (&eeh_notifier_chain, +EEH_NOTIFY_FREEZE , event);pci_dev_put (event->dev);kfree (event);
@@ -510,7 +539,7 @@ int eeh_dn_check_failure(struct device_nint ret;int rets[2 ];unsigned long flags; -int rc, reset_state; +int reset_state;struct eeh_event *event;__get_cpu_var (total_mmio_ffs)++;
@@ -530,14 +559,15 @@ int eeh_dn_check_failure(struct device_nif (!dn->eeh_config_addr) {return 0 ; } - +/* * If we already have a pending isolation event for this * slot, we know it's bad already, we don't need to check... */ if (dn->eeh_mode &EEH_MODE_ISOLATED ) { -atomic_inc (&eeh_fail_count); -if (atomic_read (&eeh_fail_count) >=EEH_MAX_FAILS ) { + dn->eeh_freeze_count ++; +if (dn->eeh_freeze_count >=EEH_MAX_FAILS ) { +dump_stack ();/* re-read the slot reset state */ rets[0 ] =-1 ; rtas_call(ibm_read_slot_reset_state, 3, 3, rets,
@@ -565,28 +595,17 @@ int eeh_dn_check_failure(struct device_nreturn 0 ; } -/* prevent repeated reports of this failure */ +/* Prevent repeated reports of this failure */ dn->eeh_mode |=EEH_MODE_ISOLATED ; reset_state = rets[0 ]; +/* Log the error with the rtas logger */ +if (dn->eeh_freeze_count <EEH_MAX_ALLOWED_FREEZES ) { +eeh_slot_error_detail (dn,1 /* Temporary Error */ ); + }else { +eeh_slot_error_detail (dn,2 /* Permanent Error */ ); + } -spin_lock_irqsave (&slot_errbuf_lock, flags); -memset (slot_errbuf,0 , eeh_error_buf_size); - - rc =rtas_call (ibm_slot_error_detail, -8 ,1 ,NULL , dn->eeh_config_addr, -BUID_HI (dn->phb->buid), -BUID_LO (dn->phb->buid),NULL ,0 , -virt_to_phys (slot_errbuf), - eeh_error_buf_size, -1 /* Temporary Error */ ); - -if (rc ==0 ) -log_error (slot_errbuf,ERR_TYPE_RTAS_LOG ,0 ); -spin_unlock_irqrestore (&slot_errbuf_lock, flags); - -printk (KERN_INFO "EEH: MMIO failure (%d) on device: %s %s\n" , - rets[0 ], dn->name, dn->full_name); event =kmalloc (sizeof (*event),GFP_ATOMIC );if (event ==NULL ) {eeh_panic (dev, reset_state);
@@ -618,7 +637,6 @@ EXPORT_SYMBOL(eeh_dn_check_failure);* @token i/o token, should be address in the form 0xA.... * @val value, should be all 1's (XXX why do we need this arg??) * -* Check for an eeh failure at the given token address. * Check for an EEH failure at the given token address. Call this * routine if the result of a read was all 0xff's and you want to * find out if this is due to an EEH slot freeze event. This routine
@@ -626,6 +644,7 @@ EXPORT_SYMBOL(eeh_dn_check_failure);* * Note this routine is safe to call in an interrupt context. */ +unsigned long eeh_check_failure (const volatile void __iomem *token,unsigned long val) {unsigned long addr;
@@ -635,7 +654,7 @@ unsigned long eeh_check_failure(const vo/* Finding the phys addr + pci device; this is pretty quick. */ addr =eeh_token_to_phys ((unsigned long __force ) token); dev =pci_get_device_by_addr (addr); -if (!dev) +if (!dev)return val; dn =pci_device_to_OF_node (dev);
@@ -647,6 +666,174 @@ unsigned long eeh_check_failure(const voEXPORT_SYMBOL (eeh_check_failure); +/* ------------------------------------------------------------- */ +/* The code below deals with error recovery */ + +void +rtas_set_slot_reset (struct device_node *dn) +{ +int token =rtas_token ("ibm,set-slot-reset" ); +int rc; + +if (token ==RTAS_UNKNOWN_SERVICE ) +return ; + rc =rtas_call (token,4 ,1 ,NULL , + dn->eeh_config_addr, +BUID_HI (dn->phb->buid), +BUID_LO (dn->phb->buid), +1 ); +if (rc) { +printk (KERN_WARNING "EEH: Unable to reset the failed slot\n" ); +return ; + } + +/* The PCI bus requires that the reset be held high for at least +* a 100 milliseconds. We wait a bit longer 'just in case'. +*/ +msleep (200 ); + + rc =rtas_call (token,4 ,1 ,NULL , + dn->eeh_config_addr, +BUID_HI (dn->phb->buid), +BUID_LO (dn->phb->buid), +0 ); +} + +EXPORT_SYMBOL (rtas_set_slot_reset); + +void +rtas_configure_bridge (struct device_node *dn) +{ +int token =rtas_token ("ibm,configure-bridge" ); +int rc; + +if (token ==RTAS_UNKNOWN_SERVICE ) +return ; + rc =rtas_call (token,3 ,1 ,NULL , + dn->eeh_config_addr, +BUID_HI (dn->phb->buid), +BUID_LO (dn->phb->buid)); +if (rc) { +printk (KERN_WARNING "EEH: Unable to configure device bridge\n" ); + } +} + +EXPORT_SYMBOL (rtas_configure_bridge); + +/* ------------------------------------------------------- */ +/** Save and restore of PCI BARs +* +* Although firmware will set up BARs during boot, it doesn't +* set up device BAR's after a device reset, although it will, +* if requested, set up bridge configuration. Thus, we need to +* configure the PCI devices ourselves. Config-space setup is +* stored in the PCI structures which are normally deleted during +* device removal. Thus, the "save" routine references the +* structures so that they aren't deleted. +*/ + + +struct eeh_cfg_tree +{ +struct eeh_cfg_tree *sibling; +struct eeh_cfg_tree *child; +struct pci_dev *dev; +struct device_node *dn; +}; + +static inline struct pci_dev *eeh_get_pci_dev (struct device_node *dn) +{ +struct pci_dev *dev =NULL ; +char bus_id[BUS_ID_SIZE ]; + +sprintf (bus_id,"%04x:%02x:%02x.%d" ,dn->phb->global_number, + dn->busno,PCI_SLOT (dn->devfn),PCI_FUNC (dn->devfn)); + +while ((dev =pci_get_device (PCI_ANY_ID ,PCI_ANY_ID , dev)) !=NULL ) { +if (!strcmp (pci_name (dev), bus_id)) +return dev; + } +return NULL ; +} + +/** +* eeh_save_bars - save the PCI config space info +*/ +struct eeh_cfg_tree *eeh_save_bars (struct device_node *dn) +{ +struct eeh_cfg_tree *cnode; +struct pci_dev *dev; + + dev =eeh_get_pci_dev (dn); +if (!dev) +return NULL ; + + cnode =kmalloc (sizeof (struct eeh_cfg_tree ),GFP_KERNEL ); +if (!cnode) +return NULL ; + + cnode->dev = dev; + +of_node_get (dn); + cnode->dn = dn; + + cnode->sibling =NULL ; + cnode->child =NULL ; + +if (dn->child) { + cnode->child =eeh_save_bars (dn->child); + } +if (dn->sibling) { + cnode->sibling =eeh_save_bars (dn->sibling); + } + +return cnode; +} +EXPORT_SYMBOL (eeh_save_bars); + +/** +* __restore_bars - Restore the Base Address Registers +* Loads the PCI configuration space base address registers +* and the expansion ROM base address from the array +* passed as the second argument. +*/ +static inline void __restore_bars (struct device_node *dn,u32 *cfg_hdr) +{ +int i; +for (i=4 ; i<10 ; i++) { +rtas_write_config (dn, i*4 ,4 , cfg_hdr[i]); + } +rtas_write_config (dn,12 *4 ,4 , cfg_hdr[12 ]); +} + +/** +* eeh_restore_bars - restore the PCI config space info +*/ +void eeh_restore_bars (struct eeh_cfg_tree *tree) +{ +if (tree->dev->hdr_type !=PCI_HEADER_TYPE_BRIDGE ) { +__restore_bars (tree->dn, tree->dev->saved_config_space); + } + +if (tree->child) { +eeh_restore_bars (tree->child); + } +if (tree->sibling) { +eeh_restore_bars (tree->sibling); + } + +of_node_put (tree->dn); +pci_dev_put (tree->dev); +kfree (tree); +} +EXPORT_SYMBOL (eeh_restore_bars); + +/* ------------------------------------------------------------- */ +/* The code below deals with enabling EEH for devices during the +* early boot sequence. EEH must be enabled before any PCI probing +* can be done. +*/ +struct eeh_early_enable_info {unsigned int buid_hi;unsigned int buid_lo;
@@ -840,6 +1027,9 @@ void eeh_add_device_late(struct pci_dev#endif pci_addr_cache_insert_device (dev); + +/* Save the BAR's; firmware doesn't restore these after EEH reset */ +pci_save_state (dev); }EXPORT_SYMBOL (eeh_add_device_late);
@@ -885,10 +1075,8 @@ static int proc_eeh_show(struct seq_fileseq_printf (m,"eeh_total_mmio_ffs=%ld\n" "eeh_false_positives=%ld\n" "eeh_ignored_failures=%ld\n" -"eeh_slot_resets=%ld\n" -"eeh_fail_count=%d\n" , - ffs, positives, failures, resets, - eeh_fail_count.counter); +"eeh_slot_resets=%ld\n" , + ffs, positives, failures, resets); }return 0 ;
===== arch/ppc64/kernel/pSeries_pci.c 1.59 vs edited =====
arch/ppc64/kernel/pSeries_pci.c +2 -1
@@ -102,7 +102,7 @@ static int rtas_pci_read_config(struct preturn PCIBIOS_DEVICE_NOT_FOUND ; } -static int rtas_write_config (struct device_node *dn,int where,int size,u32 val) +int rtas_write_config (struct device_node *dn,int where,int size,u32 val) {unsigned long buid, addr;int ret;
@@ -125,6 +125,7 @@ static int rtas_write_config(struct devireturn PCIBIOS_SUCCESSFUL ; } +EXPORT_SYMBOL (rtas_write_config); static int rtas_pci_write_config(struct pci_bus *bus, unsigned int devfn,
===== include/asm-ppc64/eeh.h 1.23 vs edited =====
include/asm-ppc64/eeh.h +40 -1
66 lines not on this page · raw message
===== include/asm-ppc64/prom.h 1.23 vs edited =====
include/asm-ppc64/prom.h +1 -0
@@ -162,6 +162,7 @@ struct device_node {int status;/* Current device status (non-zero is bad) */ int eeh_mode;/* See eeh.h for possible EEH_MODEs */ int eeh_config_addr; +int eeh_freeze_count;/* number of times this device froze up. */ struct pci_controller *phb;/* for pci devices */ struct iommu_table *iommu_table;/* for phb's or bridges */
===== include/asm-ppc64/rtas.h 1.24 vs edited =====
include/asm-ppc64/rtas.h +2 -0
@@ -241,4 +241,6 @@ extern void rtas_stop_self(void);/* RMO buffer reserved for user-space RTAS use */ extern unsigned long rtas_rmo_buf; +extern int rtas_write_config (struct device_node *dn,int where,int size,u32 val); +#endif /* _PPC64_RTAS_H */
===== drivers/pci/hotplug/rpaphp.h 1.11 vs edited =====
drivers/pci/hotplug/rpaphp.h +2 -0
@@ -126,6 +126,8 @@ extern int register_pci_slot(struct slotextern int rpaphp_unconfig_pci_adapter (struct slot *slot);extern int rpaphp_get_pci_adapter_status (struct slot *slot,int is_init,u8 * value);extern struct hotplug_slot *rpaphp_find_hotplug_slot (struct pci_dev *dev); +extern void init_eeh_handler (void ); +extern void exit_eeh_handler (void );/* rpaphp_core.c */ extern int rpaphp_add_slot (struct device_node *dn);
===== drivers/pci/hotplug/rpaphp_core.c 1.18 vs edited =====
drivers/pci/hotplug/rpaphp_core.c +6 -0
@@ -443,12 +443,18 @@ static int __init rpaphp_init(void) {info (DRIVER_DESC " version: " DRIVER_VERSION "\n" ); +/* Get set to handle EEH events. */ +init_eeh_handler (); +/* read all the PRA info from the system */ return init_rpa (); } static void __exit rpaphp_exit(void) { +/* Let EEH know we are going away. */ +exit_eeh_handler (); +cleanup_slots (); }
===== drivers/pci/hotplug/rpaphp_pci.c 1.16 vs edited =====
drivers/pci/hotplug/rpaphp_pci.c +128 -15
210 lines not on this page · raw message