summaryrefslogtreecommitdiffstats
path: root/arch/powerpc/platforms/pseries
diff options
context:
space:
mode:
Diffstat (limited to 'arch/powerpc/platforms/pseries')
-rw-r--r--arch/powerpc/platforms/pseries/Makefile7
-rw-r--r--arch/powerpc/platforms/pseries/eeh.c519
-rw-r--r--arch/powerpc/platforms/pseries/eeh_cache.c316
-rw-r--r--arch/powerpc/platforms/pseries/eeh_driver.c376
-rw-r--r--arch/powerpc/platforms/pseries/eeh_event.c39
-rw-r--r--arch/powerpc/platforms/pseries/hvconsole.c74
-rw-r--r--arch/powerpc/platforms/pseries/hvcserver.c251
-rw-r--r--arch/powerpc/platforms/pseries/iommu.c27
-rw-r--r--arch/powerpc/platforms/pseries/lpar.c92
-rw-r--r--arch/powerpc/platforms/pseries/pci_dlpar.c174
-rw-r--r--arch/powerpc/platforms/pseries/ras.c11
-rw-r--r--arch/powerpc/platforms/pseries/ras.h9
-rw-r--r--arch/powerpc/platforms/pseries/reconfig.c100
-rw-r--r--arch/powerpc/platforms/pseries/scanlog.c4
-rw-r--r--arch/powerpc/platforms/pseries/setup.c105
-rw-r--r--arch/powerpc/platforms/pseries/smp.c7
-rw-r--r--arch/powerpc/platforms/pseries/xics.c25
17 files changed, 1604 insertions, 532 deletions
diff --git a/arch/powerpc/platforms/pseries/Makefile b/arch/powerpc/platforms/pseries/Makefile
index e7ca5b1f591..61616d14407 100644
--- a/arch/powerpc/platforms/pseries/Makefile
+++ b/arch/powerpc/platforms/pseries/Makefile
@@ -1,7 +1,10 @@
obj-y := pci.o lpar.o hvCall.o nvram.o reconfig.o \
- setup.o iommu.o ras.o rtasd.o
+ setup.o iommu.o ras.o rtasd.o pci_dlpar.o
obj-$(CONFIG_SMP) += smp.o
obj-$(CONFIG_IBMVIO) += vio.o
obj-$(CONFIG_XICS) += xics.o
obj-$(CONFIG_SCANLOG) += scanlog.o
-obj-$(CONFIG_EEH) += eeh.o eeh_event.o
+obj-$(CONFIG_EEH) += eeh.o eeh_cache.o eeh_driver.o eeh_event.o
+
+obj-$(CONFIG_HVC_CONSOLE) += hvconsole.o
+obj-$(CONFIG_HVCS) += hvcserver.o
diff --git a/arch/powerpc/platforms/pseries/eeh.c b/arch/powerpc/platforms/pseries/eeh.c
index 79de2310e70..83578313ee7 100644
--- a/arch/powerpc/platforms/pseries/eeh.c
+++ b/arch/powerpc/platforms/pseries/eeh.c
@@ -76,17 +76,17 @@
*/
#define EEH_MAX_FAILS 100000
-/* Misc forward declaraions */
-static void eeh_save_bars(struct pci_dev * pdev, struct pci_dn *pdn);
-
/* RTAS tokens */
static int ibm_set_eeh_option;
static int ibm_set_slot_reset;
static int ibm_read_slot_reset_state;
static int ibm_read_slot_reset_state2;
static int ibm_slot_error_detail;
+static int ibm_get_config_addr_info;
+static int ibm_configure_bridge;
-static int eeh_subsystem_enabled;
+int eeh_subsystem_enabled;
+EXPORT_SYMBOL(eeh_subsystem_enabled);
/* Lock to avoid races due to multiple reports of an error */
static DEFINE_SPINLOCK(confirm_error_lock);
@@ -97,308 +97,23 @@ static DEFINE_SPINLOCK(slot_errbuf_lock);
static int eeh_error_buf_size;
/* System monitoring statistics */
-static DEFINE_PER_CPU(unsigned long, no_device);
-static DEFINE_PER_CPU(unsigned long, no_dn);
-static DEFINE_PER_CPU(unsigned long, no_cfg_addr);
-static DEFINE_PER_CPU(unsigned long, ignored_check);
-static DEFINE_PER_CPU(unsigned long, total_mmio_ffs);
-static DEFINE_PER_CPU(unsigned long, false_positives);
-static DEFINE_PER_CPU(unsigned long, ignored_failures);
-static DEFINE_PER_CPU(unsigned long, slot_resets);
-
-/**
- * The pci address cache subsystem. This subsystem places
- * PCI device address resources into a red-black tree, sorted
- * according to the address range, so that given only an i/o
- * address, the corresponding PCI device can be **quickly**
- * found. It is safe to perform an address lookup in an interrupt
- * context; this ability is an important feature.
- *
- * Currently, the only customer of this code is the EEH subsystem;
- * thus, this code has been somewhat tailored to suit EEH better.
- * In particular, the cache does *not* hold the addresses of devices
- * for which EEH is not enabled.
- *
- * (Implementation Note: The RB tree seems to be better/faster
- * than any hash algo I could think of for this problem, even
- * with the penalty of slow pointer chases for d-cache misses).
- */
-struct pci_io_addr_range
-{
- struct rb_node rb_node;
- unsigned long addr_lo;
- unsigned long addr_hi;
- struct pci_dev *pcidev;
- unsigned int flags;
-};
-
-static struct pci_io_addr_cache
-{
- struct rb_root rb_root;
- spinlock_t piar_lock;
-} pci_io_addr_cache_root;
-
-static inline struct pci_dev *__pci_get_device_by_addr(unsigned long addr)
-{
- struct rb_node *n = pci_io_addr_cache_root.rb_root.rb_node;
-
- while (n) {
- struct pci_io_addr_range *piar;
- piar = rb_entry(n, struct pci_io_addr_range, rb_node);
-
- if (addr < piar->addr_lo) {
- n = n->rb_left;
- } else {
- if (addr > piar->addr_hi) {
- n = n->rb_right;
- } else {
- pci_dev_get(piar->pcidev);
- return piar->pcidev;
- }
- }
- }
-
- return NULL;
-}
-
-/**
- * pci_get_device_by_addr - Get device, given only address
- * @addr: mmio (PIO) phys address or i/o port number
- *
- * Given an mmio phys address, or a port number, find a pci device
- * that implements this address. Be sure to pci_dev_put the device
- * when finished. I/O port numbers are assumed to be offset
- * from zero (that is, they do *not* have pci_io_addr added in).
- * It is safe to call this function within an interrupt.
- */
-static struct pci_dev *pci_get_device_by_addr(unsigned long addr)
-{
- struct pci_dev *dev;
- unsigned long flags;
-
- spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags);
- dev = __pci_get_device_by_addr(addr);
- spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags);
- return dev;
-}
-
-#ifdef DEBUG
-/*
- * Handy-dandy debug print routine, does nothing more
- * than print out the contents of our addr cache.
- */
-static void pci_addr_cache_print(struct pci_io_addr_cache *cache)
-{
- struct rb_node *n;
- int cnt = 0;
-
- n = rb_first(&cache->rb_root);
- while (n) {
- struct pci_io_addr_range *piar;
- piar = rb_entry(n, struct pci_io_addr_range, rb_node);
- printk(KERN_DEBUG "PCI: %s addr range %d [%lx-%lx]: %s\n",
- (piar->flags & IORESOURCE_IO) ? "i/o" : "mem", cnt,
- piar->addr_lo, piar->addr_hi, pci_name(piar->pcidev));
- cnt++;
- n = rb_next(n);
- }
-}
-#endif
-
-/* Insert address range into the rb tree. */
-static struct pci_io_addr_range *
-pci_addr_cache_insert(struct pci_dev *dev, unsigned long alo,
- unsigned long ahi, unsigned int flags)
-{
- struct rb_node **p = &pci_io_addr_cache_root.rb_root.rb_node;
- struct rb_node *parent = NULL;
- struct pci_io_addr_range *piar;
-
- /* Walk tree, find a place to insert into tree */
- while (*p) {
- parent = *p;
- piar = rb_entry(parent, struct pci_io_addr_range, rb_node);
- if (ahi < piar->addr_lo) {
- p = &parent->rb_left;
- } else if (alo > piar->addr_hi) {
- p = &parent->rb_right;
- } else {
- if (dev != piar->pcidev ||
- alo != piar->addr_lo || ahi != piar->addr_hi) {
- printk(KERN_WARNING "PIAR: overlapping address range\n");
- }
- return piar;
- }
- }
- piar = (struct pci_io_addr_range *)kmalloc(sizeof(struct pci_io_addr_range), GFP_ATOMIC);
- if (!piar)
- return NULL;
-
- piar->addr_lo = alo;
- 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
+static unsigned long no_device;
+static unsigned long no_dn;
+static unsigned long no_cfg_addr;
+static unsigned long ignored_check;
+static unsigned long total_mmio_ffs;
+static unsigned long false_positives;
+static unsigned long ignored_failures;
+static unsigned long slot_resets;
- rb_link_node(&piar->rb_node, parent, p);
- rb_insert_color(&piar->rb_node, &pci_io_addr_cache_root.rb_root);
-
- return piar;
-}
-
-static void __pci_addr_cache_insert_device(struct pci_dev *dev)
-{
- struct device_node *dn;
- struct pci_dn *pdn;
- int i;
- int inserted = 0;
-
- dn = pci_device_to_OF_node(dev);
- if (!dn) {
- printk(KERN_WARNING "PCI: no pci dn found for dev=%s\n", pci_name(dev));
- return;
- }
-
- /* Skip any devices for which EEH is not enabled. */
- pdn = PCI_DN(dn);
- if (!(pdn->eeh_mode & EEH_MODE_SUPPORTED) ||
- pdn->eeh_mode & EEH_MODE_NOCHECK) {
-#ifdef DEBUG
- printk(KERN_INFO "PCI: skip building address cache for=%s - %s\n",
- pci_name(dev), pdn->node->full_name);
-#endif
- return;
- }
-
- /* The cache holds a reference to the device... */
- pci_dev_get(dev);
-
- /* Walk resources on this device, poke them into the tree */
- for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
- unsigned long start = pci_resource_start(dev,i);
- unsigned long end = pci_resource_end(dev,i);
- unsigned int flags = pci_resource_flags(dev,i);
-
- /* We are interested only bus addresses, not dma or other stuff */
- if (0 == (flags & (IORESOURCE_IO | IORESOURCE_MEM)))
- continue;
- if (start == 0 || ~start == 0 || end == 0 || ~end == 0)
- continue;
- pci_addr_cache_insert(dev, start, end, flags);
- inserted = 1;
- }
-
- /* If there was nothing to add, the cache has no reference... */
- if (!inserted)
- pci_dev_put(dev);
-}
-
-/**
- * pci_addr_cache_insert_device - Add a device to the address cache
- * @dev: PCI device whose I/O addresses we are interested in.
- *
- * In order to support the fast lookup of devices based on addresses,
- * we maintain a cache of devices that can be quickly searched.
- * This routine adds a device to that cache.
- */
-static void pci_addr_cache_insert_device(struct pci_dev *dev)
-{
- unsigned long flags;
-
- spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags);
- __pci_addr_cache_insert_device(dev);
- spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags);
-}
-
-static inline void __pci_addr_cache_remove_device(struct pci_dev *dev)
-{
- struct rb_node *n;
- int removed = 0;
-
-restart:
- n = rb_first(&pci_io_addr_cache_root.rb_root);
- while (n) {
- struct pci_io_addr_range *piar;
- piar = rb_entry(n, struct pci_io_addr_range, rb_node);
-
- if (piar->pcidev == dev) {
- rb_erase(n, &pci_io_addr_cache_root.rb_root);
- removed = 1;
- kfree(piar);
- goto restart;
- }
- n = rb_next(n);
- }
-
- /* The cache no longer holds its reference to this device... */
- if (removed)
- pci_dev_put(dev);
-}
-
-/**
- * pci_addr_cache_remove_device - remove pci device from addr cache
- * @dev: device to remove
- *
- * Remove a device from the addr-cache tree.
- * This is potentially expensive, since it will walk
- * the tree multiple times (once per resource).
- * But so what; device removal doesn't need to be that fast.
- */
-static void pci_addr_cache_remove_device(struct pci_dev *dev)
-{
- unsigned long flags;
-
- spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags);
- __pci_addr_cache_remove_device(dev);
- spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags);
-}
-
-/**
- * pci_addr_cache_build - Build a cache of I/O addresses
- *
- * Build a cache of pci i/o addresses. This cache will be used to
- * find the pci device that corresponds to a given address.
- * This routine scans all pci busses to build the cache.
- * Must be run late in boot process, after the pci controllers
- * have been scaned for devices (after all device resources are known).
- */
-void __init pci_addr_cache_build(void)
-{
- struct device_node *dn;
- struct pci_dev *dev = NULL;
-
- if (!eeh_subsystem_enabled)
- return;
-
- spin_lock_init(&pci_io_addr_cache_root.piar_lock);
-
- while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) {
- /* Ignore PCI bridges ( XXX why ??) */
- if ((dev->class >> 16) == PCI_BASE_CLASS_BRIDGE) {
- continue;
- }
- pci_addr_cache_insert_device(dev);
-
- /* Save the BAR's; firmware doesn't restore these after EEH reset */
- dn = pci_device_to_OF_node(dev);
- eeh_save_bars(dev, PCI_DN(dn));
- }
-
-#ifdef DEBUG
- /* Verify tree built up above, echo back the list of addrs. */
- pci_addr_cache_print(&pci_io_addr_cache_root);
-#endif
-}
+#define IS_BRIDGE(class_code) (((class_code)<<16) == PCI_BASE_CLASS_BRIDGE)
/* --------------------------------------------------------------- */
-/* Above lies the PCI Address Cache. Below lies the EEH event infrastructure */
+/* Below lies the EEH event infrastructure */
void eeh_slot_error_detail (struct pci_dn *pdn, int severity)
{
+ int config_addr;
unsigned long flags;
int rc;
@@ -406,8 +121,13 @@ void eeh_slot_error_detail (struct pci_dn *pdn, int severity)
spin_lock_irqsave(&slot_errbuf_lock, flags);
memset(slot_errbuf, 0, eeh_error_buf_size);
+ /* Use PE configuration address, if present */
+ config_addr = pdn->eeh_config_addr;
+ if (pdn->eeh_pe_config_addr)
+ config_addr = pdn->eeh_pe_config_addr;
+
rc = rtas_call(ibm_slot_error_detail,
- 8, 1, NULL, pdn->eeh_config_addr,
+ 8, 1, NULL, config_addr,
BUID_HI(pdn->phb->buid),
BUID_LO(pdn->phb->buid), NULL, 0,
virt_to_phys(slot_errbuf),
@@ -427,6 +147,7 @@ void eeh_slot_error_detail (struct pci_dn *pdn, int severity)
static int read_slot_reset_state(struct pci_dn *pdn, int rets[])
{
int token, outputs;
+ int config_addr;
if (ibm_read_slot_reset_state2 != RTAS_UNKNOWN_SERVICE) {
token = ibm_read_slot_reset_state2;
@@ -437,7 +158,12 @@ static int read_slot_reset_state(struct pci_dn *pdn, int rets[])
outputs = 3;
}
- return rtas_call(token, 3, outputs, rets, pdn->eeh_config_addr,
+ /* Use PE configuration address, if present */
+ config_addr = pdn->eeh_config_addr;
+ if (pdn->eeh_pe_config_addr)
+ config_addr = pdn->eeh_pe_config_addr;
+
+ return rtas_call(token, 3, outputs, rets, config_addr,
BUID_HI(pdn->phb->buid), BUID_LO(pdn->phb->buid));
}
@@ -461,7 +187,7 @@ static inline unsigned long eeh_token_to_phys(unsigned long token)
/**
* Return the "partitionable endpoint" (pe) under which this device lies
*/
-static struct device_node * find_device_pe(struct device_node *dn)
+struct device_node * find_device_pe(struct device_node *dn)
{
while ((dn->parent) && PCI_DN(dn->parent) &&
(PCI_DN(dn->parent)->eeh_mode & EEH_MODE_SUPPORTED)) {
@@ -482,8 +208,14 @@ static void __eeh_mark_slot (struct device_node *dn, int mode_flag)
{
while (dn) {
if (PCI_DN(dn)) {
+ /* Mark the pci device driver too */
+ struct pci_dev *dev = PCI_DN(dn)->pcidev;
+
PCI_DN(dn)->eeh_mode |= mode_flag;
+ if (dev && dev->driver)
+ dev->error_state = pci_channel_io_frozen;
+
if (dn->child)
__eeh_mark_slot (dn->child, mode_flag);
}
@@ -494,6 +226,11 @@ static void __eeh_mark_slot (struct device_node *dn, int mode_flag)
void eeh_mark_slot (struct device_node *dn, int mode_flag)
{
dn = find_device_pe (dn);
+
+ /* Back up one, since config addrs might be shared */
+ if (PCI_DN(dn) && PCI_DN(dn)->eeh_pe_config_addr)
+ dn = dn->parent;
+
PCI_DN(dn)->eeh_mode |= mode_flag;
__eeh_mark_slot (dn->child, mode_flag);
}
@@ -515,7 +252,13 @@ void eeh_clear_slot (struct device_node *dn, int mode_flag)
{
unsigned long flags;
spin_lock_irqsave(&confirm_error_lock, flags);
+
dn = find_device_pe (dn);
+
+ /* Back up one, since config addrs might be shared */
+ if (PCI_DN(dn) && PCI_DN(dn)->eeh_pe_config_addr)
+ dn = dn->parent;
+
PCI_DN(dn)->eeh_mode &= ~mode_flag;
PCI_DN(dn)->eeh_check_count = 0;
__eeh_clear_slot (dn->child, mode_flag);
@@ -543,15 +286,16 @@ int eeh_dn_check_failure(struct device_node *dn, struct pci_dev *dev)
int rets[3];
unsigned long flags;
struct pci_dn *pdn;
+ enum pci_channel_state state;
int rc = 0;
- __get_cpu_var(total_mmio_ffs)++;
+ total_mmio_ffs++;
if (!eeh_subsystem_enabled)
return 0;
if (!dn) {
- __get_cpu_var(no_dn)++;
+ no_dn++;
return 0;
}
pdn = PCI_DN(dn);
@@ -559,7 +303,7 @@ int eeh_dn_check_failure(struct device_node *dn, struct pci_dev *dev)
/* Access to IO BARs might get this far and still not want checking. */
if (!(pdn->eeh_mode & EEH_MODE_SUPPORTED) ||
pdn->eeh_mode & EEH_MODE_NOCHECK) {
- __get_cpu_var(ignored_check)++;
+ ignored_check++;
#ifdef DEBUG
printk ("EEH:ignored check (%x) for %s %s\n",
pdn->eeh_mode, pci_name (dev), dn->full_name);
@@ -567,8 +311,8 @@ int eeh_dn_check_failure(struct device_node *dn, struct pci_dev *dev)
return 0;
}
- if (!pdn->eeh_config_addr) {
- __get_cpu_var(no_cfg_addr)++;
+ if (!pdn->eeh_config_addr && !pdn->eeh_pe_config_addr) {
+ no_cfg_addr++;
return 0;
}
@@ -610,7 +354,7 @@ int eeh_dn_check_failure(struct device_node *dn, struct pci_dev *dev)
if (ret != 0) {
printk(KERN_WARNING "EEH: read_slot_reset_state() failed; rc=%d dn=%s\n",
ret, dn->full_name);
- __get_cpu_var(false_positives)++;
+ false_positives++;
rc = 0;
goto dn_unlock;
}
@@ -619,14 +363,14 @@ int eeh_dn_check_failure(struct device_node *dn, struct pci_dev *dev)
if (rets[1] != 1) {
printk(KERN_WARNING "EEH: event on unsupported device, rc=%d dn=%s\n",
ret, dn->full_name);
- __get_cpu_var(false_positives)++;
+ false_positives++;
rc = 0;
goto dn_unlock;
}
/* If not the kind of error we know about, punt. */
if (rets[0] != 2 && rets[0] != 4 && rets[0] != 5) {
- __get_cpu_var(false_positives)++;
+ false_positives++;
rc = 0;
goto dn_unlock;
}
@@ -634,12 +378,12 @@ int eeh_dn_check_failure(struct device_node *dn, struct pci_dev *dev)
/* Note that config-io to empty slots may fail;
* we recognize empty because they don't have children. */
if ((rets[0] == 5) && (dn->child == NULL)) {
- __get_cpu_var(false_positives)++;
+ false_positives++;
rc = 0;
goto dn_unlock;
}
- __get_cpu_var(slot_resets)++;
+ slot_resets++;
/* Avoid repeated reports of this failure, including problems
* with other functions on this device, and functions under
@@ -647,8 +391,13 @@ int eeh_dn_check_failure(struct device_node *dn, struct pci_dev *dev)
eeh_mark_slot (dn, EEH_MODE_ISOLATED);
spin_unlock_irqrestore(&confirm_error_lock, flags);
- eeh_send_failure_event (dn, dev, rets[0], rets[2]);
-
+ state = pci_channel_io_normal;
+ if ((rets[0] == 2) || (rets[0] == 4))
+ state = pci_channel_io_frozen;
+ if (rets[0] == 5)
+ state = pci_channel_io_perm_failure;
+ eeh_send_failure_event (dn, dev, state, rets[2]);
+
/* Most EEH events are due to device driver bugs. Having
* a stack trace will help the device-driver authors figure
* out what happened. So print that out. */
@@ -684,7 +433,7 @@ unsigned long eeh_check_failure(const volatile void __iomem *token, unsigned lon
addr = eeh_token_to_phys((unsigned long __force) token);
dev = pci_get_device_by_addr(addr);
if (!dev) {
- __get_cpu_var(no_device)++;
+ no_device++;
return val;
}
@@ -715,11 +464,16 @@ eeh_slot_availability(struct pci_dn *pdn)
if (rc) return rc;
if (rets[1] == 0) return -1; /* EEH is not supported */
- if (rets[0] == 0) return 0; /* Oll Korrect */
+ if (rets[0] == 0) return 0; /* Oll Korrect */
if (rets[0] == 5) {
if (rets[2] == 0) return -1; /* permanently unavailable */
return rets[2]; /* number of millisecs to wait */
}
+ if (rets[0] == 1)
+ return 250;
+
+ printk (KERN_ERR "EEH: Slot unavailable: rc=%d, rets=%d %d %d\n",
+ rc, rets[0], rets[1], rets[2]);
return -1;
}
@@ -736,6 +490,7 @@ eeh_slot_availability(struct pci_dn *pdn)
static void
rtas_pci_slot_reset(struct pci_dn *pdn, int state)
{
+ int config_addr;
int rc;
BUG_ON (pdn==NULL);
@@ -746,8 +501,13 @@ rtas_pci_slot_reset(struct pci_dn *pdn, int state)
return;
}
+ /* Use PE configuration address, if present */
+ config_addr = pdn->eeh_config_addr;
+ if (pdn->eeh_pe_config_addr)
+ config_addr = pdn->eeh_pe_config_addr;
+
rc = rtas_call(ibm_set_slot_reset,4,1, NULL,
- pdn->eeh_config_addr,
+ config_addr,
BUID_HI(pdn->phb->buid),
BUID_LO(pdn->phb->buid),
state);
@@ -760,9 +520,11 @@ rtas_pci_slot_reset(struct pci_dn *pdn, int state)
/** rtas_set_slot_reset -- assert the pci #RST line for 1/4 second
* dn -- device node to be reset.
+ *
+ * Return 0 if success, else a non-zero value.
*/
-void
+int
rtas_set_slot_reset(struct pci_dn *pdn)
{
int i, rc;
@@ -792,10 +554,21 @@ rtas_set_slot_reset(struct pci_dn *pdn)
* ready to be used; if not, wait for recovery. */
for (i=0; i<10; i++) {
rc = eeh_slot_availability (pdn);
- if (rc <= 0) break;
+ if (rc < 0)
+ printk (KERN_ERR "EEH: failed (%d) to reset slot %s\n", rc, pdn->node->full_name);
+ if (rc == 0)
+ return 0;
+ if (rc < 0)
+ return -1;
msleep (rc+100);
}
+
+ rc = eeh_slot_availability (pdn);
+ if (rc)
+ printk (KERN_ERR "EEH: timeout resetting slot %s\n", pdn->node->full_name);
+
+ return rc;
}
/* ------------------------------------------------------- */
@@ -850,7 +623,7 @@ void eeh_restore_bars(struct pci_dn *pdn)
if (!pdn)
return;
- if (! pdn->eeh_is_bridge)
+ if ((pdn->eeh_mode & EEH_MODE_SUPPORTED) && !IS_BRIDGE(pdn->class_code))
__restore_bars (pdn);
dn = pdn->node->child;
@@ -868,30 +641,30 @@ void eeh_restore_bars(struct pci_dn *pdn)
* PCI devices are added individuallly; but, for the restore,
* an entire slot is reset at a time.
*/
-static void eeh_save_bars(struct pci_dev * pdev, struct pci_dn *pdn)
+static void eeh_save_bars(struct pci_dn *pdn)
{
int i;
- if (!pdev || !pdn )
+ if (!pdn )
return;
for (i = 0; i < 16; i++)
- pci_read_config_dword(pdev, i * 4, &pdn->config_space[i]);
-
- if (pdev->hdr_type == PCI_HEADER_TYPE_BRIDGE)
- pdn->eeh_is_bridge = 1;
+ rtas_read_config(pdn, i * 4, 4, &pdn->config_space[i]);
}
void
rtas_configure_bridge(struct pci_dn *pdn)
{
- int token = rtas_token ("ibm,configure-bridge");
+ int config_addr;
int rc;
- if (token == RTAS_UNKNOWN_SERVICE)
- return;
- rc = rtas_call(token,3,1, NULL,
- pdn->eeh_config_addr,
+ /* Use PE configuration address, if present */
+ config_addr = pdn->eeh_config_addr;
+ if (pdn->eeh_pe_config_addr)
+ config_addr = pdn->eeh_pe_config_addr;
+
+ rc = rtas_call(ibm_configure_bridge,3,1, NULL,
+ config_addr,
BUID_HI(pdn->phb->buid),
BUID_LO(pdn->phb->buid));
if (rc) {
@@ -926,6 +699,7 @@ static void *early_enable_eeh(struct device_node *dn, void *data)
int enable;
struct pci_dn *pdn = PCI_DN(dn);
+ pdn->class_code = 0;
pdn->eeh_mode = 0;
pdn->eeh_check_count = 0;
pdn->eeh_freeze_count = 0;
@@ -942,6 +716,7 @@ static void *early_enable_eeh(struct device_node *dn, void *data)
pdn->eeh_mode |= EEH_MODE_NOCHECK;
return NULL;
}
+ pdn->class_code = *class_code;
/*
* Now decide if we are going to "Disable" EEH checking
@@ -952,8 +727,10 @@ static void *early_enable_eeh(struct device_node *dn, void *data)
* But there are a few cases like display devices that make sense.
*/
enable = 1; /* i.e. we will do checking */
+#if 0
if ((*class_code >> 16) == PCI_BASE_CLASS_DISPLAY)
enable = 0;
+#endif
if (!enable)
pdn->eeh_mode |= EEH_MODE_NOCHECK;
@@ -972,8 +749,22 @@ static void *early_enable_eeh(struct device_node *dn, void *data)
eeh_subsystem_enabled = 1;
pdn->eeh_mode |= EEH_MODE_SUPPORTED;
pdn->eeh_config_addr = regs[0];
+
+ /* If the newer, better, ibm,get-config-addr-info is supported,
+ * then use that instead. */
+ pdn->eeh_pe_config_addr = 0;
+ if (ibm_get_config_addr_info != RTAS_UNKNOWN_SERVICE) {
+ unsigned int rets[2];
+ ret = rtas_call (ibm_get_config_addr_info, 4, 2, rets,
+ pdn->eeh_config_addr,
+ info->buid_hi, info->buid_lo,
+ 0);
+ if (ret == 0)
+ pdn->eeh_pe_config_addr = rets[0];
+ }
#ifdef DEBUG
- printk(KERN_DEBUG "EEH: %s: eeh enabled\n", dn->full_name);
+ printk(KERN_DEBUG "EEH: %s: eeh enabled, config=%x pe_config=%x\n",
+ dn->full_name, pdn->eeh_config_addr, pdn->eeh_pe_config_addr);
#endif
} else {
@@ -992,6 +783,7 @@ static void *early_enable_eeh(struct device_node *dn, void *data)
dn->full_name);
}
+ eeh_save_bars(pdn);
return NULL;
}
@@ -1025,6 +817,8 @@ void __init eeh_init(void)
ibm_read_slot_reset_state2 = rtas_token("ibm,read-slot-reset-state2");
ibm_read_slot_reset_state = rtas_token("ibm,read-slot-reset-state");
ibm_slot_error_detail = rtas_token("ibm,slot-error-detail");
+ ibm_get_config_addr_info = rtas_token("ibm,get-config-addr-info");
+ ibm_configure_bridge = rtas_token ("ibm,configure-bridge");
if (ibm_set_eeh_option == RTAS_UNKNOWN_SERVICE)
return;
@@ -1079,12 +873,10 @@ void eeh_add_device_early(struct device_node *dn)
if (!dn || !PCI_DN(dn))
return;
phb = PCI_DN(dn)->phb;
- if (NULL == phb || 0 == phb->buid) {
- printk(KERN_WARNING "EEH: Expected buid but found none for %s\n",
- dn->full_name);
- dump_stack();
+
+ /* USB Bus children of PCI devices will not have BUID's */
+ if (NULL == phb || 0 == phb->buid)
return;
- }
info.buid_hi = BUID_HI(phb->buid);
info.buid_lo = BUID_LO(phb->buid);
@@ -1092,6 +884,15 @@ void eeh_add_device_early(struct device_node *dn)
}
EXPORT_SYMBOL_GPL(eeh_add_device_early);
+void eeh_add_device_tree_early(struct device_node *dn)
+{
+ struct device_node *sib;
+ for (sib = dn->child; sib; sib = sib->sibling)
+ eeh_add_device_tree_early(sib);
+ eeh_add_device_early(dn);
+}
+EXPORT_SYMBOL_GPL(eeh_add_device_tree_early);
+
/**
* eeh_add_device_late - perform EEH initialization for the indicated pci device
* @dev: pci device for which to set up EEH
@@ -1117,7 +918,6 @@ void eeh_add_device_late(struct pci_dev *dev)
pdn->pcidev = dev;
pci_addr_cache_insert_device (dev);
- eeh_save_bars(dev, pdn);
}
EXPORT_SYMBOL_GPL(eeh_add_device_late);
@@ -1146,27 +946,28 @@ void eeh_remove_device(struct pci_dev *dev)
}
EXPORT_SYMBOL_GPL(eeh_remove_device);
-static int proc_eeh_show(struct seq_file *m, void *v)
-{
- unsigned int cpu;
- unsigned long ffs = 0, positives = 0, failures = 0;
- unsigned long resets = 0;
- unsigned long no_dev = 0, no_dn = 0, no_cfg = 0, no_check = 0;
-
- for_each_cpu(cpu) {
- ffs += per_cpu(total_mmio_ffs, cpu);
- positives += per_cpu(false_positives, cpu);
- failures += per_cpu(ignored_failures, cpu);
- resets += per_cpu(slot_resets, cpu);
- no_dev += per_cpu(no_device, cpu);
- no_dn += per_cpu(no_dn, cpu);
- no_cfg += per_cpu(no_cfg_addr, cpu);
- no_check += per_cpu(ignored_check, cpu);
+void eeh_remove_bus_device(struct pci_dev *dev)
+{
+ eeh_remove_device(dev);
+ if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) {
+ struct pci_bus *bus = dev->subordinate;
+ struct list_head *ln;
+ if (!bus)
+ return;
+ for (ln = bus->devices.next; ln != &bus->devices; ln = ln->next) {
+ struct pci_dev *pdev = pci_dev_b(ln);
+ if (pdev)
+ eeh_remove_bus_device(pdev);
+ }
}
+}
+EXPORT_SYMBOL_GPL(eeh_remove_bus_device);
+static int proc_eeh_show(struct seq_file *m, void *v)
+{
if (0 == eeh_subsystem_enabled) {
seq_printf(m, "EEH Subsystem is globally disabled\n");
- seq_printf(m, "eeh_total_mmio_ffs=%ld\n", ffs);
+ seq_printf(m, "eeh_total_mmio_ffs=%ld\n", total_mmio_ffs);
} else {
seq_printf(m, "EEH Subsystem is enabled\n");
seq_printf(m,
@@ -1178,8 +979,10 @@ static int proc_eeh_show(struct seq_file *m, void *v)
"eeh_false_positives=%ld\n"
"eeh_ignored_failures=%ld\n"
"eeh_slot_resets=%ld\n",
- no_dev, no_dn, no_cfg, no_check,
- ffs, positives, failures, resets);
+ no_device, no_dn, no_cfg_addr,
+ ignored_check, total_mmio_ffs,
+ false_positives, ignored_failures,
+ slot_resets);
}
return 0;
diff --git a/arch/powerpc/platforms/pseries/eeh_cache.c b/arch/powerpc/platforms/pseries/eeh_cache.c
new file mode 100644
index 00000000000..d4a402c5866
--- /dev/null
+++ b/arch/powerpc/platforms/pseries/eeh_cache.c
@@ -0,0 +1,316 @@
+/*
+ * eeh_cache.c
+ * PCI address cache; allows the lookup of PCI devices based on I/O address
+ *
+ * Copyright (C) 2004 Linas Vepstas <linas@austin.ibm.com> IBM Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/list.h>
+#include <linux/pci.h>
+#include <linux/rbtree.h>
+#include <linux/spinlock.h>
+#include <asm/atomic.h>
+#include <asm/pci-bridge.h>
+#include <asm/ppc-pci.h>
+
+#undef DEBUG
+
+/**
+ * The pci address cache subsystem. This subsystem places
+ * PCI device address resources into a red-black tree, sorted
+ * according to the address range, so that given only an i/o
+ * address, the corresponding PCI device can be **quickly**
+ * found. It is safe to perform an address lookup in an interrupt
+ * context; this ability is an important feature.
+ *
+ * Currently, the only customer of this code is the EEH subsystem;
+ * thus, this code has been somewhat tailored to suit EEH better.
+ * In particular, the cache does *not* hold the addresses of devices
+ * for which EEH is not enabled.
+ *
+ * (Implementation Note: The RB tree seems to be better/faster
+ * than any hash algo I could think of for this problem, even
+ * with the penalty of slow pointer chases for d-cache misses).
+ */
+struct pci_io_addr_range
+{
+ struct rb_node rb_node;
+ unsigned long addr_lo;
+ unsigned long addr_hi;
+ struct pci_dev *pcidev;
+ unsigned int flags;
+};
+
+static struct pci_io_addr_cache
+{
+ struct rb_root rb_root;
+ spinlock_t piar_lock;
+} pci_io_addr_cache_root;
+
+static inline struct pci_dev *__pci_get_device_by_addr(unsigned long addr)
+{
+ struct rb_node *n = pci_io_addr_cache_root.rb_root.rb_node;
+
+ while (n) {
+ struct pci_io_addr_range *piar;
+ piar = rb_entry(n, struct pci_io_addr_range, rb_node);
+
+ if (addr < piar->addr_lo) {
+ n = n->rb_left;
+ } else {
+ if (addr > piar->addr_hi) {
+ n = n->rb_right;
+ } else {
+ pci_dev_get(piar->pcidev);
+ return piar->pcidev;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * pci_get_device_by_addr - Get device, given only address
+ * @addr: mmio (PIO) phys address or i/o port number
+ *
+ * Given an mmio phys address, or a port number, find a pci device
+ * that implements this address. Be sure to pci_dev_put the device
+ * when finished. I/O port numbers are assumed to be offset
+ * from zero (that is, they do *not* have pci_io_addr added in).
+ * It is safe to call this function within an interrupt.
+ */
+struct pci_dev *pci_get_device_by_addr(unsigned long addr)
+{
+ struct pci_dev *dev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags);
+ dev = __pci_get_device_by_addr(addr);
+ spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags);
+ return dev;
+}
+
+#ifdef DEBUG
+/*
+ * Handy-dandy debug print routine, does nothing more
+ * than print out the contents of our addr cache.
+ */
+static void pci_addr_cache_print(struct pci_io_addr_cache *cache)
+{
+ struct rb_node *n;
+ int cnt = 0;
+
+ n = rb_first(&cache->rb_root);
+ while (n) {
+ struct pci_io_addr_range *piar;
+ piar = rb_entry(n, struct pci_io_addr_range, rb_node);
+ printk(KERN_DEBUG "PCI: %s addr range %d [%lx-%lx]: %s\n",
+ (piar->flags & IORESOURCE_IO) ? "i/o" : "mem", cnt,
+ piar->addr_lo, piar->addr_hi, pci_name(piar->pcidev));
+ cnt++;
+ n = rb_next(n);
+ }
+}
+#endif
+
+/* Insert address range into the rb tree. */
+static struct pci_io_addr_range *
+pci_addr_cache_insert(struct pci_dev *dev, unsigned long alo,
+ unsigned long ahi, unsigned int flags)
+{
+ struct rb_node **p = &pci_io_addr_cache_root.rb_root.rb_node;
+ struct rb_node *parent = NULL;
+ struct pci_io_addr_range *piar;
+
+ /* Walk tree, find a place to insert into tree */
+ while (*p) {
+ parent = *p;
+ piar = rb_entry(parent, struct pci_io_addr_range, rb_node);
+ if (ahi < piar->addr_lo) {
+ p = &parent->rb_left;
+ } else if (alo > piar->addr_hi) {
+ p = &parent->rb_right;
+ } else {
+ if (dev != piar->pcidev ||
+ alo != piar->addr_lo || ahi != piar->addr_hi) {
+ printk(KERN_WARNING "PIAR: overlapping address range\n");
+ }
+ return piar;
+ }
+ }
+ piar = (struct pci_io_addr_range *)kmalloc(sizeof(struct pci_io_addr_range), GFP_ATOMIC);
+ if (!piar)
+ return NULL;
+
+ piar->addr_lo = alo;
+ 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);
+
+ return piar;
+}
+
+static void __pci_addr_cache_insert_device(struct pci_dev *dev)
+{
+ struct device_node *dn;
+ struct pci_dn *pdn;
+ int i;
+ int inserted = 0;
+
+ dn = pci_device_to_OF_node(dev);
+ if (!dn) {
+ printk(KERN_WARNING "PCI: no pci dn found for dev=%s\n", pci_name(dev));
+ return;
+ }
+
+ /* Skip any devices for which EEH is not enabled. */
+ pdn = PCI_DN(dn);
+ if (!(pdn->eeh_mode & EEH_MODE_SUPPORTED) ||
+ pdn->eeh_mode & EEH_MODE_NOCHECK) {
+#ifdef DEBUG
+ printk(KERN_INFO "PCI: skip building address cache for=%s - %s\n",
+ pci_name(dev), pdn->node->full_name);
+#endif
+ return;
+ }
+
+ /* The cache holds a reference to the device... */
+ pci_dev_get(dev);
+
+ /* Walk resources on this device, poke them into the tree */
+ for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
+ unsigned long start = pci_resource_start(dev,i);
+ unsigned long end = pci_resource_end(dev,i);
+ unsigned int flags = pci_resource_flags(dev,i);
+
+ /* We are interested only bus addresses, not dma or other stuff */
+ if (0 == (flags & (IORESOURCE_IO | IORESOURCE_MEM)))
+ continue;
+ if (start == 0 || ~start == 0 || end == 0 || ~end == 0)
+ continue;
+ pci_addr_cache_insert(dev, start, end, flags);
+ inserted = 1;
+ }
+
+ /* If there was nothing to add, the cache has no reference... */
+ if (!inserted)
+ pci_dev_put(dev);
+}
+
+/**
+ * pci_addr_cache_insert_device - Add a device to the address cache
+ * @dev: PCI device whose I/O addresses we are interested in.
+ *
+ * In order to support the fast lookup of devices based on addresses,
+ * we maintain a cache of devices that can be quickly searched.
+ * This routine adds a device to that cache.
+ */
+void pci_addr_cache_insert_device(struct pci_dev *dev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags);
+ __pci_addr_cache_insert_device(dev);
+ spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags);
+}
+
+static inline void __pci_addr_cache_remove_device(struct pci_dev *dev)
+{
+ struct rb_node *n;
+ int removed = 0;
+
+restart:
+ n = rb_first(&pci_io_addr_cache_root.rb_root);
+ while (n) {
+ struct pci_io_addr_range *piar;
+ piar = rb_entry(n, struct pci_io_addr_range, rb_node);
+
+ if (piar->pcidev == dev) {
+ rb_erase(n, &pci_io_addr_cache_root.rb_root);
+ removed = 1;
+ kfree(piar);
+ goto restart;
+ }
+ n = rb_next(n);
+ }
+
+ /* The cache no longer holds its reference to this device... */
+ if (removed)
+ pci_dev_put(dev);
+}
+
+/**
+ * pci_addr_cache_remove_device - remove pci device from addr cache
+ * @dev: device to remove
+ *
+ * Remove a device from the addr-cache tree.
+ * This is potentially expensive, since it will walk
+ * the tree multiple times (once per resource).
+ * But so what; device removal doesn't need to be that fast.
+ */
+void pci_addr_cache_remove_device(struct pci_dev *dev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags);
+ __pci_addr_cache_remove_device(dev);
+ spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags);
+}
+
+/**
+ * pci_addr_cache_build - Build a cache of I/O addresses
+ *
+ * Build a cache of pci i/o addresses. This cache will be used to
+ * find the pci device that corresponds to a given address.
+ * This routine scans all pci busses to build the cache.
+ * Must be run late in boot process, after the pci controllers
+ * have been scaned for devices (after all device resources are known).
+ */
+void __init pci_addr_cache_build(void)
+{
+ struct device_node *dn;
+ struct pci_dev *dev = NULL;
+
+ spin_lock_init(&pci_io_addr_cache_root.piar_lock);
+
+ while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) {
+ /* Ignore PCI bridges */
+ if ((dev->class >> 16) == PCI_BASE_CLASS_BRIDGE)
+ continue;
+
+ pci_addr_cache_insert_device(dev);
+
+ dn = pci_device_to_OF_node(dev);
+ pci_dev_get (dev); /* matching put is in eeh_remove_device() */
+ PCI_DN(dn)->pcidev = dev;
+ }
+
+#ifdef DEBUG
+ /* Verify tree built up above, echo back the list of addrs. */
+ pci_addr_cache_print(&pci_io_addr_cache_root);
+#endif
+}
+
diff --git a/arch/powerpc/platforms/pseries/eeh_driver.c b/arch/powerpc/platforms/pseries/eeh_driver.c
new file mode 100644
index 00000000000..6373372932b
--- /dev/null
+++ b/arch/powerpc/platforms/pseries/eeh_driver.c
@@ -0,0 +1,376 @@
+/*
+ * PCI Error Recovery Driver for RPA-compliant PPC64 platform.
+ * Copyright (C) 2004, 2005 Linas Vepstas <linas@linas.org>
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Send feedback to <linas@us.ibm.com>
+ *
+ */
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/pci.h>
+#include <asm/eeh.h>
+#include <asm/eeh_event.h>
+#include <asm/ppc-pci.h>
+#include <asm/pci-bridge.h>
+#include <asm/prom.h>
+#include <asm/rtas.h>
+
+
+static inline const char * pcid_name (struct pci_dev *pdev)
+{
+ if (pdev->dev.driver)
+ return pdev->dev.driver->name;
+ return "";
+}
+
+#ifdef DEBUG
+static void print_device_node_tree (struct pci_dn *pdn, int dent)
+{
+ int i;
+ if (!pdn) return;
+ for (i=0;i<dent; i++)
+ printk(" ");
+ printk("dn=%s mode=%x \tcfg_addr=%x pe_addr=%x \tfull=%s\n",
+ pdn->node->name, pdn->eeh_mode, pdn->eeh_config_addr,
+ pdn->eeh_pe_config_addr, pdn->node->full_name);
+ dent += 3;
+ struct device_node *pc = pdn->node->child;
+ while (pc) {
+ print_device_node_tree(PCI_DN(pc), dent);
+ pc = pc->sibling;
+ }
+}
+#endif
+
+/**
+ * irq_in_use - return true if this irq is being used
+ */
+static int irq_in_use(unsigned int irq)
+{
+ int rc = 0;
+ unsigned long flags;
+ struct irq_desc *desc = irq_desc + irq;
+
+ spin_lock_irqsave(&desc->lock, flags);
+ if (desc->action)
+ rc = 1;
+ spin_unlock_irqrestore(&desc->lock, flags);
+ return rc;
+}
+
+/* ------------------------------------------------------- */
+/** eeh_report_error - report an EEH error to each device,
+ * collect up and merge the device responses.
+ */
+
+static void eeh_report_error(struct pci_dev *dev, void *userdata)
+{
+ enum pci_ers_result rc, *res = userdata;
+ struct pci_driver *driver = dev->driver;
+
+ dev->error_state = pci_channel_io_frozen;
+
+ if (!driver)
+ return;
+
+ if (irq_in_use (dev->irq)) {
+ struct device_node *dn = pci_device_to_OF_node(dev);
+ PCI_DN(dn)->eeh_mode |= EEH_MODE_IRQ_DISABLED;
+ disable_irq_nosync(dev->irq);
+ }
+ if (!driver->err_handler)
+ return;
+ if (!driver->err_handler->error_detected)
+ return;
+
+ rc = driver->err_handler->error_detected (dev, pci_channel_io_frozen);
+ if (*res == PCI_ERS_RESULT_NONE) *res = rc;
+ if (*res == PCI_ERS_RESULT_NEED_RESET) return;
+ if (*res == PCI_ERS_RESULT_DISCONNECT &&
+ rc == PCI_ERS_RESULT_NEED_RESET) *res = rc;
+}
+
+/** eeh_report_reset -- tell this device that the pci slot
+ * has been reset.
+ */
+
+static void eeh_report_reset(struct pci_dev *dev, void *userdata)
+{
+ struct pci_driver *driver = dev->driver;
+ struct device_node *dn = pci_device_to_OF_node(dev);
+
+ if (!driver)
+ return;
+
+ if ((PCI_DN(dn)->eeh_mode) & EEH_MODE_IRQ_DISABLED) {
+ PCI_DN(dn)->eeh_mode &= ~EEH_MODE_IRQ_DISABLED;
+ enable_irq(dev->irq);
+ }
+ if (!driver->err_handler)
+ return;
+ if (!driver->err_handler->slot_reset)
+ return;
+
+ driver->err_handler->slot_reset(dev);
+}
+
+static void eeh_report_resume(struct pci_dev *dev, void *userdata)
+{
+ struct pci_driver *driver = dev->driver;
+
+ dev->error_state = pci_channel_io_normal;
+
+ if (!driver)
+ return;
+ if (!driver->err_handler)
+ return;
+ if (!driver->err_handler->resume)
+ return;
+
+ driver->err_handler->resume(dev);
+}
+
+static void eeh_report_failure(struct pci_dev *dev, void *userdata)
+{
+ struct pci_driver *driver = dev->driver;
+
+ dev->error_state = pci_channel_io_perm_failure;
+
+ if (!driver)
+ return;
+
+ if (irq_in_use (dev->irq)) {
+ struct device_node *dn = pci_device_to_OF_node(dev);
+ PCI_DN(dn)->eeh_mode |= EEH_MODE_IRQ_DISABLED;
+ disable_irq_nosync(dev->irq);
+ }
+ if (!driver->err_handler)
+ return;
+ if (!driver->err_handler->error_detected)
+ return;
+ driver->err_handler->error_detected(dev, pci_channel_io_perm_failure);
+}
+
+/* ------------------------------------------------------- */
+/**
+ * handle_eeh_events -- reset a PCI device after hard lockup.
+ *
+ * pSeries systems will isolate a PCI slot if the PCI-Host
+ * bridge detects address or data parity errors, DMA's
+ * occuring to wild addresses (which usually happen due to
+ * bugs in device drivers or in PCI adapter firmware).
+ * Slot isolations also occur if #SERR, #PERR or other misc
+ * PCI-related errors are detected.
+ *
+ * Recovery process consists of unplugging the device driver
+ * (which generated hotplug events to userspace), then issuing
+ * a PCI #RST to the device, then reconfiguring the PCI config
+ * space for all bridges & devices under this slot, and then
+ * finally restarting the device drivers (which cause a second
+ * set of hotplug events to go out to userspace).
+ */
+
+/**
+ * eeh_reset_device() -- perform actual reset of a pci slot
+ * Args: bus: pointer to the pci bus structure corresponding
+ * to the isolated slot. A non-null value will
+ * cause all devices under the bus to be removed
+ * and then re-added.
+ * pe_dn: pointer to a "Partionable Endpoint" device node.
+ * This is the top-level structure on which pci
+ * bus resets can be performed.
+ */
+
+static int eeh_reset_device (struct pci_dn *pe_dn, struct pci_bus *bus)
+{
+ int rc;
+ if (bus)
+ pcibios_remove_pci_devices(bus);
+
+ /* Reset the pci controller. (Asserts RST#; resets config space).
+ * Reconfigure bridges and devices. Don't try to bring the system
+ * up if the reset failed for some reason. */
+ rc = rtas_set_slot_reset(pe_dn);
+ if (rc)
+ return rc;
+
+ /* New-style config addrs might be shared across multiple devices,
+ * Walk over all functions on this device */
+ if (pe_dn->eeh_pe_config_addr) {
+ struct device_node *pe = pe_dn->node;
+ pe = pe->parent->child;
+ while (pe) {
+ struct pci_dn *ppe = PCI_DN(pe);
+ if (pe_dn->eeh_pe_config_addr == ppe->eeh_pe_config_addr) {
+ rtas_configure_bridge(ppe);
+ eeh_restore_bars(ppe);
+ }
+ pe = pe->sibling;
+ }
+ } else {
+ rtas_configure_bridge(pe_dn);
+ eeh_restore_bars(pe_dn);
+ }
+
+ /* Give the system 5 seconds to finish running the user-space
+ * hotplug shutdown scripts, e.g. ifdown for ethernet. Yes,
+ * this is a hack, but if we don't do this, and try to bring
+ * the device up before the scripts have taken it down,
+ * potentially weird things happen.
+ */
+ if (bus) {
+ ssleep (5);
+ pcibios_add_pci_devices(bus);
+ }
+
+ return 0;
+}
+
+/* The longest amount of time to wait for a pci device
+ * to come back on line, in seconds.
+ */
+#define MAX_WAIT_FOR_RECOVERY 15
+
+void handle_eeh_events (struct eeh_event *event)
+{
+ struct device_node *frozen_dn;
+ struct pci_dn *frozen_pdn;
+ struct pci_bus *frozen_bus;
+ int rc = 0;
+ enum pci_ers_result result = PCI_ERS_RESULT_NONE;
+
+ frozen_dn = find_device_pe(event->dn);
+ frozen_bus = pcibios_find_pci_bus(frozen_dn);
+
+ if (!frozen_dn) {
+ printk(KERN_ERR "EEH: Error: Cannot find partition endpoint for %s\n",
+ pci_name(event->dev));
+ return;
+ }
+
+ /* There are two different styles for coming up with the PE.
+ * In the old style, it was the highest EEH-capable device
+ * which was always an EADS pci bridge. In the new style,
+ * there might not be any EADS bridges, and even when there are,
+ * the firmware marks them as "EEH incapable". So another
+ * two-step is needed to find the pci bus.. */
+ if (!frozen_bus)
+ frozen_bus = pcibios_find_pci_bus (frozen_dn->parent);
+
+ if (!frozen_bus) {
+ printk(KERN_ERR "EEH: Cannot find PCI bus for %s\n",
+ frozen_dn->full_name);
+ return;
+ }
+
+#if 0
+ /* We may get "permanent failure" messages on empty slots.
+ * These are false alarms. Empty slots have no child dn. */
+ if ((event->state == pci_channel_io_perm_failure) && (frozen_device == NULL))
+ return;
+#endif
+
+ frozen_pdn = PCI_DN(frozen_dn);
+ frozen_pdn->eeh_freeze_count++;
+
+ if (frozen_pdn->eeh_freeze_count > EEH_MAX_ALLOWED_FREEZES)
+ goto hard_fail;
+
+ /* If the reset state is a '5' and the time to reset is 0 (infinity)
+ * or is more then 15 seconds, then mark this as a permanent failure.
+ */
+ if ((event->state == pci_channel_io_perm_failure) &&
+ ((event->time_unavail <= 0) ||
+ (event->time_unavail > MAX_WAIT_FOR_RECOVERY*1000)))
+ goto hard_fail;
+
+ eeh_slot_error_detail(frozen_pdn, 1 /* Temporary Error */);
+ printk(KERN_WARNING
+ "EEH: This PCI device has failed %d times since last reboot: %s - %s\n",
+ frozen_pdn->eeh_freeze_count,
+ pci_name (frozen_pdn->pcidev),
+ pcid_name(frozen_pdn->pcidev));
+
+ /* Walk the various device drivers attached to this slot through
+ * a reset sequence, giving each an opportunity to do what it needs
+ * to accomplish the reset. Each child gets a report of the
+ * status ... if any child can't handle the reset, then the entire
+ * slot is dlpar removed and added.
+ */
+ pci_walk_bus(frozen_bus, eeh_report_error, &result);
+
+ /* If all device drivers were EEH-unaware, then shut
+ * down all of the device drivers, and hope they
+ * go down willingly, without panicing the system.
+ */
+ if (result == PCI_ERS_RESULT_NONE) {
+ rc = eeh_reset_device(frozen_pdn, frozen_bus);
+ if (rc)
+ goto hard_fail;
+ }
+
+ /* If any device called out for a reset, then reset the slot */
+ if (result == PCI_ERS_RESULT_NEED_RESET) {
+ rc = eeh_reset_device(frozen_pdn, NULL);
+ if (rc)
+ goto hard_fail;
+ pci_walk_bus(frozen_bus, eeh_report_reset, 0);
+ }
+
+ /* If all devices reported they can proceed, the re-enable PIO */
+ if (result == PCI_ERS_RESULT_CAN_RECOVER) {
+ /* XXX Not supported; we brute-force reset the device */
+ rc = eeh_reset_device(frozen_pdn, NULL);
+ if (rc)
+ goto hard_fail;
+ pci_walk_bus(frozen_bus, eeh_report_reset, 0);
+ }
+
+ /* Tell all device drivers that they can resume operations */
+ pci_walk_bus(frozen_bus, eeh_report_resume, 0);
+
+ return;
+
+hard_fail:
+ /*
+ * About 90% of all real-life EEH failures in the field
+ * are due to poorly seated PCI cards. Only 10% or so are
+ * due to actual, failed cards.
+ */
+ printk(KERN_ERR
+ "EEH: PCI device %s - %s has failed %d times \n"
+ "and has been permanently disabled. Please try reseating\n"
+ "this device or replacing it.\n",
+ pci_name (frozen_pdn->pcidev),
+ pcid_name(frozen_pdn->pcidev),
+ frozen_pdn->eeh_freeze_count);
+
+ eeh_slot_error_detail(frozen_pdn, 2 /* Permanent Error */);
+
+ /* Notify all devices that they're about to go down. */
+ pci_walk_bus(frozen_bus, eeh_report_failure, 0);
+
+ /* Shut down the device drivers for good. */
+ pcibios_remove_pci_devices(frozen_bus);
+}
+
+/* ---------- end of file ---------- */
diff --git a/arch/powerpc/platforms/pseries/eeh_event.c b/arch/powerpc/platforms/pseries/eeh_event.c
index 92497333c2b..9a9961f2748 100644
--- a/arch/powerpc/platforms/pseries/eeh_event.c
+++ b/arch/powerpc/platforms/pseries/eeh_event.c
@@ -21,6 +21,7 @@
#include <linux/list.h>
#include <linux/pci.h>
#include <asm/eeh_event.h>
+#include <asm/ppc-pci.h>
/** Overview:
* EEH error states may be detected within exception handlers;
@@ -37,31 +38,6 @@ static void eeh_thread_launcher(void *);
DECLARE_WORK(eeh_event_wq, eeh_thread_launcher, NULL);
/**
- * eeh_panic - call panic() for an eeh event that cannot be handled.
- * The philosophy of this routine is that it is better to panic and
- * halt the OS than it is to risk possible data corruption by
- * oblivious device drivers that don't know better.
- *
- * @dev pci device that had an eeh event
- * @reset_state current reset state of the device slot
- */
-static void eeh_panic(struct pci_dev *dev, int reset_state)
-{
- /*
- * Since the panic_on_oops sysctl is used to halt the system
- * in light of potential corruption, we can use it here.
- */
- if (panic_on_oops) {
- panic("EEH: MMIO failure (%d) on device:%s\n", reset_state,
- pci_name(dev));
- }
- else {
- printk(KERN_INFO "EEH: Ignored MMIO failure (%d) on device:%s\n",
- reset_state, pci_name(dev));
- }
-}
-
-/**
* eeh_event_handler - dispatch EEH events. The detection of a frozen
* slot can occur inside an interrupt, where it can be hard to do
* anything about it. The goal of this routine is to pull these
@@ -82,10 +58,16 @@ static int eeh_event_handler(void * dummy)
spin_lock_irqsave(&eeh_eventlist_lock, flags);
event = NULL;
+
+ /* Unqueue the event, get ready to process. */
if (!list_empty(&eeh_eventlist)) {
event = list_entry(eeh_eventlist.next, struct eeh_event, list);
list_del(&event->list);
}
+
+ if (event)
+ eeh_mark_slot(event->dn, EEH_MODE_RECOVERING);
+
spin_unlock_irqrestore(&eeh_eventlist_lock, flags);
if (event == NULL)
break;
@@ -93,8 +75,11 @@ static int eeh_event_handler(void * dummy)
printk(KERN_INFO "EEH: Detected PCI bus error on device %s\n",
pci_name(event->dev));
- eeh_panic (event->dev, event->state);
+ handle_eeh_events(event);
+
+ eeh_clear_slot(event->dn, EEH_MODE_RECOVERING);
+ pci_dev_put(event->dev);
kfree(event);
}
@@ -122,7 +107,7 @@ static void eeh_thread_launcher(void *dummy)
*/
int eeh_send_failure_event (struct device_node *dn,
struct pci_dev *dev,
- int state,
+ enum pci_channel_state state,
int time_unavail)
{
unsigned long flags;
diff --git a/arch/powerpc/platforms/pseries/hvconsole.c b/arch/powerpc/platforms/pseries/hvconsole.c
new file mode 100644
index 00000000000..138e128a388
--- /dev/null
+++ b/arch/powerpc/platforms/pseries/hvconsole.c
@@ -0,0 +1,74 @@
+/*
+ * hvconsole.c
+ * Copyright (C) 2004 Hollis Blanchard, IBM Corporation
+ * Copyright (C) 2004 IBM Corporation
+ *
+ * Additional Author(s):
+ * Ryan S. Arnold <rsa@us.ibm.com>
+ *
+ * LPAR console support.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/hvcall.h>
+#include <asm/hvconsole.h>
+
+/**
+ * hvc_get_chars - retrieve characters from firmware for denoted vterm adatper
+ * @vtermno: The vtermno or unit_address of the adapter from which to fetch the
+ * data.
+ * @buf: The character buffer into which to put the character data fetched from
+ * firmware.
+ * @count: not used?
+ */
+int hvc_get_chars(uint32_t vtermno, char *buf, int count)
+{
+ unsigned long got;
+
+ if (plpar_hcall(H_GET_TERM_CHAR, vtermno, 0, 0, 0, &got,
+ (unsigned long *)buf, (unsigned long *)buf+1) == H_Success)
+ return got;
+ return 0;
+}
+
+EXPORT_SYMBOL(hvc_get_chars);
+
+
+/**
+ * hvc_put_chars: send characters to firmware for denoted vterm adapter
+ * @vtermno: The vtermno or unit_address of the adapter from which the data
+ * originated.
+ * @buf: The character buffer that contains the character data to send to
+ * firmware.
+ * @count: Send this number of characters.
+ */
+int hvc_put_chars(uint32_t vtermno, const char *buf, int count)
+{
+ unsigned long *lbuf = (unsigned long *) buf;
+ long ret;
+
+ ret = plpar_hcall_norets(H_PUT_TERM_CHAR, vtermno, count, lbuf[0],
+ lbuf[1]);
+ if (ret == H_Success)
+ return count;
+ if (ret == H_Busy)
+ return 0;
+ return -EIO;
+}
+
+EXPORT_SYMBOL(hvc_put_chars);
diff --git a/arch/powerpc/platforms/pseries/hvcserver.c b/arch/powerpc/platforms/pseries/hvcserver.c
new file mode 100644
index 00000000000..22bfb5c89db
--- /dev/null
+++ b/arch/powerpc/platforms/pseries/hvcserver.c
@@ -0,0 +1,251 @@
+/*
+ * hvcserver.c
+ * Copyright (C) 2004 Ryan S Arnold, IBM Corporation
+ *
+ * PPC64 virtual I/O console server support.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <asm/hvcall.h>
+#include <asm/hvcserver.h>
+#include <asm/io.h>
+
+#define HVCS_ARCH_VERSION "1.0.0"
+
+MODULE_AUTHOR("Ryan S. Arnold <rsa@us.ibm.com>");
+MODULE_DESCRIPTION("IBM hvcs ppc64 API");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(HVCS_ARCH_VERSION);
+
+/*
+ * Convert arch specific return codes into relevant errnos. The hvcs
+ * functions aren't performance sensitive, so this conversion isn't an
+ * issue.
+ */
+static int hvcs_convert(long to_convert)
+{
+ switch (to_convert) {
+ case H_Success:
+ return 0;
+ case H_Parameter:
+ return -EINVAL;
+ case H_Hardware:
+ return -EIO;
+ case H_Busy:
+ case H_LongBusyOrder1msec:
+ case H_LongBusyOrder10msec:
+ case H_LongBusyOrder100msec:
+ case H_LongBusyOrder1sec:
+ case H_LongBusyOrder10sec:
+ case H_LongBusyOrder100sec:
+ return -EBUSY;
+ case H_Function: /* fall through */
+ default:
+ return -EPERM;
+ }
+}
+
+/**
+ * hvcs_free_partner_info - free pi allocated by hvcs_get_partner_info
+ * @head: list_head pointer for an allocated list of partner info structs to
+ * free.
+ *
+ * This function is used to free the partner info list that was returned by
+ * calling hvcs_get_partner_info().
+ */
+int hvcs_free_partner_info(struct list_head *head)
+{
+ struct hvcs_partner_info *pi;
+ struct list_head *element;
+
+ if (!head)
+ return -EINVAL;
+
+ while (!list_empty(head)) {
+ element = head->next;
+ pi = list_entry(element, struct hvcs_partner_info, node);
+ list_del(element);
+ kfree(pi);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(hvcs_free_partner_info);
+
+/* Helper function for hvcs_get_partner_info */
+static int hvcs_next_partner(uint32_t unit_address,
+ unsigned long last_p_partition_ID,
+ unsigned long last_p_unit_address, unsigned long *pi_buff)
+
+{
+ long retval;
+ retval = plpar_hcall_norets(H_VTERM_PARTNER_INFO, unit_address,
+ last_p_partition_ID,
+ last_p_unit_address, virt_to_phys(pi_buff));
+ return hvcs_convert(retval);
+}
+
+/**
+ * hvcs_get_partner_info - Get all of the partner info for a vty-server adapter
+ * @unit_address: The unit_address of the vty-server adapter for which this
+ * function is fetching partner info.
+ * @head: An initialized list_head pointer to an empty list to use to return the
+ * list of partner info fetched from the hypervisor to the caller.
+ * @pi_buff: A page sized buffer pre-allocated prior to calling this function
+ * that is to be used to be used by firmware as an iterator to keep track
+ * of the partner info retrieval.
+ *
+ * This function returns non-zero on success, or if there is no partner info.
+ *
+ * The pi_buff is pre-allocated prior to calling this function because this
+ * function may be called with a spin_lock held and kmalloc of a page is not
+ * recommended as GFP_ATOMIC.
+ *
+ * The first long of this buffer is used to store a partner unit address. The
+ * second long is used to store a partner partition ID and starting at
+ * pi_buff[2] is the 79 character Converged Location Code (diff size than the
+ * unsigned longs, hence the casting mumbo jumbo you see later).
+ *
+ * Invocation of this function should always be followed by an invocation of
+ * hvcs_free_partner_info() using a pointer to the SAME list head instance
+ * that was passed as a parameter to this function.
+ */
+int hvcs_get_partner_info(uint32_t unit_address, struct list_head *head,
+ unsigned long *pi_buff)
+{
+ /*
+ * Dealt with as longs because of the hcall interface even though the
+ * values are uint32_t.
+ */
+ unsigned long last_p_partition_ID;
+ unsigned long last_p_unit_address;
+ struct hvcs_partner_info *next_partner_info = NULL;
+ int more = 1;
+ int retval;
+
+ memset(pi_buff, 0x00, PAGE_SIZE);
+ /* invalid parameters */
+ if (!head || !pi_buff)
+ return -EINVAL;
+
+ last_p_partition_ID = last_p_unit_address = ~0UL;
+ INIT_LIST_HEAD(head);
+
+ do {
+ retval = hvcs_next_partner(unit_address, last_p_partition_ID,
+ last_p_unit_address, pi_buff);
+ if (retval) {
+ /*
+ * Don't indicate that we've failed if we have
+ * any list elements.
+ */
+ if (!list_empty(head))
+ return 0;
+ return retval;
+ }
+
+ last_p_partition_ID = pi_buff[0];
+ last_p_unit_address = pi_buff[1];
+
+ /* This indicates that there are no further partners */
+ if (last_p_partition_ID == ~0UL
+ && last_p_unit_address == ~0UL)
+ break;
+
+ /* This is a very small struct and will be freed soon in
+ * hvcs_free_partner_info(). */
+ next_partner_info = kmalloc(sizeof(struct hvcs_partner_info),
+ GFP_ATOMIC);
+
+ if (!next_partner_info) {
+ printk(KERN_WARNING "HVCONSOLE: kmalloc() failed to"
+ " allocate partner info struct.\n");
+ hvcs_free_partner_info(head);
+ return -ENOMEM;
+ }
+
+ next_partner_info->unit_address
+ = (unsigned int)last_p_unit_address;
+ next_partner_info->partition_ID
+ = (unsigned int)last_p_partition_ID;
+
+ /* copy the Null-term char too */
+ strncpy(&next_partner_info->location_code[0],
+ (char *)&pi_buff[2],
+ strlen((char *)&pi_buff[2]) + 1);
+
+ list_add_tail(&(next_partner_info->node), head);
+ next_partner_info = NULL;
+
+ } while (more);
+
+ return 0;
+}
+EXPORT_SYMBOL(hvcs_get_partner_info);
+
+/**
+ * hvcs_register_connection - establish a connection between this vty-server and
+ * a vty.
+ * @unit_address: The unit address of the vty-server adapter that is to be
+ * establish a connection.
+ * @p_partition_ID: The partition ID of the vty adapter that is to be connected.
+ * @p_unit_address: The unit address of the vty adapter to which the vty-server
+ * is to be connected.
+ *
+ * If this function is called once and -EINVAL is returned it may
+ * indicate that the partner info needs to be refreshed for the
+ * target unit address at which point the caller must invoke
+ * hvcs_get_partner_info() and then call this function again. If,
+ * for a second time, -EINVAL is returned then it indicates that
+ * there is probably already a partner connection registered to a
+ * different vty-server adapter. It is also possible that a second
+ * -EINVAL may indicate that one of the parms is not valid, for
+ * instance if the link was removed between the vty-server adapter
+ * and the vty adapter that you are trying to open. Don't shoot the
+ * messenger. Firmware implemented it this way.
+ */
+int hvcs_register_connection( uint32_t unit_address,
+ uint32_t p_partition_ID, uint32_t p_unit_address)
+{
+ long retval;
+ retval = plpar_hcall_norets(H_REGISTER_VTERM, unit_address,
+ p_partition_ID, p_unit_address);
+ return hvcs_convert(retval);
+}
+EXPORT_SYMBOL(hvcs_register_connection);
+
+/**
+ * hvcs_free_connection - free the connection between a vty-server and vty
+ * @unit_address: The unit address of the vty-server that is to have its
+ * connection severed.
+ *
+ * This function is used to free the partner connection between a vty-server
+ * adapter and a vty adapter.
+ *
+ * If -EBUSY is returned continue to call this function until 0 is returned.
+ */
+int hvcs_free_connection(uint32_t unit_address)
+{
+ long retval;
+ retval = plpar_hcall_norets(H_FREE_VTERM, unit_address);
+ return hvcs_convert(retval);
+}
+EXPORT_SYMBOL(hvcs_free_connection);
diff --git a/arch/powerpc/platforms/pseries/iommu.c b/arch/powerpc/platforms/pseries/iommu.c
index 97ba5214417..48cfbfc43f9 100644
--- a/arch/powerpc/platforms/pseries/iommu.c
+++ b/arch/powerpc/platforms/pseries/iommu.c
@@ -5,7 +5,7 @@
*
* Rewrite, cleanup:
*
- * Copyright (C) 2004 Olof Johansson <olof@austin.ibm.com>, IBM Corporation
+ * Copyright (C) 2004 Olof Johansson <olof@lixom.net>, IBM Corporation
*
* Dynamic DMA mapping support, pSeries-specific parts, both SMP and LPAR.
*
@@ -51,8 +51,6 @@
#define DBG(fmt...)
-extern int is_python(struct device_node *);
-
static void tce_build_pSeries(struct iommu_table *tbl, long index,
long npages, unsigned long uaddr,
enum dma_data_direction direction)
@@ -109,6 +107,9 @@ static void tce_build_pSeriesLP(struct iommu_table *tbl, long tcenum,
u64 rc;
union tce_entry tce;
+ tcenum <<= TCE_PAGE_FACTOR;
+ npages <<= TCE_PAGE_FACTOR;
+
tce.te_word = 0;
tce.te_rpn = (virt_to_abs(uaddr)) >> TCE_SHIFT;
tce.te_rdwr = 1;
@@ -143,10 +144,7 @@ static void tce_buildmulti_pSeriesLP(struct iommu_table *tbl, long tcenum,
union tce_entry tce, *tcep;
long l, limit;
- tcenum <<= TCE_PAGE_FACTOR;
- npages <<= TCE_PAGE_FACTOR;
-
- if (npages == 1)
+ if (TCE_PAGE_FACTOR == 0 && npages == 1)
return tce_build_pSeriesLP(tbl, tcenum, npages, uaddr,
direction);
@@ -164,6 +162,9 @@ static void tce_buildmulti_pSeriesLP(struct iommu_table *tbl, long tcenum,
__get_cpu_var(tce_page) = tcep;
}
+ tcenum <<= TCE_PAGE_FACTOR;
+ npages <<= TCE_PAGE_FACTOR;
+
tce.te_word = 0;
tce.te_rpn = (virt_to_abs(uaddr)) >> TCE_SHIFT;
tce.te_rdwr = 1;
@@ -433,7 +434,7 @@ static void iommu_bus_setup_pSeriesLP(struct pci_bus *bus)
return;
}
- ppci = pdn->data;
+ ppci = PCI_DN(pdn);
if (!ppci->iommu_table) {
/* Bussubno hasn't been copied yet.
* Do it now because iommu_table_setparms_lpar needs it.
@@ -480,10 +481,10 @@ static void iommu_dev_setup_pSeries(struct pci_dev *dev)
* an already allocated iommu table is found and use that.
*/
- while (dn && dn->data && PCI_DN(dn)->iommu_table == NULL)
+ while (dn && PCI_DN(dn) && PCI_DN(dn)->iommu_table == NULL)
dn = dn->parent;
- if (dn && dn->data) {
+ if (dn && PCI_DN(dn)) {
PCI_DN(mydn)->iommu_table = PCI_DN(dn)->iommu_table;
} else {
DBG("iommu_dev_setup_pSeries, dev %p (%s) has no iommu table\n", dev, pci_name(dev));
@@ -494,7 +495,7 @@ static int iommu_reconfig_notifier(struct notifier_block *nb, unsigned long acti
{
int err = NOTIFY_OK;
struct device_node *np = node;
- struct pci_dn *pci = np->data;
+ struct pci_dn *pci = PCI_DN(np);
switch (action) {
case PSERIES_RECONFIG_REMOVE:
@@ -530,7 +531,7 @@ static void iommu_dev_setup_pSeriesLP(struct pci_dev *dev)
*/
dn = pci_device_to_OF_node(dev);
- for (pdn = dn; pdn && pdn->data && !PCI_DN(pdn)->iommu_table;
+ for (pdn = dn; pdn && PCI_DN(pdn) && !PCI_DN(pdn)->iommu_table;
pdn = pdn->parent) {
dma_window = (unsigned int *)
get_property(pdn, "ibm,dma-window", NULL);
@@ -549,7 +550,7 @@ static void iommu_dev_setup_pSeriesLP(struct pci_dev *dev)
DBG("Found DMA window, allocating table\n");
}
- pci = pdn->data;
+ pci = PCI_DN(pdn);
if (!pci->iommu_table) {
/* iommu_table_setparms_lpar needs bussubno. */
pci->bussubno = pci->phb->bus->number;
diff --git a/arch/powerpc/platforms/pseries/lpar.c b/arch/powerpc/platforms/pseries/lpar.c
index a50e5f3f396..8952528d31a 100644
--- a/arch/powerpc/platforms/pseries/lpar.c
+++ b/arch/powerpc/platforms/pseries/lpar.c
@@ -24,6 +24,7 @@
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/dma-mapping.h>
+#include <linux/console.h>
#include <asm/processor.h>
#include <asm/mmu.h>
#include <asm/page.h>
@@ -60,7 +61,7 @@ extern void pSeries_find_serial_port(void);
int vtermno; /* virtual terminal# for udbg */
#define __ALIGNED__ __attribute__((__aligned__(sizeof(long))))
-static void udbg_hvsi_putc(unsigned char c)
+static void udbg_hvsi_putc(char c)
{
/* packet's seqno isn't used anyways */
uint8_t packet[] __ALIGNED__ = { 0xff, 5, 0, 0, c };
@@ -111,7 +112,7 @@ static int udbg_hvsi_getc_poll(void)
return ch;
}
-static unsigned char udbg_hvsi_getc(void)
+static int udbg_hvsi_getc(void)
{
int ch;
for (;;) {
@@ -127,7 +128,7 @@ static unsigned char udbg_hvsi_getc(void)
}
}
-static void udbg_putcLP(unsigned char c)
+static void udbg_putcLP(char c)
{
char buf[16];
unsigned long rc;
@@ -172,7 +173,7 @@ static int udbg_getc_pollLP(void)
return ch;
}
-static unsigned char udbg_getcLP(void)
+static int udbg_getcLP(void)
{
int ch;
for (;;) {
@@ -191,7 +192,7 @@ static unsigned char udbg_getcLP(void)
/* call this from early_init() for a working debug console on
* vterm capable LPAR machines
*/
-void udbg_init_debug_lpar(void)
+void __init udbg_init_debug_lpar(void)
{
vtermno = 0;
udbg_putc = udbg_putcLP;
@@ -200,73 +201,64 @@ void udbg_init_debug_lpar(void)
}
/* returns 0 if couldn't find or use /chosen/stdout as console */
-int find_udbg_vterm(void)
+void __init find_udbg_vterm(void)
{
struct device_node *stdout_node;
u32 *termno;
char *name;
- int found = 0;
+ int add_console;
/* find the boot console from /chosen/stdout */
if (!of_chosen)
- return 0;
+ return;
name = (char *)get_property(of_chosen, "linux,stdout-path", NULL);
if (name == NULL)
- return 0;
+ return;
stdout_node = of_find_node_by_path(name);
if (!stdout_node)
- return 0;
-
- /* now we have the stdout node; figure out what type of device it is. */
+ return;
name = (char *)get_property(stdout_node, "name", NULL);
if (!name) {
printk(KERN_WARNING "stdout node missing 'name' property!\n");
goto out;
}
+ /* The user has requested a console so this is already set up. */
+ add_console = !strstr(cmd_line, "console=");
- if (strncmp(name, "vty", 3) == 0) {
- if (device_is_compatible(stdout_node, "hvterm1")) {
- termno = (u32 *)get_property(stdout_node, "reg", NULL);
- if (termno) {
- vtermno = termno[0];
- udbg_putc = udbg_putcLP;
- udbg_getc = udbg_getcLP;
- udbg_getc_poll = udbg_getc_pollLP;
- found = 1;
- }
- } else if (device_is_compatible(stdout_node, "hvterm-protocol")) {
- termno = (u32 *)get_property(stdout_node, "reg", NULL);
- if (termno) {
- vtermno = termno[0];
- udbg_putc = udbg_hvsi_putc;
- udbg_getc = udbg_hvsi_getc;
- udbg_getc_poll = udbg_hvsi_getc_poll;
- found = 1;
- }
- }
- } else if (strncmp(name, "serial", 6)) {
- /* XXX fix ISA serial console */
- printk(KERN_WARNING "serial stdout on LPAR ('%s')! "
- "can't print udbg messages\n",
- stdout_node->full_name);
- } else {
- printk(KERN_WARNING "don't know how to print to stdout '%s'\n",
- stdout_node->full_name);
+ /* Check if it's a virtual terminal */
+ if (strncmp(name, "vty", 3) != 0)
+ goto out;
+ termno = (u32 *)get_property(stdout_node, "reg", NULL);
+ if (termno == NULL)
+ goto out;
+ vtermno = termno[0];
+
+ if (device_is_compatible(stdout_node, "hvterm1")) {
+ udbg_putc = udbg_putcLP;
+ udbg_getc = udbg_getcLP;
+ udbg_getc_poll = udbg_getc_pollLP;
+ if (add_console)
+ add_preferred_console("hvc", termno[0] & 0xff, NULL);
+ } else if (device_is_compatible(stdout_node, "hvterm-protocol")) {
+ vtermno = termno[0];
+ udbg_putc = udbg_hvsi_putc;
+ udbg_getc = udbg_hvsi_getc;
+ udbg_getc_poll = udbg_hvsi_getc_poll;
+ if (add_console)
+ add_preferred_console("hvsi", termno[0] & 0xff, NULL);
}
-
out:
of_node_put(stdout_node);
- return found;
}
void vpa_init(int cpu)
{
int hwcpu = get_hard_smp_processor_id(cpu);
- unsigned long vpa = __pa(&paca[cpu].lppaca);
+ unsigned long vpa = __pa(&lppaca[cpu]);
long ret;
if (cpu_has_feature(CPU_FTR_ALTIVEC))
- paca[cpu].lppaca.vmxregs_in_use = 1;
+ lppaca[cpu].vmxregs_in_use = 1;
ret = register_vpa(hwcpu, vpa);
@@ -298,18 +290,6 @@ long pSeries_lpar_hpte_insert(unsigned long hpte_group,
if (!(vflags & HPTE_V_BOLTED))
DBG_LOW(" hpte_v=%016lx, hpte_r=%016lx\n", hpte_v, hpte_r);
-#if 1
- {
- int i;
- for (i=0;i<8;i++) {
- unsigned long w0, w1;
- plpar_pte_read(0, hpte_group, &w0, &w1);
- BUG_ON (HPTE_V_COMPARE(hpte_v, w0)
- && (w0 & HPTE_V_VALID));
- }
- }
-#endif
-
/* Now fill in the actual HPTE */
/* Set CEC cookie to 0 */
/* Zero page = 0 */
diff --git a/arch/powerpc/platforms/pseries/pci_dlpar.c b/arch/powerpc/platforms/pseries/pci_dlpar.c
new file mode 100644
index 00000000000..21934784f93
--- /dev/null
+++ b/arch/powerpc/platforms/pseries/pci_dlpar.c
@@ -0,0 +1,174 @@
+/*
+ * PCI Dynamic LPAR, PCI Hot Plug and PCI EEH recovery code
+ * for RPA-compliant PPC64 platform.
+ * Copyright (C) 2003 Linda Xie <lxie@us.ibm.com>
+ * Copyright (C) 2005 International Business Machines
+ *
+ * Updates, 2005, John Rose <johnrose@austin.ibm.com>
+ * Updates, 2005, Linas Vepstas <linas@austin.ibm.com>
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/pci.h>
+#include <asm/pci-bridge.h>
+
+static struct pci_bus *
+find_bus_among_children(struct pci_bus *bus,
+ struct device_node *dn)
+{
+ struct pci_bus *child = NULL;
+ struct list_head *tmp;
+ struct device_node *busdn;
+
+ busdn = pci_bus_to_OF_node(bus);
+ if (busdn == dn)
+ return bus;
+
+ list_for_each(tmp, &bus->children) {
+ child = find_bus_among_children(pci_bus_b(tmp), dn);
+ if (child)
+ break;
+ };
+ return child;
+}
+
+struct pci_bus *
+pcibios_find_pci_bus(struct device_node *dn)
+{
+ struct pci_dn *pdn = dn->data;
+
+ if (!pdn || !pdn->phb || !pdn->phb->bus)
+ return NULL;
+
+ return find_bus_among_children(pdn->phb->bus, dn);
+}
+
+/**
+ * pcibios_remove_pci_devices - remove all devices under this bus
+ *
+ * Remove all of the PCI devices under this bus both from the
+ * linux pci device tree, and from the powerpc EEH address cache.
+ */
+void
+pcibios_remove_pci_devices(struct pci_bus *bus)
+{
+ struct pci_dev *dev, *tmp;
+
+ list_for_each_entry_safe(dev, tmp, &bus->devices, bus_list) {
+ eeh_remove_bus_device(dev);
+ pci_remove_bus_device(dev);
+ }
+}
+
+/* Must be called before pci_bus_add_devices */
+void
+pcibios_fixup_new_pci_devices(struct pci_bus *bus, int fix_bus)
+{
+ struct pci_dev *dev;
+
+ list_for_each_entry(dev, &bus->devices, bus_list) {
+ /*
+ * Skip already-present devices (which are on the
+ * global device list.)
+ */
+ if (list_empty(&dev->global_list)) {
+ int i;
+
+ /* Need to setup IOMMU tables */
+ ppc_md.iommu_dev_setup(dev);
+
+ if(fix_bus)
+ pcibios_fixup_device_resources(dev, bus);
+ pci_read_irq_line(dev);
+ for (i = 0; i < PCI_NUM_RESOURCES; i++) {
+ struct resource *r = &dev->resource[i];
+
+ if (r->parent || !r->start || !r->flags)
+ continue;
+ pci_claim_resource(dev, i);
+ }
+ }
+ }
+}
+
+static int
+pcibios_pci_config_bridge(struct pci_dev *dev)
+{
+ u8 sec_busno;
+ struct pci_bus *child_bus;
+ struct pci_dev *child_dev;
+
+ /* Get busno of downstream bus */
+ pci_read_config_byte(dev, PCI_SECONDARY_BUS, &sec_busno);
+
+ /* Add to children of PCI bridge dev->bus */
+ child_bus = pci_add_new_bus(dev->bus, dev, sec_busno);
+ if (!child_bus) {
+ printk (KERN_ERR "%s: could not add second bus\n", __FUNCTION__);
+ return -EIO;
+ }
+ sprintf(child_bus->name, "PCI Bus #%02x", child_bus->number);
+
+ pci_scan_child_bus(child_bus);
+
+ list_for_each_entry(child_dev, &child_bus->devices, bus_list) {
+ eeh_add_device_late(child_dev);
+ }
+
+ /* Fixup new pci devices without touching bus struct */
+ pcibios_fixup_new_pci_devices(child_bus, 0);
+
+ /* Make the discovered devices available */
+ pci_bus_add_devices(child_bus);
+ return 0;
+}
+
+/**
+ * pcibios_add_pci_devices - adds new pci devices to bus
+ *
+ * This routine will find and fixup new pci devices under
+ * the indicated bus. This routine presumes that there
+ * might already be some devices under this bridge, so
+ * it carefully tries to add only new devices. (And that
+ * is how this routine differs from other, similar pcibios
+ * routines.)
+ */
+void
+pcibios_add_pci_devices(struct pci_bus * bus)
+{
+ int slotno, num;
+ struct pci_dev *dev;
+ struct device_node *dn = pci_bus_to_OF_node(bus);
+
+ eeh_add_device_tree_early(dn);
+
+ /* pci_scan_slot should find all children */
+ slotno = PCI_SLOT(PCI_DN(dn->child)->devfn);
+ num = pci_scan_slot(bus, PCI_DEVFN(slotno, 0));
+ if (num) {
+ pcibios_fixup_new_pci_devices(bus, 1);
+ pci_bus_add_devices(bus);
+ }
+
+ list_for_each_entry(dev, &bus->devices, bus_list) {
+ eeh_add_device_late (dev);
+ if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE)
+ pcibios_pci_config_bridge(dev);
+ }
+}
diff --git a/arch/powerpc/platforms/pseries/ras.c b/arch/powerpc/platforms/pseries/ras.c
index fbd214d68b0..b046bcf7443 100644
--- a/arch/powerpc/platforms/pseries/ras.c
+++ b/arch/powerpc/platforms/pseries/ras.c
@@ -49,14 +49,14 @@
#include <asm/machdep.h>
#include <asm/rtas.h>
#include <asm/udbg.h>
+#include <asm/firmware.h>
+
+#include "ras.h"
static unsigned char ras_log_buf[RTAS_ERROR_LOG_MAX];
static DEFINE_SPINLOCK(ras_log_buf_lock);
-char mce_data_buf[RTAS_ERROR_LOG_MAX]
-;
-/* This is true if we are using the firmware NMI handler (typically LPAR) */
-extern int fwnmi_active;
+char mce_data_buf[RTAS_ERROR_LOG_MAX];
static int ras_get_sensor_state_token;
static int ras_check_exception_token;
@@ -280,7 +280,7 @@ static void fwnmi_release_errinfo(void)
printk("FWNMI: nmi-interlock failed: %d\n", ret);
}
-void pSeries_system_reset_exception(struct pt_regs *regs)
+int pSeries_system_reset_exception(struct pt_regs *regs)
{
if (fwnmi_active) {
struct rtas_error_log *errhdr = fwnmi_get_errinfo(regs);
@@ -289,6 +289,7 @@ void pSeries_system_reset_exception(struct pt_regs *regs)
}
fwnmi_release_errinfo();
}
+ return 0; /* need to perform reset */
}
/*
diff --git a/arch/powerpc/platforms/pseries/ras.h b/arch/powerpc/platforms/pseries/ras.h
new file mode 100644
index 00000000000..0e66b0da55e
--- /dev/null
+++ b/arch/powerpc/platforms/pseries/ras.h
@@ -0,0 +1,9 @@
+#ifndef _PSERIES_RAS_H
+#define _PSERIES_RAS_H
+
+struct pt_regs;
+
+extern int pSeries_system_reset_exception(struct pt_regs *regs);
+extern int pSeries_machine_check_exception(struct pt_regs *regs);
+
+#endif /* _PSERIES_RAS_H */
diff --git a/arch/powerpc/platforms/pseries/reconfig.c b/arch/powerpc/platforms/pseries/reconfig.c
index d8864164dbe..86cfa6ecdcf 100644
--- a/arch/powerpc/platforms/pseries/reconfig.c
+++ b/arch/powerpc/platforms/pseries/reconfig.c
@@ -350,6 +350,100 @@ static int do_remove_node(char *buf)
return rv;
}
+static char *parse_node(char *buf, size_t bufsize, struct device_node **npp)
+{
+ char *handle_str;
+ phandle handle;
+ *npp = NULL;
+
+ handle_str = buf;
+
+ buf = strchr(buf, ' ');
+ if (!buf)
+ return NULL;
+ *buf = '\0';
+ buf++;
+
+ handle = simple_strtoul(handle_str, NULL, 10);
+
+ *npp = of_find_node_by_phandle(handle);
+ return buf;
+}
+
+static int do_add_property(char *buf, size_t bufsize)
+{
+ struct property *prop = NULL;
+ struct device_node *np;
+ unsigned char *value;
+ char *name, *end;
+ int length;
+ end = buf + bufsize;
+ buf = parse_node(buf, bufsize, &np);
+
+ if (!np)
+ return -ENODEV;
+
+ if (parse_next_property(buf, end, &name, &length, &value) == NULL)
+ return -EINVAL;
+
+ prop = new_property(name, length, value, NULL);
+ if (!prop)
+ return -ENOMEM;
+
+ prom_add_property(np, prop);
+
+ return 0;
+}
+
+static int do_remove_property(char *buf, size_t bufsize)
+{
+ struct device_node *np;
+ char *tmp;
+ struct property *prop;
+ buf = parse_node(buf, bufsize, &np);
+
+ if (!np)
+ return -ENODEV;
+
+ tmp = strchr(buf,' ');
+ if (tmp)
+ *tmp = '\0';
+
+ if (strlen(buf) == 0)
+ return -EINVAL;
+
+ prop = of_find_property(np, buf, NULL);
+
+ return prom_remove_property(np, prop);
+}
+
+static int do_update_property(char *buf, size_t bufsize)
+{
+ struct device_node *np;
+ unsigned char *value;
+ char *name, *end;
+ int length;
+ struct property *newprop, *oldprop;
+ buf = parse_node(buf, bufsize, &np);
+ end = buf + bufsize;
+
+ if (!np)
+ return -ENODEV;
+
+ if (parse_next_property(buf, end, &name, &length, &value) == NULL)
+ return -EINVAL;
+
+ newprop = new_property(name, length, value, NULL);
+ if (!newprop)
+ return -ENOMEM;
+
+ oldprop = of_find_property(np, name,NULL);
+ if (!oldprop)
+ return -ENODEV;
+
+ return prom_update_property(np, newprop, oldprop);
+}
+
/**
* ofdt_write - perform operations on the Open Firmware device tree
*
@@ -392,6 +486,12 @@ static ssize_t ofdt_write(struct file *file, const char __user *buf, size_t coun
rv = do_add_node(tmp, count - (tmp - kbuf));
else if (!strcmp(kbuf, "remove_node"))
rv = do_remove_node(tmp);
+ else if (!strcmp(kbuf, "add_property"))
+ rv = do_add_property(tmp, count - (tmp - kbuf));
+ else if (!strcmp(kbuf, "remove_property"))
+ rv = do_remove_property(tmp, count - (tmp - kbuf));
+ else if (!strcmp(kbuf, "update_property"))
+ rv = do_update_property(tmp, count - (tmp - kbuf));
else
rv = -EINVAL;
out:
diff --git a/arch/powerpc/platforms/pseries/scanlog.c b/arch/powerpc/platforms/pseries/scanlog.c
index 2edc947f7c4..50643496eb6 100644
--- a/arch/powerpc/platforms/pseries/scanlog.c
+++ b/arch/powerpc/platforms/pseries/scanlog.c
@@ -192,7 +192,7 @@ struct file_operations scanlog_fops = {
.release = scanlog_release,
};
-int __init scanlog_init(void)
+static int __init scanlog_init(void)
{
struct proc_dir_entry *ent;
@@ -222,7 +222,7 @@ int __init scanlog_init(void)
return 0;
}
-void __exit scanlog_cleanup(void)
+static void __exit scanlog_cleanup(void)
{
if (proc_ppc64_scan_log_dump) {
kfree(proc_ppc64_scan_log_dump->data);
diff --git a/arch/powerpc/platforms/pseries/setup.c b/arch/powerpc/platforms/pseries/setup.c
index 31990829310..da6cebaf72c 100644
--- a/arch/powerpc/platforms/pseries/setup.c
+++ b/arch/powerpc/platforms/pseries/setup.c
@@ -56,6 +56,7 @@
#include <asm/dma.h>
#include <asm/machdep.h>
#include <asm/irq.h>
+#include <asm/kexec.h>
#include <asm/time.h>
#include <asm/nvram.h>
#include "xics.h"
@@ -68,6 +69,7 @@
#include <asm/smp.h>
#include "plpar_wrappers.h"
+#include "ras.h"
#ifdef DEBUG
#define DBG(fmt...) udbg_printf(fmt)
@@ -76,22 +78,15 @@
#endif
extern void find_udbg_vterm(void);
-extern void system_reset_fwnmi(void); /* from head.S */
-extern void machine_check_fwnmi(void); /* from head.S */
-extern void generic_find_legacy_serial_ports(u64 *physport,
- unsigned int *default_speed);
int fwnmi_active; /* TRUE if an FWNMI handler is present */
-extern void pSeries_system_reset_exception(struct pt_regs *regs);
-extern int pSeries_machine_check_exception(struct pt_regs *regs);
-
static void pseries_shared_idle(void);
static void pseries_dedicated_idle(void);
struct mpic *pSeries_mpic;
-void pSeries_show_cpuinfo(struct seq_file *m)
+static void pSeries_show_cpuinfo(struct seq_file *m)
{
struct device_node *root;
const char *model = "";
@@ -105,18 +100,22 @@ void pSeries_show_cpuinfo(struct seq_file *m)
/* Initialize firmware assisted non-maskable interrupts if
* the firmware supports this feature.
- *
*/
static void __init fwnmi_init(void)
{
- int ret;
+ unsigned long system_reset_addr, machine_check_addr;
+
int ibm_nmi_register = rtas_token("ibm,nmi-register");
if (ibm_nmi_register == RTAS_UNKNOWN_SERVICE)
return;
- ret = rtas_call(ibm_nmi_register, 2, 1, NULL,
- __pa((unsigned long)system_reset_fwnmi),
- __pa((unsigned long)machine_check_fwnmi));
- if (ret == 0)
+
+ /* If the kernel's not linked at zero we point the firmware at low
+ * addresses anyway, and use a trampoline to get to the real code. */
+ system_reset_addr = __pa(system_reset_fwnmi) - PHYSICAL_START;
+ machine_check_addr = __pa(machine_check_fwnmi) - PHYSICAL_START;
+
+ if (0 == rtas_call(ibm_nmi_register, 2, 1, NULL, system_reset_addr,
+ machine_check_addr))
fwnmi_active = 1;
}
@@ -191,7 +190,7 @@ static void pseries_lpar_enable_pmcs(void)
/* instruct hypervisor to maintain PMCs */
if (firmware_has_feature(FW_FEATURE_SPLPAR))
- get_paca()->lppaca.pmcregs_in_use = 1;
+ get_lppaca()->pmcregs_in_use = 1;
}
static void __init pSeries_setup_arch(void)
@@ -200,14 +199,12 @@ static void __init pSeries_setup_arch(void)
if (ppc64_interrupt_controller == IC_OPEN_PIC) {
ppc_md.init_IRQ = pSeries_init_mpic;
ppc_md.get_irq = mpic_get_irq;
- ppc_md.cpu_irq_down = mpic_teardown_this_cpu;
/* Allocate the mpic now, so that find_and_init_phbs() can
* fill the ISUs */
pSeries_setup_mpic();
} else {
ppc_md.init_IRQ = xics_init_IRQ;
ppc_md.get_irq = xics_get_irq;
- ppc_md.cpu_irq_down = xics_teardown_cpu;
}
#ifdef CONFIG_SMP
@@ -237,7 +234,7 @@ static void __init pSeries_setup_arch(void)
/* Choose an idle loop */
if (firmware_has_feature(FW_FEATURE_SPLPAR)) {
vpa_init(boot_cpuid);
- if (get_paca()->lppaca.shared_proc) {
+ if (get_lppaca()->shared_proc) {
printk(KERN_INFO "Using shared processor idle loop\n");
ppc_md.idle_loop = pseries_shared_idle;
} else {
@@ -325,15 +322,18 @@ static void __init pSeries_discover_pic(void)
ppc64_interrupt_controller = IC_INVALID;
for (np = NULL; (np = of_find_node_by_name(np, "interrupt-controller"));) {
typep = (char *)get_property(np, "compatible", NULL);
- if (strstr(typep, "open-pic"))
+ if (strstr(typep, "open-pic")) {
ppc64_interrupt_controller = IC_OPEN_PIC;
- else if (strstr(typep, "ppc-xicp"))
+ break;
+ } else if (strstr(typep, "ppc-xicp")) {
ppc64_interrupt_controller = IC_PPC_XIC;
- else
- printk("pSeries_discover_pic: failed to recognize"
- " interrupt-controller\n");
- break;
+ break;
+ }
}
+ if (ppc64_interrupt_controller == IC_INVALID)
+ printk("pSeries_discover_pic: failed to recognize"
+ " interrupt-controller\n");
+
}
static void pSeries_mach_cpu_die(void)
@@ -367,10 +367,7 @@ static int pseries_set_xdabr(unsigned long dabr)
*/
static void __init pSeries_init_early(void)
{
- void *comport;
int iommu_off = 0;
- unsigned int default_speed;
- u64 physport;
DBG(" -> pSeries_init_early()\n");
@@ -384,17 +381,8 @@ static void __init pSeries_init_early(void)
get_property(of_chosen, "linux,iommu-off", NULL));
}
- generic_find_legacy_serial_ports(&physport, &default_speed);
-
if (platform_is_lpar())
find_udbg_vterm();
- else if (physport) {
- /* Map the uart for udbg. */
- comport = (void *)ioremap(physport, 16);
- udbg_init_uart(comport, default_speed);
-
- DBG("Hello World !\n");
- }
if (firmware_has_feature(FW_FEATURE_DABR))
ppc_md.set_dabr = pseries_set_dabr;
@@ -456,10 +444,10 @@ DECLARE_PER_CPU(unsigned long, smt_snooze_delay);
static inline void dedicated_idle_sleep(unsigned int cpu)
{
- struct paca_struct *ppaca = &paca[cpu ^ 1];
+ struct lppaca *plppaca = &lppaca[cpu ^ 1];
/* Only sleep if the other thread is not idle */
- if (!(ppaca->lppaca.idle)) {
+ if (!(plppaca->idle)) {
local_irq_disable();
/*
@@ -492,7 +480,6 @@ static inline void dedicated_idle_sleep(unsigned int cpu)
static void pseries_dedicated_idle(void)
{
- struct paca_struct *lpaca = get_paca();
unsigned int cpu = smp_processor_id();
unsigned long start_snooze;
unsigned long *smt_snooze_delay = &__get_cpu_var(smt_snooze_delay);
@@ -503,10 +490,10 @@ static void pseries_dedicated_idle(void)
* Indicate to the HV that we are idle. Now would be
* a good time to find other work to dispatch.
*/
- lpaca->lppaca.idle = 1;
+ get_lppaca()->idle = 1;
if (!need_resched()) {
- start_snooze = __get_tb() +
+ start_snooze = get_tb() +
*smt_snooze_delay * tb_ticks_per_usec;
while (!need_resched() && !cpu_is_offline(cpu)) {
@@ -520,7 +507,7 @@ static void pseries_dedicated_idle(void)
HMT_very_low();
if (*smt_snooze_delay != 0 &&
- __get_tb() > start_snooze) {
+ get_tb() > start_snooze) {
HMT_medium();
dedicated_idle_sleep(cpu);
}
@@ -530,7 +517,7 @@ static void pseries_dedicated_idle(void)
HMT_medium();
}
- lpaca->lppaca.idle = 0;
+ get_lppaca()->idle = 0;
ppc64_runlatch_on();
preempt_enable_no_resched();
@@ -544,7 +531,6 @@ static void pseries_dedicated_idle(void)
static void pseries_shared_idle(void)
{
- struct paca_struct *lpaca = get_paca();
unsigned int cpu = smp_processor_id();
while (1) {
@@ -552,7 +538,7 @@ static void pseries_shared_idle(void)
* Indicate to the HV that we are idle. Now would be
* a good time to find other work to dispatch.
*/
- lpaca->lppaca.idle = 1;
+ get_lppaca()->idle = 1;
while (!need_resched() && !cpu_is_offline(cpu)) {
local_irq_disable();
@@ -576,7 +562,7 @@ static void pseries_shared_idle(void)
HMT_medium();
}
- lpaca->lppaca.idle = 0;
+ get_lppaca()->idle = 0;
ppc64_runlatch_on();
preempt_enable_no_resched();
@@ -595,6 +581,27 @@ static int pSeries_pci_probe_mode(struct pci_bus *bus)
return PCI_PROBE_NORMAL;
}
+#ifdef CONFIG_KEXEC
+static void pseries_kexec_cpu_down(int crash_shutdown, int secondary)
+{
+ /* Don't risk a hypervisor call if we're crashing */
+ if (!crash_shutdown) {
+ unsigned long vpa = __pa(get_lppaca());
+
+ if (unregister_vpa(hard_smp_processor_id(), vpa)) {
+ printk("VPA deregistration of cpu %u (hw_cpu_id %d) "
+ "failed\n", smp_processor_id(),
+ hard_smp_processor_id());
+ }
+ }
+
+ if (ppc64_interrupt_controller == IC_OPEN_PIC)
+ mpic_teardown_this_cpu(secondary);
+ else
+ xics_teardown_cpu(secondary);
+}
+#endif
+
struct machdep_calls __initdata pSeries_md = {
.probe = pSeries_probe,
.setup_arch = pSeries_setup_arch,
@@ -617,4 +624,10 @@ struct machdep_calls __initdata pSeries_md = {
.check_legacy_ioport = pSeries_check_legacy_ioport,
.system_reset_exception = pSeries_system_reset_exception,
.machine_check_exception = pSeries_machine_check_exception,
+#ifdef CONFIG_KEXEC
+ .kexec_cpu_down = pseries_kexec_cpu_down,
+ .machine_kexec = default_machine_kexec,
+ .machine_kexec_prepare = default_machine_kexec_prepare,
+ .machine_crash_shutdown = default_machine_crash_shutdown,
+#endif
};
diff --git a/arch/powerpc/platforms/pseries/smp.c b/arch/powerpc/platforms/pseries/smp.c
index 5800cde7d5a..8e6b1ed1396 100644
--- a/arch/powerpc/platforms/pseries/smp.c
+++ b/arch/powerpc/platforms/pseries/smp.c
@@ -51,6 +51,7 @@
#include "plpar_wrappers.h"
#ifdef DEBUG
+#include <asm/udbg.h>
#define DBG(fmt...) udbg_printf(fmt)
#else
#define DBG(fmt...)
@@ -92,7 +93,7 @@ static int query_cpu_stopped(unsigned int pcpu)
return cpu_status;
}
-int pSeries_cpu_disable(void)
+static int pSeries_cpu_disable(void)
{
int cpu = smp_processor_id();
@@ -108,7 +109,7 @@ int pSeries_cpu_disable(void)
return 0;
}
-void pSeries_cpu_die(unsigned int cpu)
+static void pSeries_cpu_die(unsigned int cpu)
{
int tries;
int cpu_status;
@@ -281,7 +282,7 @@ static inline int __devinit smp_startup_cpu(unsigned int lcpu)
pcpu = get_hard_smp_processor_id(lcpu);
/* Fixup atomic count: it exited inside IRQ handler. */
- paca[lcpu].__current->thread_info->preempt_count = 0;
+ task_thread_info(paca[lcpu].__current)->preempt_count = 0;
/*
* If the RTAS start-cpu token does not exist then presume the
diff --git a/arch/powerpc/platforms/pseries/xics.c b/arch/powerpc/platforms/pseries/xics.c
index 72ac18067ec..fd823c7c9ac 100644
--- a/arch/powerpc/platforms/pseries/xics.c
+++ b/arch/powerpc/platforms/pseries/xics.c
@@ -48,11 +48,6 @@ static struct hw_interrupt_type xics_pic = {
.set_affinity = xics_set_affinity
};
-static struct hw_interrupt_type xics_8259_pic = {
- .typename = " XICS/8259",
- .ack = xics_mask_and_ack_irq,
-};
-
/* This is used to map real irq numbers to virtual */
static struct radix_tree_root irq_map = RADIX_TREE_INIT(GFP_ATOMIC);
@@ -367,12 +362,7 @@ int xics_get_irq(struct pt_regs *regs)
/* for sanity, this had better be < NR_IRQS - 16 */
if (vec == xics_irq_8259_cascade_real) {
irq = i8259_irq(regs);
- if (irq == -1) {
- /* Spurious cascaded interrupt. Still must ack xics */
- xics_end_irq(irq_offset_up(xics_irq_8259_cascade));
-
- irq = -1;
- }
+ xics_end_irq(irq_offset_up(xics_irq_8259_cascade));
} else if (vec == XICS_IRQ_SPURIOUS) {
irq = -1;
} else {
@@ -391,7 +381,7 @@ int xics_get_irq(struct pt_regs *regs)
#ifdef CONFIG_SMP
-irqreturn_t xics_ipi_action(int irq, void *dev_id, struct pt_regs *regs)
+static irqreturn_t xics_ipi_action(int irq, void *dev_id, struct pt_regs *regs)
{
int cpu = smp_processor_id();
@@ -417,7 +407,7 @@ irqreturn_t xics_ipi_action(int irq, void *dev_id, struct pt_regs *regs)
smp_message_recv(PPC_MSG_MIGRATE_TASK, regs);
}
#endif
-#ifdef CONFIG_DEBUGGER
+#if defined(CONFIG_DEBUGGER) || defined(CONFIG_KEXEC)
if (test_and_clear_bit(PPC_MSG_DEBUGGER_BREAK,
&xics_ipi_message[cpu].value)) {
mb();
@@ -542,6 +532,7 @@ nextnode:
xics_irq_8259_cascade_real = *ireg;
xics_irq_8259_cascade
= virt_irq_create_mapping(xics_irq_8259_cascade_real);
+ i8259_init(0, 0);
of_node_put(np);
}
@@ -565,12 +556,7 @@ nextnode:
#endif /* CONFIG_SMP */
}
- xics_8259_pic.enable = i8259_pic.enable;
- xics_8259_pic.disable = i8259_pic.disable;
- xics_8259_pic.end = i8259_pic.end;
- for (i = 0; i < 16; ++i)
- get_irq_desc(i)->handler = &xics_8259_pic;
- for (; i < NR_IRQS; ++i)
+ for (i = irq_offset_value(); i < NR_IRQS; ++i)
get_irq_desc(i)->handler = &xics_pic;
xics_setup_cpu();
@@ -590,7 +576,6 @@ static int __init xics_setup_i8259(void)
no_action, 0, "8259 cascade", NULL))
printk(KERN_ERR "xics_setup_i8259: couldn't get 8259 "
"cascade\n");
- i8259_init(0, 0);
}
return 0;
}