This is an incomplete patch to support cpu offlining. It doesn't work completely yet. - the pdc call always fails. not sure why - the gsc irq rerouting stuff needs to be written This was partially tested on 2.6.8.1/2.6.9-rc2 randolph Index: arch/parisc/kernel/firmware.c =================================================================== RCS file: /var/cvs/linux-2.6/arch/parisc/kernel/firmware.c,v retrieving revision 1.12 diff -u -p -r1.12 firmware.c --- arch/parisc/kernel/firmware.c 17 Aug 2004 05:24:11 -0000 1.12 +++ arch/parisc/kernel/firmware.c 17 Sep 2004 20:59:46 -0000 @@ -205,6 +205,41 @@ int pdc_add_valid(unsigned long address) EXPORT_SYMBOL(pdc_add_valid); /** + * pdc_proc_stop - causes the currently executing processor to stop. Bus + * requestorship is disabled, the PSW I-bit is 0, and the module no longer + * participates in cache coherence protocols. It is the callers responsibility + * to ensure that any required data is flushed from the cache prior to calling + * the Stop Processor option. + */ +int pdc_proc_stop(void) +{ + int retval; + + spin_lock_irq(&pdc_lock); + retval = mem_pdc_call(PDC_PROC, PDC_PROC_STOP); + spin_unlock_irq(&pdc_lock); + + return retval; +} +EXPORT_SYMBOL(pdc_proc_stop); + +/** + * pdc_proc_rendez - causes the processor currently executing to enter + * the PDCE_RESET rendezvous code. + */ +int pdc_proc_rendez(void) +{ + int retval; + + spin_lock_irq(&pdc_lock); + retval = mem_pdc_call(PDC_PROC, PDC_PROC_RENDEZ); + spin_unlock_irq(&pdc_lock); + + return retval; +} +EXPORT_SYMBOL(pdc_proc_rendez); + +/** * pdc_chassis_info - Return chassis information. * @result: The return buffer. * @chassis_info: The memory buffer address. Index: arch/parisc/kernel/irq.c =================================================================== RCS file: /var/cvs/linux-2.6/arch/parisc/kernel/irq.c,v retrieving revision 1.16 diff -u -p -r1.16 irq.c --- arch/parisc/kernel/irq.c 30 Jul 2004 21:53:39 -0000 1.16 +++ arch/parisc/kernel/irq.c 17 Sep 2004 20:59:46 -0000 @@ -101,6 +101,11 @@ static inline void unmask_cpu_irq(void * on_each_cpu(cpu_set_eiem, (void *) cpu_eiem, 1, 1); } +static void reroute_cpu_irq(void *dev, int irq, int cpu) +{ + /* nothing to do for the CPU region */ +} + /* * XXX cpu_irq_actions[] will become 2 dimensional for per CPU EIR support. * correspond changes needed in: @@ -127,7 +132,8 @@ struct irq_region cpu0_irq_region = { .disable_irq = disable_cpu_irq, .enable_irq = enable_cpu_irq, .mask_irq = unmask_cpu_irq, - .unmask_irq = unmask_cpu_irq + .unmask_irq = unmask_cpu_irq, + .reroute_irq = reroute_cpu_irq }, .data = { .dev = &cpu_data[0], @@ -344,6 +350,10 @@ txn_alloc_addr(int virt_irq) return cpu_data[next_cpu].txn_addr; } +int txn_addr_on_cpu(unsigned long addr, int cpu) +{ + return cpu_data[cpu].txn_addr == addr; +} /* ** The alloc process needs to accept a parameter to accommodate limitations @@ -877,5 +887,38 @@ void __init init_IRQ(void) /* called from kernel/sysctl.c:sysctl_init() */ void __init init_irq_proc(void) { +} +#endif + +#ifdef CONFIG_SMP +void migrate_irqs_away_from(int cpu) +{ + /* Go through all the irqs, if they are programmed for this + * CPU, reprogram them somewhere else + */ + int regnr, i; + + spin_lock(&irq_lock); + for (regnr = 0; regnr < NR_IRQ_REGS; regnr++) { + struct irq_region *region = irq_region[regnr]; + + if (!region || !region->action) + continue; + + for (i = 0; i <= MAX_CPU_IRQ; i++) { + struct irqaction *action = ®ion->action[i]; + unsigned int irq = IRQ_FROM_REGION(regnr) + i; + + if (!action->handler) + continue; + + BUG_ON(region->ops.reroute_irq == NULL); + + region->ops.reroute_irq(region->data.dev, IRQ_OFFSET(irq), cpu); + } + } + spin_unlock(&irq_lock); + + local_irq_disable(); } #endif Index: arch/parisc/kernel/smp.c =================================================================== RCS file: /var/cvs/linux-2.6/arch/parisc/kernel/smp.c,v retrieving revision 1.14 diff -u -p -r1.14 smp.c --- arch/parisc/kernel/smp.c 13 Sep 2004 15:22:25 -0000 1.14 +++ arch/parisc/kernel/smp.c 17 Sep 2004 20:59:46 -0000 @@ -51,6 +51,7 @@ #include #include #include +#include #define kDEBUG 0 @@ -314,7 +315,6 @@ smp_send_start(void) { send_IPI_allbutse void smp_send_reschedule(int cpu) { send_IPI_single(cpu, IPI_RESCHEDULE); } - /** * Run a function on all other CPUs. * The function to run. This must be fast and non-blocking. @@ -420,7 +420,7 @@ smp_do_timer(struct pt_regs *regs) /* * Called by secondaries to update state and initialize CPU registers. */ -static void __init +static void __devinit smp_cpu_init(int cpunum) { extern int init_per_cpu(int); /* arch/parisc/kernel/setup.c */ @@ -457,7 +457,7 @@ smp_cpu_init(int cpunum) * Slaves start using C here. Indirectly called from smp_slave_stext. * Do what start_kernel() and main() do for boot strap processor (aka monarch) */ -void __init smp_callin(void) +void __devinit smp_callin(void) { extern void cpu_idle(void); /* arch/parisc/kernel/process.c */ int slave_id = cpu_now_booting; @@ -490,7 +490,7 @@ void __init smp_callin(void) /* * Bring one cpu online. */ -int __init smp_boot_one_cpu(int cpuid) +int __devinit smp_boot_one_cpu(int cpuid) { struct task_struct *idle; long timeout; @@ -523,8 +523,6 @@ int __init smp_boot_one_cpu(int cpuid) smp_init_current_idle_task = idle ; mb(); - printk("Releasing cpu %d now, hpa=%lx\n", cpuid, cpu_data[cpuid].hpa); - /* ** This gets PDC to release the CPU from a very tight loop. ** @@ -571,7 +569,7 @@ alive: return 0; } -void __devinit smp_prepare_boot_cpu(void) +void __init smp_prepare_boot_cpu(void) { int bootstrap_processor=cpu_data[0].cpuid; /* CPU ID of BSP */ @@ -594,7 +592,7 @@ void __devinit smp_prepare_boot_cpu(void ** inventory.c:do_inventory() hasn't yet been run and thus we ** don't 'discover' the additional CPU's until later. */ -void __init smp_prepare_cpus(unsigned int max_cpus) +void __devinit smp_prepare_cpus(unsigned int max_cpus) { cpus_clear(cpu_present_map); cpu_set(0, cpu_present_map); @@ -613,10 +611,41 @@ void smp_cpus_done(unsigned int cpu_max) int __devinit __cpu_up(unsigned int cpu) { + printk("SMP: CPU %d is now booting...\n", cpu); + if (cpu != 0 && cpu < parisc_max_cpus) smp_boot_one_cpu(cpu); return cpu_online(cpu) ? 0 : -ENOSYS; +} + +int __cpu_disable (void) +{ + int cpu = smp_processor_id(); + + /* + * dont permit boot processor for now + */ + if (cpu == 0) + return -EBUSY; + + migrate_irqs_away_from(cpu); + flush_cache_all_local(); + + printk ("SMP: CPU %d disabled\n", cpu); + return 0; +} + +void __cpu_die (unsigned int cpu) +{ + int ret; + + printk("SMP: CPU %d is being reset\n", cpu); + + if ((ret = pdc_proc_rendez()) != PDC_OK) + printk("Failed to place CPU %d in RENDEZ state, ret = %d\n", cpu, ret); + else + printk("CPU %d placed in RENDEZ state\n", cpu); } Index: drivers/parisc/gsc.c =================================================================== RCS file: /var/cvs/linux-2.6/drivers/parisc/gsc.c,v retrieving revision 1.3 diff -u -p -r1.3 gsc.c --- drivers/parisc/gsc.c 10 Mar 2004 19:24:48 -0000 1.3 +++ drivers/parisc/gsc.c 17 Sep 2004 20:59:47 -0000 @@ -157,11 +157,19 @@ busdev_unmask_irq(void *irq_dev, int irq */ } +static void +busdev_reroute_irq(void *irq_dev, int irq, int cpu) +{ + /* FIXME */ + BUG(); +} + struct irq_region_ops busdev_irq_ops = { .disable_irq = busdev_disable_irq, .enable_irq = busdev_enable_irq, .mask_irq = busdev_mask_irq, - .unmask_irq = busdev_unmask_irq + .unmask_irq = busdev_unmask_irq, + .reroute_irq = busdev_reroute_irq }; Index: drivers/parisc/iosapic.c =================================================================== RCS file: /var/cvs/linux-2.6/drivers/parisc/iosapic.c,v retrieving revision 1.7 diff -u -p -r1.7 iosapic.c --- drivers/parisc/iosapic.c 10 Jul 2004 07:51:15 -0000 1.7 +++ drivers/parisc/iosapic.c 17 Sep 2004 20:59:47 -0000 @@ -914,12 +914,33 @@ iosapic_unmask_irq(void *dev, int irq) BUG(); } +static void +iosapic_reroute_irq(void *dev, int irq, int cpu) +{ + struct vector_info *vi = &(((struct vector_info *)dev)[irq]); + + if (!txn_addr_on_cpu(vi->txn_addr, cpu)) + return; + + /* cpu should not be online at this point, so this should give + * as a txn addr on a different cpu + */ + vi->txn_addr = txn_alloc_addr(vi->txn_irq); + BUG_ON(txn_addr_on_cpu(vi->txn_addr, cpu)); + + printk("irq %d rerouted to txn_addr %lx\n", irq, vi->txn_addr); + + /* Reprogram the IRT */ + iosapic_enable_irq(dev, irq); +} + static struct irq_region_ops iosapic_irq_ops = { .disable_irq = iosapic_disable_irq, .enable_irq = iosapic_enable_irq, .mask_irq = iosapic_mask_irq, - .unmask_irq = iosapic_unmask_irq + .unmask_irq = iosapic_unmask_irq, + .reroute_irq = iosapic_reroute_irq }; @@ -163,6 +163,7 @@ #define LBA_EIOS_BASE 0x0260 /* Extra I/O port space */ #define LBA_EIOS_MASK 0x0268 +#define LBA_MMIO_MASK 0x0270 /* Global Address Mask */ #define LBA_DMA_CTL 0x0278 /* firmware sets this */ #define LBA_IBASE 0x0300 /* SBA DMA support */ @@ -182,13 +183,21 @@ #define LBA_IOSAPIC_BASE 0x800 /* Offset of IRQ logic */ -/* non-postable I/O port space, densely packed */ #ifdef CONFIG_PARISC64 -#define LBA_ASTRO_PORT_BASE (0xfffffffffee00000UL) + /* + ** Sign extend all BAR values on "legacy" platforms. + ** "Sprockets" PDC (Forte/Allegro) initializes everything + ** for "legacy" 32-bit OS (HPUX 10.20). + ** Upper 32-bits of 64-bit BAR will be zero too. + */ +#define LBA_F_EXTEND (0xffffffff00000000UL) #else -#define LBA_ASTRO_PORT_BASE (0xfee00000UL) +#define LBA_F_EXTEND (0UL) #endif +/* non-postable I/O port space, densely packed */ +#define LBA_ASTRO_PORT_BASE (LBA_F_EXTEND | 0xfee00000UL) + #define ELROY_HVERS 0x782 #define MERCURY_HVERS 0x783 #define QUICKSILVER_HVERS 0x784 @@ -785,18 +794,27 @@ lba_fixup_bus(struct pci_bus *bus) lba_dump_res(&ioport_resource, 2); } + err = request_resource(&iomem_resource, &(ldev->hba.elmmio_space)); + if (err < 0) { + lba_dump_res(&iomem_resource, 2); + /* BUG(); */ + } + err = request_resource(&iomem_resource, &(ldev->hba.lmmio_space)); if (err < 0) { - BUG(); + /* FIXME - overlaps with elmmio will fail here. + * Need to prune (or disable) the distributed range + */ lba_dump_res(&iomem_resource, 2); + /* BUG(); */ } #ifdef CONFIG_PARISC64 if (ldev->hba.gmmio_space.flags) { err = request_resource(&iomem_resource, &(ldev->hba.gmmio_space)); if (err < 0) { - BUG(); lba_dump_res(&iomem_resource, 2); + BUG(); } bus->resource[2] = &(ldev->hba.gmmio_space); } @@ -1001,7 +1019,6 @@ static struct pci_port_ops lba_astro_por static u##size lba_pat_in##size (struct pci_hba_data *l, u16 addr) \ { \ u##size t; \ - ASSERT(bus != NULL); \ DBG_PORT("%s(0x%p, 0x%x) ->", __FUNCTION__, l, addr); \ t = READ_REG##size(PIOP_TO_GMMIO(LBA_DEV(l), addr)); \ DBG_PORT(" 0x%x\n", t); \ @@ -1018,7 +1035,6 @@ LBA_PORT_IN(32, 0) static void lba_pat_out##size (struct pci_hba_data *l, u16 addr, u##size val) \ { \ void *where = (void *) PIOP_TO_GMMIO(LBA_DEV(l), addr); \ - ASSERT(bus != NULL); \ DBG_PORT("%s(0x%p, 0x%x, 0x%x)\n", __FUNCTION__, l, addr, val); \ WRITE_REG##size(val, where); \ /* flush the I/O down to the elroy at least */ \ @@ -1159,20 +1175,10 @@ static void lba_legacy_resources(struct parisc_device *pa_dev, struct lba_device *lba_dev) { struct resource *r; - unsigned long rsize; + unsigned long mmio_mask; int lba_num; -#ifdef CONFIG_PARISC64 - /* - ** Sign extend all BAR values on "legacy" platforms. - ** "Sprockets" PDC (Forte/Allegro) initializes everything - ** for "legacy" 32-bit OS (HPUX 10.20). - ** Upper 32-bits of 64-bit BAR will be zero too. - */ - lba_dev->hba.lmmio_space_offset = 0xffffffff00000000UL; -#else - lba_dev->hba.lmmio_space_offset = 0UL; -#endif + lba_dev->hba.lmmio_space_offset = LBA_F_EXTEND; /* ** With "legacy" firmware, the lowest byte of FW_SCRATCH @@ -1187,27 +1193,40 @@ lba_legacy_resources(struct parisc_devic r->start = lba_num & 0xff; r->end = (lba_num>>8) & 0xff; + if (IS_MERCURY(pa_dev)) { + /* Global Address Mask describes relevant addr/mask bits */ + mmio_mask = READ_REG32(pa_dev->hpa + LBA_MMIO_MASK); + } else + mmio_mask = ~1UL; /* clear enable bit */ + + /* Set up local PCI Bus resources - we don't really need ** them for Legacy boxes but it's nice to see in /proc. */ r = &(lba_dev->hba.lmmio_space); r->name = "LBA PCI LMMIO"; - r->flags = IORESOURCE_MEM; - /* Ignore "Range Enable" bit in the BASE register */ - r->start = PCI_HOST_ADDR(HBA_DATA(lba_dev), - ((long) READ_REG32(pa_dev->hpa + LBA_LMMIO_BASE)) & ~1UL); - rsize = ~READ_REG32(pa_dev->hpa + LBA_LMMIO_MASK) + 1; + r->start = (long) READ_REG32(pa_dev->hpa + LBA_LMMIO_BASE); + if (r->start & 1) { + unsigned long rsize; + r->flags = IORESOURCE_MEM; + /* mmio_mask also clears Enable bit */ + r->start &= mmio_mask; + r->start = PCI_HOST_ADDR(HBA_DATA(lba_dev), r->start); + rsize = READ_REG32(pa_dev->hpa + LBA_LMMIO_MASK); + r->end = r->start + ~rsize; - /* - ** Each rope only gets part of the distributed range. - ** Adjust "window" for this rope - */ - rsize /= ROPES_PER_SBA; - r->start += rsize * LBA_NUM(pa_dev->hpa); - r->end = r->start + rsize - 1 ; +#if 0 + /* + ** Each rope only gets part of the distributed range. + ** Adjust "window" for this rope + */ + rsize /= ROPES_PER_SBA; + r->start += rsize * LBA_NUM(pa_dev->hpa); + r->end = r->start + rsize - 1; +#endif + } /* - ** XXX FIXME - ignore LBA_ELMMIO_BASE for now ** "Directed" ranges are used when the "distributed range" isn't ** sufficient for all devices below a given LBA. Typically devices ** like graphics cards or X25 may need a directed range when the @@ -1216,31 +1235,24 @@ lba_legacy_resources(struct parisc_devic ** ** The main reason for ignoring it now frigging complications. ** Directed ranges may overlap (and have precedence) over - ** distributed ranges. Ie a distributed range assigned to a unused + ** distributed ranges. Or a distributed range assigned to a unused ** rope may be used by a directed range on a different rope. ** Support for graphics devices may require fixing this ** since they may be assigned a directed range which overlaps ** an existing (but unused portion of) distributed range. */ r = &(lba_dev->hba.elmmio_space); - r->name = "extra LBA PCI LMMIO"; - r->flags = IORESOURCE_MEM; + r->name = "LBA PCI ELMMIO"; r->start = READ_REG32(pa_dev->hpa + LBA_ELMMIO_BASE); - r->end = 0; - /* check Range Enable bit */ if (r->start & 1) { - /* First baby step to getting Direct Ranges listed in /proc. - ** AFAIK, only Sprockets PDC will setup a directed Range. - */ - - r->start &= ~1; - r->end = r->start; - r->end += ~READ_REG32(pa_dev->hpa + LBA_ELMMIO_MASK); - printk(KERN_DEBUG "WARNING: Ignoring enabled ELMMIO BASE 0x%0lx SIZE 0x%lx\n", - r->start, - r->end + 1); - + unsigned long rsize; + r->flags = IORESOURCE_MEM; + /* mmio_mask also clears Enable bit */ + r->start &= mmio_mask; + r->start = PCI_HOST_ADDR(HBA_DATA(lba_dev), r->start); + rsize = READ_REG32(pa_dev->hpa + LBA_ELMMIO_MASK); + r->end = r->start + ~rsize; } r = &(lba_dev->hba.io_space); @@ -1481,6 +1493,7 @@ lba_driver_probe(struct parisc_device *d is_pdc_pat() ? &pat_cfg_ops : &lba_cfg_ops, NULL); + /* This is in lieu of calling pci_assign_unassigned_resources() */ if (is_pdc_pat()) { /* assign resources to un-initialized devices */ DBG_PAT("LBA pci_bus_assign_resources()\n"); @@ -1493,6 +1506,8 @@ lba_driver_probe(struct parisc_device *d lba_dump_res(&lba_dev->hba.lmmio_space, 2); #endif } + pci_enable_bridges(lba_bus); + /* ** Once PCI register ops has walked the bus, access to config Index: include/asm-parisc/irq.h =================================================================== RCS file: /var/cvs/linux-2.6/include/asm-parisc/irq.h,v retrieving revision 1.3 diff -u -p -r1.3 irq.h --- include/asm-parisc/irq.h 15 Apr 2004 18:05:18 -0000 1.3 +++ include/asm-parisc/irq.h 17 Sep 2004 20:59:50 -0000 @@ -49,6 +49,7 @@ struct irq_region_ops { void (* enable_irq)(void *dev, int irq); void (* mask_irq)(void *dev, int irq); void (* unmask_irq)(void *dev, int irq); + void (*reroute_irq)(void *dev, int irq, int cpu); }; struct irq_region_data { @@ -80,6 +81,7 @@ static __inline__ int irq_canonicalize(i extern void disable_irq(int); #define disable_irq_nosync(i) disable_irq(i) extern void enable_irq(int); +extern void migrate_irqs_away_from(int cpu); extern void do_irq(struct irqaction *a, int i, struct pt_regs *p); extern void do_irq_mask(unsigned long mask, struct irq_region *region, @@ -92,6 +94,7 @@ extern int txn_alloc_irq(void); extern int txn_claim_irq(int); extern unsigned int txn_alloc_data(int, unsigned int); extern unsigned long txn_alloc_addr(int); +extern int txn_addr_on_cpu(unsigned long addr, int cpu); /* soft power switch support (power.c) */ extern struct tasklet_struct power_tasklet; Index: include/asm-parisc/pdc.h =================================================================== RCS file: /var/cvs/linux-2.6/include/asm-parisc/pdc.h,v retrieving revision 1.9 diff -u -p -r1.9 pdc.h --- include/asm-parisc/pdc.h 16 Aug 2004 17:25:28 -0000 1.9 +++ include/asm-parisc/pdc.h 17 Sep 2004 20:59:50 -0000 @@ -108,9 +108,11 @@ #define PDC_INSTR 15 /* get instr to invoke PDCE_CHECK() */ -#define PDC_PROC 16 /* (sprockets) */ +#define PDC_PROC 16 /* processor control */ +#define PDC_PROC_STOP 0 +#define PDC_PROC_RENDEZ 1 -#define PDC_CONFIG 16 /* (sprockets) */ +#define PDC_CONFIG 17 /* deconfig/reconfig modules */ #define PDC_CONFIG_DECONFIG 0 #define PDC_CONFIG_DRECONFIG 1 #define PDC_CONFIG_DRETURN_CONFIG 2 @@ -708,9 +710,11 @@ void pdc_console_restart(void); void setup_pdc(void); /* in inventory.c */ -/* wrapper-functions from pdc.c */ +/* wrapper-functions from firmware.c */ int pdc_add_valid(unsigned long address); +int pdc_proc_stop(void); +int pdc_proc_rendez(void); int pdc_chassis_info(struct pdc_chassis_info *chassis_info, void *led_info, unsigned long len); int pdc_chassis_disp(unsigned long disp); int pdc_coproc_cfg(struct pdc_coproc_cfg *pdc_coproc_info); Index: include/asm-parisc/smp.h =================================================================== RCS file: /var/cvs/linux-2.6/include/asm-parisc/smp.h,v retrieving revision 1.6 diff -u -p -r1.6 smp.h --- include/asm-parisc/smp.h 30 Jul 2004 21:53:39 -0000 1.6 +++ include/asm-parisc/smp.h 17 Sep 2004 20:59:50 -0000 @@ -57,13 +57,9 @@ extern unsigned long cpu_present_mask; #define NO_PROC_ID 0xFF /* No processor magic marker */ #define ANY_PROC_ID 0xFF /* Any processor magic marker */ -static inline int __cpu_disable (void) { - return 0; -} -static inline void __cpu_die (unsigned int cpu) { - while(1) - ; -} -extern int __cpu_up (unsigned int cpu); + +int __cpu_up (unsigned int cpu); +int __cpu_disable (void); +void __cpu_die (unsigned int cpu); #endif /* __ASM_SMP_H */