powerpc/eeh: Synchronize access to struct eeh_pe
Subsystems (2)
cover powerpc/eeh: Synchronize access to struct eeh_pe
Hi Everyone,
It's currently possible to cause a kernel crash by being (un)lucky while adding or removing PCI devices during EEH recovery. The cause of the problem is that there are trees of objects (struct eeh_pe, implemented with pointers and kernel linked lists) that are manipulated concurrently by the PCI core and the EEH recovery system, without synchronization. Unsurprisingly this allows the kernel to hit list poison values or free'd memory, and crash.
Here's a first draft of a way to address this, and I would welcome feedback at this stage: Does it make sense? Is it simple enough? Is it maintainable? Are there other options I should explore? etc...
The design:
We have to consider that:
- eeh_dev_check_failure() can be called in any context, so the only methods that are safe to use there are a spinlock or atomics.
- Some recovery operations are blocking (and can take a long time), so they can't be protected by a spinlock. These operations are performed while traversing lists of PEs.
The simplest solution might initially seem to be to hold the pci_rescan_remove lock during EEH recovery, but it seems to have some significant problems:
- It's a mutex, and can't be used in eeh_dev_check_failure().
- If there is a crash during recovery, either in the core kernel or a driver's call-back function, the lock would be held forever and that would block a lot of the PCI core.
- The EEH code is quite recursive and it's very difficult to work out where to take the lock without causing deadlocks. If there are bugs, they would likely be deadlocks.
So instead, this proposal uses a spinlock (eeh_pe_lock) to protect access to the list-membership fields of eeh_pe in combination with reference counting for blocking operations. Because the spinlock is only protecting the list fields (and no state), the lock only needs to be used over small sections that can be inspected easily.
For blocking operations I'm using the standard kernel refcounting. It only involves one extra field on each PE but it does require tweaks to maintain the refcount in most functions that use PEs and sometimes that can be tricky.
The way the refcount is used is fairly standard: it starts at 1 and is incremented for each pointer pointing to the PE. The initial value of 1 represents the 'existance' of the PE and it's membership in the 'tree' under it's PHB, regardless of where in that tree it is, or if it has children. A PE is removed from those lists before it's inital reference is dropped.
The interaction of the spinlock and refcounting provides this property: If the spinlock is held and a ref is held on a PE then it is safe to start from that PE and traverse anywhere along the list fields or parent pointers to other PEs without taking references to them. (To keep a reference to a PE for use outside of the spinlock, a ref must be taken before the spinlock is released.)
That property can be used to perform blocking operations while traversing the lists by 'sliding' the reference along, only holding the spinlock while moving to the next iteration. It seems to work well but is not perfect: when the lock is released, the current PE may be removed and there isn't any simple, safe way to continue the traversal. The situation can be detected, so there won't be a crash, but traversal can be left incomplete. Perhaps this could be fixed by using a temporary list of struct eeh_pe that is collected while holding the spinlock (I've experimented with this, and it works but seems a bit complicated), or perhaps we can just work around it where it matters. It hasn't happened at all during my testing. I'll look at it if this proposal goes ahead.
For clarity, I have also kept the scope very tightly on the lists of struct eeh_pe and the parent pointer. This patchset does not protect the list of eeh_dev in each eeh_pe or the pdev member of struct eeh_dev, both of which are also affected by races. I've left a few 'this is unsafe' comments in the code in those areas. I'll look more at it if this proposal goes ahead, I don't think that they will be difficult compared to eeh_pe.
Lastly, I've included an "orphan tracking system" that I used during development to verify that reference couting was acting as expected, but I have no idea if it should be included in the final version. It keeps track of PEs that have been removed from the PHB tree, but not yet freed and makes that list available in debugfs. Any PEs that remain orphans for very long are going to be the result of bugs. It's extra risk because it itself could contain bugs, but it could also be useful during debugging.
Cheers,
Sam.
Sam Bobroff (15):
powerpc/eeh: Introduce refcounting for struct eeh_pe powerpc/eeh: Rename eeh_pe_get() to eeh_pe_find() powerpc/eeh: Track orphaned struct eeh_pe
powerpc/eeh: Sync eeh_pe_next(), eeh_pe_find() and early-out
traversals
powerpc/eeh: Sync eeh_pe_get_parent()
powerpc/eeh: Sync eeh_phb_pe_get()
powerpc/eeh: Sync eeh_add_to_parent_pe() and eeh_rmv_from_parent_pe() powerpc/eeh: Sync eeh_handle_normal_event()
powerpw/eeh: Sync eeh_handle_special_event(), pnv_eeh_get_pe(),
pnv_eeh_next_error()
powerpc/eeh: Sync eeh_phb_check_failure()
powerpc/eeh: Sync eeh_dev_check_failure()
powerpc/eeh: Sync eeh_pe_get_state()
powerpc/eeh: Sync pnv_eeh_ei_write()
powerpc/eeh: Sync eeh_force_recover_write()
powerpc/eeh: Sync pcibios_set_pcie_reset_state()
arch/powerpc/include/asm/eeh.h | 20 +- arch/powerpc/kernel/eeh.c | 65 +++++- arch/powerpc/kernel/eeh_driver.c | 23 +- arch/powerpc/kernel/eeh_pe.c | 230 ++++++++++++++++--- arch/powerpc/platforms/powernv/eeh-powernv.c | 43 +++- 5 files changed, 329 insertions(+), 52 deletions(-)
Signature · 1 line
2.22.0.216.g00a2a96fc9
RFC PATCH 1/15 powerpc/eeh: Introduce refcounting for struct eeh_pe
Introduce infrastructure supporting refcounting for struct eeh_pe.
This will provide protection of the list members and struct memory so that crashes related to accessing free memory or poisoned list pointers can be avoided (for example, when EEH is triggered during device removal).
While this provides no additional synchronization of the other EEH state, it seems to be an effective way of providing the necessary safety with a very low risk of introducing deadlocks.
--- arch/powerpc/include/asm/eeh.h | 7 ++++ arch/powerpc/kernel/eeh_pe.c | 70 +++++++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 2 deletions(-)
arch/powerpc/include/asm/eeh.h +7 -0
@@ -13,6 +13,7 @@#include <linux/string.h> #include <linux/time.h> #include <linux/atomic.h> +#include <linux/kref.h> #include <uapi/asm/eeh.h>
@@ -72,6 +73,7 @@ struct pci_dn;#define EEH_PE_PRI_BUS (1 << 11)/* Cached primary bus */ struct eeh_pe { +struct kref kref;/* Reference count */ int type;/* PE type: PHB/Bus/Device */ int state;/* PE EEH dependent mode */ int config_addr;/* Traditional PCI address */
@@ -276,7 +278,12 @@ static inline bool eeh_state_active(int state)typedef void (*eeh_edev_traverse_func )(struct eeh_dev *edev,void *flag);typedef void *(*eeh_pe_traverse_func )(struct eeh_pe *pe,void *flag); +void eeh_lock_pes (unsigned long *flags); +void eeh_unlock_pes (unsigned long flags);void eeh_set_pe_aux_size (int size); +void eeh_get_pe (struct eeh_pe *pe); +void eeh_put_pe (struct eeh_pe *pe); +void eeh_pe_move_to_parent (struct eeh_pe **pe);int eeh_phb_pe_create (struct pci_controller *phb);int eeh_wait_state (struct eeh_pe *pe,int max_wait);struct eeh_pe *eeh_phb_pe_get (struct pci_controller *phb);
arch/powerpc/kernel/eeh_pe.c +68 -2
100 lines not on this page · raw message
Signature · 1 line
2.22.0.216.g00a2a96fc9
RFC PATCH 2/15 powerpc/eeh: Rename eeh_pe_get() to eeh_pe_find()
In reply to Sam BobroffThere are now functions eeh_get_pe() and eeh_pe_get() which seems likely to cause confusion.
Keep eeh_get_pe() because "get" is commonly used to refer to acquiring a reference (which it does), and rename eeh_pe_get() to eeh_pe_find() because it performs a search.
--- arch/powerpc/include/asm/eeh.h | 2 +- arch/powerpc/kernel/eeh.c | 2 +- arch/powerpc/kernel/eeh_pe.c | 12 ++++++------ arch/powerpc/platforms/powernv/eeh-powernv.c | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-)
arch/powerpc/include/asm/eeh.h +1 -1
@@ -288,7 +288,7 @@ int eeh_phb_pe_create(struct pci_controller *phb);int eeh_wait_state (struct eeh_pe *pe,int max_wait);struct eeh_pe *eeh_phb_pe_get (struct pci_controller *phb);struct eeh_pe *eeh_pe_next (struct eeh_pe *pe,struct eeh_pe *root); -struct eeh_pe *eeh_pe_get (struct pci_controller *phb, +struct eeh_pe *eeh_pe_find (struct pci_controller *phb,int pe_no,int config_addr);int eeh_add_to_parent_pe (struct eeh_dev *edev);int eeh_rmv_from_parent_pe (struct eeh_dev *edev);
arch/powerpc/kernel/eeh.c +1 -1
@@ -1940,7 +1940,7 @@ static ssize_t eeh_force_recover_write(struct file *filp,return -ENODEV ;/* Retrieve PE */ - pe =eeh_pe_get (hose, pe_no,0 ); + pe =eeh_pe_find (hose, pe_no,0 );if (!pe)return -ENODEV ;
arch/powerpc/kernel/eeh_pe.c +6 -6
@@ -315,7 +315,7 @@ void eeh_pe_dev_traverse(struct eeh_pe *root, }/** -* __eeh_pe_get - Check the PE address +* __eeh_pe_find - Check the PE address * @data: EEH PE * @flag: EEH device *
@@ -329,7 +329,7 @@ struct eeh_pe_get_flag {int config_addr; }; -static void *__eeh_pe_get (struct eeh_pe *pe,void *flag) +static void *__eeh_pe_find (struct eeh_pe *pe,void *flag) {struct eeh_pe_get_flag *tmp = (struct eeh_pe_get_flag *) flag;
@@ -371,14 +371,14 @@ static void *__eeh_pe_get(struct eeh_pe *pe, void *flag)* which is composed of PCI bus/device/function number, or unified * PE address. */ -struct eeh_pe *eeh_pe_get (struct pci_controller *phb, -int pe_no,int config_addr) +struct eeh_pe *eeh_pe_find (struct pci_controller *phb, +int pe_no,int config_addr) {struct eeh_pe *root =eeh_phb_pe_get (phb);struct eeh_pe_get_flag tmp = { pe_no, config_addr };struct eeh_pe *pe; - pe =eeh_pe_traverse (root, __eeh_pe_get, &tmp); + pe =eeh_pe_traverse (root, __eeh_pe_find, &tmp);return pe; }
@@ -447,7 +447,7 @@ int eeh_add_to_parent_pe(struct eeh_dev *edev)* PE should be composed of PCI bus and its subordinate * components. */ - pe =eeh_pe_get (pdn->phb, edev->pe_config_addr, config_addr); + pe =eeh_pe_find (pdn->phb, edev->pe_config_addr, config_addr);if (pe) {if (pe->type &EEH_PE_INVALID ) {list_add_tail (&edev->entry, &pe->edevs);
arch/powerpc/platforms/powernv/eeh-powernv.c +2 -2
@@ -142,7 +142,7 @@ static ssize_t pnv_eeh_ei_write(struct file *filp,return -EINVAL ;/* Retrieve PE */ - pe =eeh_pe_get (hose, pe_no,0 ); + pe =eeh_pe_find (hose, pe_no,0 );if (!pe)return -ENODEV ;
@@ -1425,7 +1425,7 @@ static int pnv_eeh_get_pe(struct pci_controller *hose, }/* Find the PE according to PE# */ - dev_pe =eeh_pe_get (hose, pe_no,0 ); + dev_pe =eeh_pe_find (hose, pe_no,0 );if (!dev_pe)return -EEXIST ;
Signature · 1 line
2.22.0.216.g00a2a96fc9
Re: [PATCH RFC 02/15] powerpc/eeh: Rename eeh_pe_get() to eeh_pe_find()
Quoted 9 lines · Sam Bobroff
There are now functions
eeh_get_pe()andeeh_pe_get()which seems likely to cause confusion.Keep
eeh_get_pe()because "get" is commonly used to refer to acquiring a reference (which it does), and renameeeh_pe_get()toeeh_pe_find()because it performs a search.Signed-off-by Sam Bobroff <[email protected]>
Good idea.
RFC PATCH 3/15 powerpc/eeh: Track orphaned struct eeh_pe
In reply to Sam BobroffTo facilitate debugging of (the inevitable) refcounting problems with struct eeh_pes, detect when a struct eeh_pe has been removed from it's global PHB list but not yet freed ("orphaned"), and collect these PEs in a list.
They should only remain in the list briefly during processing, so any PEs that stay longer will be the result of bugs.
The list can be examined by reading from the "eeh_pe_debug" file in debugfs.
--- arch/powerpc/include/asm/eeh.h | 4 +++ arch/powerpc/kernel/eeh.c | 21 ++++++++++++++ arch/powerpc/kernel/eeh_pe.c | 53 +++++++++++++++++++++++++++++++++- 3 files changed, 77 insertions(+), 1 deletion(-)
arch/powerpc/include/asm/eeh.h +4 -0
@@ -14,6 +14,7 @@#include <linux/time.h> #include <linux/atomic.h> #include <linux/kref.h> +#include <linux/seq_file.h> #include <uapi/asm/eeh.h>
@@ -66,6 +67,7 @@ struct pci_dn;#define EEH_PE_RECOVERING (1 << 1)/* Recovering PE */ #define EEH_PE_CFG_BLOCKED (1 << 2)/* Block config access */ #define EEH_PE_RESET (1 << 3)/* PE reset in progress */ +#define EEH_PE_ORPHAN (1 << 4)/* Removal pending */ #define EEH_PE_KEEP (1 << 8)/* Keep PE on hotplug */ #define EEH_PE_CFG_RESTRICTED (1 << 9)/* Block config on error */
@@ -103,6 +105,7 @@ struct eeh_pe {unsigned long stack_trace[64 ];int trace_entries;#endif /* CONFIG_STACKTRACE */ +struct list_head orphan_entry;/* Memb. eeh_orphan_pes */ };#define eeh_pe_for_each_dev (pe, edev, tmp) \
@@ -280,6 +283,7 @@ typedef void (*eeh_edev_traverse_func)(struct eeh_dev *edev, void *flag);typedef void *(*eeh_pe_traverse_func )(struct eeh_pe *pe,void *flag);void eeh_lock_pes (unsigned long *flags);void eeh_unlock_pes (unsigned long flags); +void eeh_pe_seq_show_orphans (struct seq_file *s);void eeh_set_pe_aux_size (int size);void eeh_get_pe (struct eeh_pe *pe);void eeh_put_pe (struct eeh_pe *pe);
arch/powerpc/kernel/eeh.c +21 -0
@@ -2153,6 +2153,24 @@ static const struct file_operations eeh_dev_break_fops = { .read = eeh_debugfs_dev_usage, }; +static int eeh_pe_debug_seq_file (struct seq_file *s,void *unused) +{ +eeh_pe_seq_show_orphans (s); +return 0 ; +} + +static int eeh_pe_debug_dbgfs_open (struct inode *inode,struct file *file) +{ +return single_open (file, eeh_pe_debug_seq_file, inode->i_private); +} + +static const struct file_operations eeh_pe_debug_fops = { + .owner =THIS_MODULE , + .open = eeh_pe_debug_dbgfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +};#endif static int __init eeh_init_proc (void )
@@ -2177,6 +2195,9 @@ static int __init eeh_init_proc(void)debugfs_create_file_unsafe ("eeh_force_recover" ,0600 , powerpc_debugfs_root,NULL , &eeh_force_recover_fops); +debugfs_create_file_unsafe ("eeh_pe_debug" ,0400 , + powerpc_debugfs_root,NULL , + &eeh_pe_debug_fops);eeh_cache_debugfs_init ();#endif }
arch/powerpc/kernel/eeh_pe.c +52 -1
88 lines not on this page · raw message
Signature · 1 line
2.22.0.216.g00a2a96fc9
RFC PATCH 4/15 powerpc/eeh: Sync eeh_pe_next(), eeh_pe_find() and early-out traversals
In reply to Sam BobroffReference counting for the in-line loop macro "eeh_for_each_pe()" can be done by having eeh_pe_next() both get and put references; "rolling" a reference along the list. This allows most loops to work without change, although ones that use an "early-out" must manually release the final reference.
While reference counting will keep the current iteration's PE from being freed while it is in use, it's also necessary to check that it hasn't been removed from the list that's being traversed. This is done by checking the parent pointer, which is set to NULL on removal (see eeh_rmv_from_parent_pe()) (PHB type PEs never have their parent set, but aren't a problem: they can't be removed). If this does occur, the traversal is terminated. This may leave the traversal incomplete, but that is preferable to crashing.
--- arch/powerpc/include/asm/eeh.h | 7 ++++- arch/powerpc/kernel/eeh_driver.c | 4 ++- arch/powerpc/kernel/eeh_pe.c | 50 +++++++++++++++++++++++++------- 3 files changed, 48 insertions(+), 13 deletions(-)
arch/powerpc/include/asm/eeh.h +6 -1
@@ -111,8 +111,13 @@ struct eeh_pe {#define eeh_pe_for_each_dev (pe, edev, tmp) \ list_for_each_entry_safe(edev, tmp, &pe->edevs, entry) +/* +* Note that eeh_pe_next() maintins a reference on 'pe' for each +* iteration and that it must be manually released if the loop is +* exited early (i.e. before eeh_pe_next() returns NULL). +*/ #define eeh_for_each_pe (root, pe) \ - for (pe = root; pe; pe = eeh_pe_next(pe, root)) + for (pe = root, eeh_get_pe(pe); pe; pe = eeh_pe_next(pe, root))static inline bool eeh_pe_passed (struct eeh_pe *pe) {
arch/powerpc/kernel/eeh_driver.c +3 -1
@@ -590,8 +590,10 @@ static int eeh_clear_pe_frozen_state(struct eeh_pe *root, bool include_passed)for (i =0 ; i <3 ; i++)if (!eeh_unfreeze_pe (pe))break ; -if (i >=3 ) +if (i >=3 ) { +eeh_put_pe (pe);/* Release loop ref */ return -EIO ; + } } }eeh_pe_state_clear (root,EEH_PE_ISOLATED , include_passed);
arch/powerpc/kernel/eeh_pe.c +39 -11
108 lines not on this page · raw message
Signature · 1 line
2.22.0.216.g00a2a96fc9
RFC PATCH 5/15 powerpc/eeh: Sync eeh_pe_get_parent()
In reply to Sam BobroffSynchronize access to eeh_pe.
--- arch/powerpc/kernel/eeh_pe.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-)
arch/powerpc/kernel/eeh_pe.c +10 -2
@@ -469,10 +469,12 @@ struct eeh_pe *eeh_pe_find(struct pci_controller *phb,* The whole PEs existing in the system are organized as hierarchy * tree. The function is used to retrieve the parent PE according * to the parent EEH device. +* Returns a referenced PE. */ static struct eeh_pe *eeh_pe_get_parent (struct eeh_dev *edev) {struct eeh_dev *parent; +struct eeh_pe *pe;struct pci_dn *pdn =eeh_dev_to_pdn (edev);/*
@@ -490,8 +492,14 @@ static struct eeh_pe *eeh_pe_get_parent(struct eeh_dev *edev)if (!parent)return NULL ; -if (parent->pe) -return parent->pe; +if (parent->pe) { +/* TODO: Unsafe until eeh_dev can be synchronized +* with eeh_pe. +*/ + pe = parent->pe; +eeh_get_pe (pe); +return pe; + } pdn = pdn->parent; }
Signature · 1 line
2.22.0.216.g00a2a96fc9
RFC PATCH 6/15 powerpc/eeh: Sync eeh_phb_pe_get()
In reply to Sam BobroffSynchronize access to eeh_pe.
--- arch/powerpc/kernel/eeh_pe.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-)
arch/powerpc/kernel/eeh_pe.c +9 -2
@@ -269,20 +269,27 @@ int eeh_wait_state(struct eeh_pe *pe, int max_wait)* The overall PEs form hierarchy tree. The first layer of the * hierarchy tree is composed of PHB PEs. The function is used * to retrieve the corresponding PHB PE according to the given PHB. +* Returns a referenced PE. */ struct eeh_pe *eeh_phb_pe_get (struct pci_controller *phb) {struct eeh_pe *pe; +unsigned long flags; +eeh_lock_pes (&flags);list_for_each_entry (pe, &eeh_phb_pe, child) {/* * Actually, we needn't check the type since * the PE for PHB has been determined when that * was created. */ -if ((pe->type &EEH_PE_PHB ) && pe->phb == phb) -return pe; +if ((pe->type &EEH_PE_PHB ) && pe->phb == phb) { +eeh_get_pe (pe);/* Acquire ref */ +eeh_unlock_pes (flags); +return pe;/* Give ref */ + } } +eeh_unlock_pes (flags);return NULL ; }
Signature · 1 line
2.22.0.216.g00a2a96fc9
RFC PATCH 7/15 powerpc/eeh: Sync eeh_add_to_parent_pe() and eeh_rmv_from_parent_pe()
In reply to Sam BobroffNote that even though there is currently only one place where a PE can be removed from the parent/child tree (eeh_rmv_from_parent_pe()), it is still protected against concurrent removal in case that changes in the future.
--- arch/powerpc/kernel/eeh_pe.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-)
arch/powerpc/kernel/eeh_pe.c +22 -4
@@ -528,6 +528,7 @@ int eeh_add_to_parent_pe(struct eeh_dev *edev)struct eeh_pe *pe, *parent;struct pci_dn *pdn =eeh_dev_to_pdn (edev);int config_addr = (pdn->busno <<8 ) | (pdn->devfn); +unsigned long flags;/* Check if the PE number is valid */ if (!eeh_has_flag (EEH_VALID_PE_ZERO ) && !edev->pe_config_addr) {
@@ -541,6 +542,7 @@ int eeh_add_to_parent_pe(struct eeh_dev *edev)* PE should be composed of PCI bus and its subordinate * components. */ +/* Acquire ref */ pe =eeh_pe_find (pdn->phb, edev->pe_config_addr, config_addr);if (pe) {if (pe->type &EEH_PE_INVALID ) {
@@ -557,7 +559,6 @@ int eeh_add_to_parent_pe(struct eeh_dev *edev) parent->type &= ~EEH_PE_INVALID ; parent = parent->parent; } -eeh_edev_dbg (edev,"Added to device PE (parent: PE#%x)\n" , pe->parent->addr);
@@ -570,10 +571,12 @@ int eeh_add_to_parent_pe(struct eeh_dev *edev)list_add_tail (&edev->entry, &pe->edevs);eeh_edev_dbg (edev,"Added to bus PE\n" ); } +eeh_put_pe (pe);/* Release ref */ return 0 ; }/* Create a new EEH PE */ +/* Acquire existence ref */ if (edev->physfn) pe =eeh_pe_alloc (pdn->phb,EEH_PE_VF );else
@@ -591,9 +594,9 @@ int eeh_add_to_parent_pe(struct eeh_dev *edev)* to PHB directly. Otherwise, we have to associate the * PE with its parent. */ - parent =eeh_pe_get_parent (edev); + parent =eeh_pe_get_parent (edev);/* Acquire ref */ if (!parent) { - parent =eeh_phb_pe_get (pdn->phb); + parent =eeh_phb_pe_get (pdn->phb);/* Acquire ref */ if (!parent) {pr_err ("%s: No PHB PE is found (PHB Domain=%d)\n" , __func__, pdn->phb->global_number);
@@ -602,6 +605,7 @@ int eeh_add_to_parent_pe(struct eeh_dev *edev)return -EEXIST ; } } +eeh_lock_pes (&flags); pe->parent = parent;/*
@@ -609,10 +613,13 @@ int eeh_add_to_parent_pe(struct eeh_dev *edev)* link the EEH device accordingly. */ list_add_tail (&pe->child, &parent->child_list); +eeh_unlock_pes (flags);list_add_tail (&edev->entry, &pe->edevs); edev->pe = pe;eeh_edev_dbg (edev,"Added to device PE (parent: PE#%x)\n" , - pe->parent->addr); + parent->addr); +eeh_put_pe (parent);/* Release ref */ +/* Retain existence ref */ return 0 ; }
@@ -631,12 +638,15 @@ int eeh_rmv_from_parent_pe(struct eeh_dev *edev)struct eeh_pe *pe, *parent, *child;bool keep, recover;int cnt; +unsigned long flags; +/* TODO: Unsafe until eeh_dev can be synchronized * with eeh_pe. */ pe =eeh_dev_to_pe (edev);if (!pe) {eeh_edev_dbg (edev,"No PE found for device.\n" );return -EEXIST ; } +eeh_get_pe (pe);/* Acquire ref */ /* Remove the EEH device */ edev->pe =NULL ;
@@ -648,6 +658,7 @@ int eeh_rmv_from_parent_pe(struct eeh_dev *edev)* delete the parent PE if it doesn't have associated * child PEs and EEH devices. */ +eeh_lock_pes (&flags);while (1 ) { parent = pe->parent;
@@ -699,8 +710,15 @@ int eeh_rmv_from_parent_pe(struct eeh_dev *edev) } } +if (!parent) +break ;/* PE has been removed */ + +eeh_get_pe (parent);/* Acquire ref */ +eeh_put_pe (pe);/* Release ref */ pe = parent; } +eeh_unlock_pes (flags); +eeh_put_pe (pe);/* Release ref */ return 0 ; }
Signature · 1 line
2.22.0.216.g00a2a96fc9
RFC PATCH 8/15 powerpc/eeh: Sync eeh_handle_normal_event()
In reply to Sam BobroffSynchronize access to eeh_pe.
--- arch/powerpc/kernel/eeh_driver.c | 4 ++++ 1 file changed, 4 insertions(+)
arch/powerpc/kernel/eeh_driver.c +4 -0
@@ -879,6 +879,7 @@ static void eeh_clear_slot_attention(struct pci_dev *pdev)* & devices under this slot, and then finally restarting the device * drivers (which cause a second set of hotplug events to go out to * userspace). +* Consumes the reference on 'pe'. */ void eeh_handle_normal_event (struct eeh_pe *pe) {
@@ -898,6 +899,7 @@ void eeh_handle_normal_event(struct eeh_pe *pe)if (!bus) {pr_err ("%s: Cannot find PCI bus for PHB#%x-PE#%x\n" , __func__, pe->phb->global_number, pe->addr); +eeh_put_pe (pe);/* Release ref */ return ; }
@@ -1141,6 +1143,7 @@ void eeh_handle_normal_event(struct eeh_pe *pe)pci_hp_remove_devices (bus);pci_unlock_rescan_remove ();/* The passed PE should no longer be used */ +eeh_put_pe (pe);/* Release ref */ return ; } }
@@ -1160,6 +1163,7 @@ void eeh_handle_normal_event(struct eeh_pe *pe)eeh_pe_state_clear (pe,EEH_PE_RECOVERING , true);pr_info ("PE state after recovery:\n" );eeh_tree_state_dump_kprintf (pe); +eeh_put_pe (pe);/* Release ref */ }/**
Signature · 1 line
2.22.0.216.g00a2a96fc9
RFC PATCH 9/15 powerpw/eeh: Sync eeh_handle_special_event(), pnv_eeh_get_pe(), pnv_eeh_next_error()
In reply to Sam BobroffSynchronize access to eeh_pe.
--- arch/powerpc/kernel/eeh_driver.c | 15 +++++--- arch/powerpc/platforms/powernv/eeh-powernv.c | 38 ++++++++++++++++---- 2 files changed, 43 insertions(+), 10 deletions(-)
arch/powerpc/kernel/eeh_driver.c +11 -4
@@ -1184,6 +1184,7 @@ void eeh_handle_special_event(void) do { + /* Acquire ref if rc == _FROZEN_PE, _FENCED_PHB or _DEAD_PHB */ rc = eeh_ops->next_error(&pe); switch (rc) {
@@ -1195,10 +1196,11 @@ void eeh_handle_special_event(void)eeh_remove_event (NULL , true);list_for_each_entry (hose, &hose_list, list_node) { - phb_pe =eeh_phb_pe_get (hose); + phb_pe =eeh_phb_pe_get (hose);/* Acquire ref */ if (!phb_pe)continue ;eeh_pe_mark_isolated (phb_pe); +eeh_put_pe (phb_pe);/* Release ref */ }eeh_serialize_unlock (flags);
@@ -1236,15 +1238,17 @@ void eeh_handle_special_event(void)if (rc ==EEH_NEXT_ERR_FROZEN_PE || rc ==EEH_NEXT_ERR_FENCED_PHB ) {eeh_pe_state_mark (pe,EEH_PE_RECOVERING ); -eeh_handle_normal_event (pe); +eeh_handle_normal_event (pe);/* Give ref */ }else {pci_lock_rescan_remove ();list_for_each_entry (hose, &hose_list, list_node) { - phb_pe =eeh_phb_pe_get (hose); + phb_pe =eeh_phb_pe_get (hose);/* Acquire ref */ if (!phb_pe || !(phb_pe->state &EEH_PE_ISOLATED ) || - (phb_pe->state &EEH_PE_RECOVERING )) + (phb_pe->state &EEH_PE_RECOVERING )) { +eeh_put_pe (phb_pe);/* Release ref */ continue ; + }eeh_for_each_pe (pe, tmp_pe)eeh_pe_for_each_dev (tmp_pe, edev, tmp_edev)
@@ -1263,11 +1267,14 @@ void eeh_handle_special_event(void) __func__, pe->phb->global_number, pe->addr); +eeh_put_pe (phb_pe);/* Release ref */ break ; }pci_hp_remove_devices (bus); +eeh_put_pe (phb_pe);/* Release ref */ }pci_unlock_rescan_remove (); +eeh_put_pe (pe);/* Release ref */ }/*
arch/powerpc/platforms/powernv/eeh-powernv.c +32 -6
@@ -1404,6 +1404,7 @@ static void pnv_eeh_get_and_dump_hub_diag(struct pci_controller *hose) } } +/* A return of 0 indicates that *pe is set, and referenced. */ static int pnv_eeh_get_pe (struct pci_controller *hose,u16 pe_no,struct eeh_pe **pe) {
@@ -1431,6 +1432,7 @@ static int pnv_eeh_get_pe(struct pci_controller *hose,/* Freeze the (compound) PE */ *pe = dev_pe; +eeh_get_pe (*pe);/* Acquire ref */ if (!(dev_pe->state &EEH_PE_ISOLATED )) phb->freeze_pe (phb, pe_no);
@@ -1439,23 +1441,26 @@ static int pnv_eeh_get_pe(struct pci_controller *hose,* have been frozen. However, we still need poke until * hitting the frozen PE on top level. */ - dev_pe = dev_pe->parent; +eeh_pe_move_to_parent (&dev_pe);while (dev_pe && !(dev_pe->type &EEH_PE_PHB )) {int ret; ret = eeh_ops->get_state (dev_pe,NULL );if (ret <=0 ||eeh_state_active (ret)) { - dev_pe = dev_pe->parent; +eeh_pe_move_to_parent (&dev_pe);continue ; }/* Frozen parent PE */ +eeh_put_pe (*pe);/* Release ref */ *pe = dev_pe; +eeh_get_pe (*pe);/* Acquire ref */ if (!(dev_pe->state &EEH_PE_ISOLATED )) phb->freeze_pe (phb, dev_pe->addr);/* Next one */ - dev_pe = dev_pe->parent; +eeh_pe_move_to_parent (&dev_pe); } +eeh_put_pe (dev_pe);return 0 ; }
@@ -1469,6 +1474,8 @@ static int pnv_eeh_get_pe(struct pci_controller *hose,* OPAL APIs for next error to handle. The informational error is * handled internally by platform. However, the dead IOC, dead PHB, * fenced PHB and frozen PE should be handled by EEH core eventually. +* On return, *pe will be ref'd iff returning _FROZEN_PE, _FENCED_PHB or +* _DEAD_PHB. */ static int pnv_eeh_next_error (struct eeh_pe **pe) {
@@ -1479,6 +1486,7 @@ static int pnv_eeh_next_error(struct eeh_pe **pe)__be16 err_type, severity;long rc;int state, ret =EEH_NEXT_ERR_NONE ; +unsigned long flags;/* * While running here, it's safe to purge the event queue. The
@@ -1493,9 +1501,11 @@ static int pnv_eeh_next_error(struct eeh_pe **pe)* needn't take care of it any more. */ phb = hose->private_data; - phb_pe =eeh_phb_pe_get (hose); -if (!phb_pe || (phb_pe->state &EEH_PE_ISOLATED )) + phb_pe =eeh_phb_pe_get (hose);/* Acquire ref */ +if (!phb_pe || (phb_pe->state &EEH_PE_ISOLATED )) { +eeh_put_pe (phb_pe);/* Release ref */ continue ; + } rc =opal_pci_next_error (phb->opal_id, &frozen_pe_no, &err_type, &severity);
@@ -1503,6 +1513,7 @@ static int pnv_eeh_next_error(struct eeh_pe **pe)pr_devel ("%s: Invalid return value on " "PHB#%x (0x%lx) from opal_pci_next_error" , __func__, hose->global_number, rc); +eeh_put_pe (phb_pe);/* Release ref */ continue ; }
@@ -1511,6 +1522,7 @@ static int pnv_eeh_next_error(struct eeh_pe **pe) be16_to_cpu(severity) == OPAL_EEH_SEV_NO_ERROR) {pr_devel ("%s: No error found on PHB#%x\n" , __func__, hose->global_number); +eeh_put_pe (phb_pe);/* Release ref */ continue ; }
@@ -1539,19 +1551,23 @@ static int pnv_eeh_next_error(struct eeh_pe **pe)case OPAL_EEH_PHB_ERROR :if (be16_to_cpu (severity) ==OPAL_EEH_SEV_PHB_DEAD ) { *pe = phb_pe; +eeh_get_pe (*pe);/* Acquire ref */ pr_err ("EEH: dead PHB#%x detected, " "location: %s\n" , hose->global_number,eeh_pe_loc_get (phb_pe)); ret =EEH_NEXT_ERR_DEAD_PHB ; +/* Retain ref on pe */ }else if (be16_to_cpu (severity) ==OPAL_EEH_SEV_PHB_FENCED ) { *pe = phb_pe; +eeh_get_pe (*pe);/* Acquire ref */ pr_err ("EEH: Fenced PHB#%x detected, " "location: %s\n" , hose->global_number,eeh_pe_loc_get (phb_pe)); ret =EEH_NEXT_ERR_FENCED_PHB ; +/* Retain ref on pe */ }else if (be16_to_cpu (severity) ==OPAL_EEH_SEV_INF ) { pr_info("EEH: PHB#%x informative error " "detected, location: %s\n",
@@ -1568,8 +1584,10 @@ static int pnv_eeh_next_error(struct eeh_pe **pe)* If we can't find the corresponding PE, we * just try to unfreeze. */ +/* Maybe acquire ref */ if (pnv_eeh_get_pe (hose,be64_to_cpu (frozen_pe_no), pe)) { +/* 'pe' was not set by pnv_eeh_get_pe() */ pr_info ("EEH: Clear non-existing PHB#%x-PE#%llx\n" , hose->global_number,be64_to_cpu (frozen_pe_no)); pr_info("EEH: PHB location: %s\n",
@@ -1589,6 +1607,7 @@ static int pnv_eeh_next_error(struct eeh_pe **pe) ret =EEH_NEXT_ERR_NONE ; }else if ((*pe)->state &EEH_PE_ISOLATED ||eeh_pe_passed (*pe)) { +eeh_put_pe (*pe);/* Release ref */ ret =EEH_NEXT_ERR_NONE ; }else { pr_err("EEH: Frozen PE#%x "
@@ -1600,6 +1619,7 @@ static int pnv_eeh_next_error(struct eeh_pe **pe)eeh_pe_loc_get (*pe), eeh_pe_loc_get(phb_pe)); ret =EEH_NEXT_ERR_FROZEN_PE ; +/* Retain ref on pe */ }break ;
@@ -1631,7 +1651,10 @@ static int pnv_eeh_next_error(struct eeh_pe **pe)* we need have to handle frozen parent PE firstly. */ if (ret ==EEH_NEXT_ERR_FROZEN_PE ) { +eeh_lock_pes (&flags); parent_pe = (*pe)->parent; +eeh_get_pe (parent_pe); +eeh_unlock_pes (flags);while (parent_pe) {/* Hit the ceiling ? */ if (parent_pe->type &EEH_PE_PHB )
@@ -1643,13 +1666,15 @@ static int pnv_eeh_next_error(struct eeh_pe **pe) *pe = parent_pe;/* Next parent level */ - parent_pe = parent_pe->parent; +eeh_pe_move_to_parent (&parent_pe); } +eeh_put_pe (parent_pe);/* Release ref (for early-out) */ /* We possibly migrate to another PE */ eeh_pe_mark_isolated (*pe); } +eeh_put_pe (phb_pe);/* Release ref */ /* * If we have no errors on the specific PHB or only * informative error there, we continue poking it.
@@ -1664,6 +1689,7 @@ static int pnv_eeh_next_error(struct eeh_pe **pe)if (ret ==EEH_NEXT_ERR_NONE &&eeh_enabled ())enable_irq (eeh_event_irq); +/* *pe may be ref'd, see above */ return ret; }
Signature · 1 line
2.22.0.216.g00a2a96fc9
RFC PATCH 10/15 powerpc/eeh: Sync eeh_phb_check_failure()
In reply to Sam BobroffSynchronize access to eeh_pe.
--- arch/powerpc/kernel/eeh.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
arch/powerpc/kernel/eeh.c +3 -2
@@ -394,7 +394,7 @@ static int eeh_phb_check_failure(struct eeh_pe *pe)return -EPERM ;/* Find the PHB PE */ - phb_pe =eeh_phb_pe_get (pe->phb); + phb_pe =eeh_phb_pe_get (pe->phb);/* Acquire ref */ if (!phb_pe) {pr_warn ("%s Can't find PE for PHB#%x\n" , __func__, pe->phb->global_number);
@@ -422,9 +422,10 @@ static int eeh_phb_check_failure(struct eeh_pe *pe)pr_debug ("EEH: PHB#%x failure detected, location: %s\n" , phb_pe->phb->global_number,eeh_pe_loc_get (phb_pe)); -eeh_send_failure_event (phb_pe); +eeh_send_failure_event (phb_pe);/* Give ref */ return 1 ; out: +eeh_put_pe (phb_pe);/* Release ref */ eeh_serialize_unlock (flags);return ret; }
Signature · 1 line
2.22.0.216.g00a2a96fc9
RFC PATCH 11/15 powerpc/eeh: Sync eeh_dev_check_failure()
In reply to Sam BobroffSynchronize access to eeh_pe.
--- arch/powerpc/kernel/eeh.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-)
arch/powerpc/kernel/eeh.c +20 -6
@@ -447,7 +447,7 @@ static int eeh_phb_check_failure(struct eeh_pe *pe)int eeh_dev_check_failure (struct eeh_dev *edev) {int ret; -unsigned long flags; +unsigned long flags, pe_flags;struct device_node *dn;struct pci_dev *dev;struct eeh_pe *pe, *parent_pe;
@@ -464,7 +464,9 @@ int eeh_dev_check_failure(struct eeh_dev *edev)return 0 ; } dev =eeh_dev_to_pci_dev (edev); +/* TODO: Unsafe until eeh_dev can be synchronized with eeh_pe. */ pe =eeh_dev_to_pe (edev); +eeh_get_pe (pe);/* Access to IO BARs might get this far and still not want checking. */ if (!pe) {
@@ -475,6 +477,7 @@ int eeh_dev_check_failure(struct eeh_dev *edev)if (!pe->addr && !pe->config_addr) { eeh_stats.no_cfg_addr++; +eeh_put_pe (pe);/* Release ref */ return 0 ; }
@@ -482,17 +485,21 @@ int eeh_dev_check_failure(struct eeh_dev *edev)* On PowerNV platform, we might already have fenced PHB * there and we need take care of that firstly. */ - ret =eeh_phb_check_failure (pe); -if (ret >0 ) + ret =eeh_phb_check_failure (pe);/* Acquire ref */ +if (ret >0 ) { +eeh_put_pe (pe);/* Release ref */ return ret; + }/* * If the PE isn't owned by us, we shouldn't check the * state. Instead, let the owner handle it if the PE has * been frozen. */ -if (eeh_pe_passed (pe)) +if (eeh_pe_passed (pe)) { +eeh_put_pe (pe);/* Release ref */ 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.
@@ -548,7 +555,10 @@ int eeh_dev_check_failure(struct eeh_dev *edev)* put into frozen state as well. We should take care * that at first. */ +eeh_lock_pes (&pe_flags); parent_pe = pe->parent; +eeh_get_pe (parent_pe);/* Acquire ref */ +eeh_unlock_pes (pe_flags);while (parent_pe) {/* Hit the ceiling ? */ if (parent_pe->type &EEH_PE_PHB )
@@ -557,15 +567,18 @@ int eeh_dev_check_failure(struct eeh_dev *edev)/* Frozen parent PE ? */ ret = eeh_ops->get_state (parent_pe,NULL );if (ret >0 && !eeh_state_active (ret)) { +eeh_put_pe (pe);/* Release ref */ pe = parent_pe; +eeh_get_pe (pe);/* Acquire ref */ pr_err ("EEH: Failure of PHB#%x-PE#%x will be handled at parent PHB#%x-PE#%x.\n" , pe->phb->global_number, pe->addr, pe->phb->global_number, parent_pe->addr); }/* Next parent level */ - parent_pe = parent_pe->parent; +eeh_pe_move_to_parent (&parent_pe); } +eeh_put_pe (parent_pe);/* Release ref */ eeh_stats.slot_resets++;
@@ -582,11 +595,12 @@ int eeh_dev_check_failure(struct eeh_dev *edev)*/ pr_debug ("EEH: %s: Frozen PHB#%x-PE#%x detected\n" , __func__, pe->phb->global_number, pe->addr); -eeh_send_failure_event (pe); +eeh_send_failure_event (pe);/* Give ref */ return 1 ; dn_unlock: +eeh_put_pe (pe);/* Release ref */ eeh_serialize_unlock (flags);return rc; }
Signature · 1 line
2.22.0.216.g00a2a96fc9
RFC PATCH 12/15 powerpc/eeh: Sync eeh_pe_get_state()
In reply to Sam BobroffSynchronize access to eeh_pe.
--- arch/powerpc/kernel/eeh.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)
arch/powerpc/kernel/eeh.c +6 -1
@@ -1689,6 +1689,7 @@ int eeh_pe_get_state(struct eeh_pe *pe) {int result, ret =0 ;bool rst_active, dma_en, mmio_en; +unsigned long flags;/* Existing PE ? */ if (!pe)
@@ -1703,10 +1704,14 @@ int eeh_pe_get_state(struct eeh_pe *pe)* unavailable so that the error recovery on the guest is suspended * until the recovery completes on the host. */ +eeh_lock_pes (&flags);if (pe->parent && !(pe->state &EEH_PE_REMOVED ) && - (pe->parent->state & (EEH_PE_ISOLATED |EEH_PE_RECOVERING ))) + (pe->parent->state & (EEH_PE_ISOLATED |EEH_PE_RECOVERING ))) { +eeh_unlock_pes (flags);return EEH_PE_STATE_UNAVAIL ; + } +eeh_unlock_pes (flags); result = eeh_ops->get_state (pe,NULL ); rst_active = !!(result &EEH_STATE_RESET_ACTIVE );
Signature · 1 line
2.22.0.216.g00a2a96fc9
RFC PATCH 13/15 powerpc/eeh: Sync pnv_eeh_ei_write()
In reply to Sam BobroffSynchronize access to eeh_pe.
--- arch/powerpc/platforms/powernv/eeh-powernv.c | 1 + 1 file changed, 1 insertion(+)
arch/powerpc/platforms/powernv/eeh-powernv.c +1 -0
@@ -148,6 +148,7 @@ static ssize_t pnv_eeh_ei_write(struct file *filp,/* Do error injection */ ret = eeh_ops->err_inject (pe, type, func, addr, mask); +eeh_put_pe (pe);/* Release ref */ return ret <0 ? ret : count; }
Signature · 1 line
2.22.0.216.g00a2a96fc9
RFC PATCH 14/15 powerpc/eeh: Sync eeh_force_recover_write()
In reply to Sam BobroffSynchronize access to eeh_pe.
--- arch/powerpc/kernel/eeh.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
arch/powerpc/kernel/eeh.c +1 -1
@@ -1971,7 +1971,7 @@ static ssize_t eeh_force_recover_write(struct file *filp,* from an odd state (e.g. PE removed, or recovery of a * non-isolated PE) */ -__eeh_send_failure_event (pe); +__eeh_send_failure_event (pe);/* Give ref */ return ret <0 ? ret : count; }
Signature · 1 line
2.22.0.216.g00a2a96fc9
RFC PATCH 15/15 powerpc/eeh: Sync pcibios_set_pcie_reset_state()
In reply to Sam BobroffSynchronize access to eeh_pe.
--- arch/powerpc/kernel/eeh.c | 2 ++ 1 file changed, 2 insertions(+)
arch/powerpc/kernel/eeh.c +2 -0
@@ -924,6 +924,7 @@ int pcibios_set_pcie_reset_state(struct pci_dev *dev, enum pcie_reset_state stat __func__, pci_name(dev));return -EINVAL ; } +eeh_get_pe (pe);switch (state) {case pcie_deassert_reset:
@@ -957,6 +958,7 @@ int pcibios_set_pcie_reset_state(struct pci_dev *dev, enum pcie_reset_state statreturn -EINVAL ; }; +eeh_put_pe (pe);return 0 ; }
Signature · 1 line
2.22.0.216.g00a2a96fc9