x86: optimise x86's do_page_fault (C entry point for the page fault path)
[linux-2.6.git] / arch / ia64 / sn / kernel / sn2 / sn2_smp.c
index b2e1e74..e585f9a 100644 (file)
 #include <asm/sn/shub_mmr.h>
 #include <asm/sn/nodepda.h>
 #include <asm/sn/rw_mmr.h>
+#include <asm/sn/sn_feature_sets.h>
 
 DEFINE_PER_CPU(struct ptc_stats, ptcstats);
 DECLARE_PER_CPU(struct ptc_stats, ptcstats);
 
 static  __cacheline_aligned DEFINE_SPINLOCK(sn2_global_ptc_lock);
 
+/* 0 = old algorithm (no IPI flushes), 1 = ipi deadlock flush, 2 = ipi instead of SHUB ptc, >2 = always ipi */
+static int sn2_flush_opt = 0;
+
 extern unsigned long
 sn2_ptc_deadlock_recovery_core(volatile unsigned long *, unsigned long,
                               volatile unsigned long *, unsigned long,
@@ -76,6 +80,8 @@ struct ptc_stats {
        unsigned long shub_itc_clocks;
        unsigned long shub_itc_clocks_max;
        unsigned long shub_ptc_flushes_not_my_mm;
+       unsigned long shub_ipi_flushes;
+       unsigned long shub_ipi_flushes_itc_clocks;
 };
 
 #define sn2_ptctest    0
@@ -93,6 +99,27 @@ static inline unsigned long wait_piowc(void)
        return (ws & SH_PIO_WRITE_STATUS_WRITE_DEADLOCK_MASK) != 0;
 }
 
+/**
+ * sn_migrate - SN-specific task migration actions
+ * @task: Task being migrated to new CPU
+ *
+ * SN2 PIO writes from separate CPUs are not guaranteed to arrive in order.
+ * Context switching user threads which have memory-mapped MMIO may cause
+ * PIOs to issue from separate CPUs, thus the PIO writes must be drained
+ * from the previous CPU's Shub before execution resumes on the new CPU.
+ */
+void sn_migrate(struct task_struct *task)
+{
+       pda_t *last_pda = pdacpu(task_thread_info(task)->last_cpu);
+       volatile unsigned long *adr = last_pda->pio_write_status_addr;
+       unsigned long val = last_pda->pio_write_status_val;
+
+       /* Drain PIO writes from old CPU's Shub */
+       while (unlikely((*adr & SH_PIO_WRITE_STATUS_PENDING_WRITE_COUNT_MASK)
+                       != val))
+               cpu_relax();
+}
+
 void sn_tlb_migrate_finish(struct mm_struct *mm)
 {
        /* flush_tlb_mm is inefficient if more than 1 users of mm */
@@ -100,6 +127,18 @@ void sn_tlb_migrate_finish(struct mm_struct *mm)
                flush_tlb_mm(mm);
 }
 
+static void
+sn2_ipi_flush_all_tlb(struct mm_struct *mm)
+{
+       unsigned long itc;
+
+       itc = ia64_get_itc();
+       smp_flush_tlb_cpumask(mm->cpu_vm_mask);
+       itc = ia64_get_itc() - itc;
+       __get_cpu_var(ptcstats).shub_ipi_flushes_itc_clocks += itc;
+       __get_cpu_var(ptcstats).shub_ipi_flushes++;
+}
+
 /**
  * sn2_global_tlb_purge - globally purge translation cache of virtual address range
  * @mm: mm_struct containing virtual address range
@@ -133,7 +172,12 @@ sn2_global_tlb_purge(struct mm_struct *mm, unsigned long start,
        unsigned long itc, itc2, flags, data0 = 0, data1 = 0, rr_value, old_rr = 0;
        short nasids[MAX_NUMNODES], nix;
        nodemask_t nodes_flushed;
-       int active, max_active, deadlock;
+       int active, max_active, deadlock, flush_opt = sn2_flush_opt;
+
+       if (flush_opt > 2) {
+               sn2_ipi_flush_all_tlb(mm);
+               return;
+       }
 
        nodes_clear(nodes_flushed);
        i = 0;
@@ -168,6 +212,12 @@ sn2_global_tlb_purge(struct mm_struct *mm, unsigned long start,
                return;
        }
 
+       if (flush_opt == 2) {
+               sn2_ipi_flush_all_tlb(mm);
+               preempt_enable();
+               return;
+       }
+
        itc = ia64_get_itc();
        nix = 0;
        for_each_node_mask(cnode, nodes_flushed)
@@ -235,6 +285,8 @@ sn2_global_tlb_purge(struct mm_struct *mm, unsigned long start,
                        }
                        if (active >= max_active || i == (nix - 1)) {
                                if ((deadlock = wait_piowc())) {
+                                       if (flush_opt == 1)
+                                               goto done;
                                        sn2_ptc_deadlock_recovery(nasids, ibegin, i, mynasid, ptc0, data0, ptc1, data1);
                                        if (reset_max_active_on_deadlock())
                                                max_active = 1;
@@ -246,6 +298,7 @@ sn2_global_tlb_purge(struct mm_struct *mm, unsigned long start,
                start += (1UL << nbits);
        } while (start < end);
 
+done:
        itc2 = ia64_get_itc() - itc2;
        __get_cpu_var(ptcstats).shub_itc_clocks += itc2;
        if (itc2 > __get_cpu_var(ptcstats).shub_itc_clocks_max)
@@ -258,6 +311,11 @@ sn2_global_tlb_purge(struct mm_struct *mm, unsigned long start,
 
        spin_unlock_irqrestore(PTC_LOCK(shub1), flags);
 
+       if (flush_opt == 1 && deadlock) {
+               __get_cpu_var(ptcstats).deadlocks++;
+               sn2_ipi_flush_all_tlb(mm);
+       }
+
        preempt_enable();
 }
 
@@ -372,6 +430,31 @@ void sn2_send_IPI(int cpuid, int vector, int delivery_mode, int redirect)
        sn_send_IPI_phys(nasid, physid, vector, delivery_mode);
 }
 
+#ifdef CONFIG_HOTPLUG_CPU
+/**
+ * sn_cpu_disable_allowed - Determine if a CPU can be disabled.
+ * @cpu - CPU that is requested to be disabled.
+ *
+ * CPU disable is only allowed on SHub2 systems running with a PROM
+ * that supports CPU disable. It is not permitted to disable the boot processor.
+ */
+bool sn_cpu_disable_allowed(int cpu)
+{
+       if (is_shub2() && sn_prom_feature_available(PRF_CPU_DISABLE_SUPPORT)) {
+               if (cpu != 0)
+                       return true;
+               else
+                       printk(KERN_WARNING
+                             "Disabling the boot processor is not allowed.\n");
+
+       } else
+               printk(KERN_WARNING
+                      "CPU disable is not supported on this system.\n");
+
+       return false;
+}
+#endif /* CONFIG_HOTPLUG_CPU */
+
 #ifdef CONFIG_PROC_FS
 
 #define PTC_BASENAME   "sgi_sn/ptc_statistics"
@@ -404,25 +487,45 @@ static int sn2_ptc_seq_show(struct seq_file *file, void *data)
 
        if (!cpu) {
                seq_printf(file,
-                          "# cpu ptc_l newrid ptc_flushes nodes_flushed deadlocks lock_nsec shub_nsec shub_nsec_max not_my_mm deadlock2\n");
-               seq_printf(file, "# ptctest %d\n", sn2_ptctest);
+                          "# cpu ptc_l newrid ptc_flushes nodes_flushed deadlocks lock_nsec shub_nsec shub_nsec_max not_my_mm deadlock2 ipi_fluches ipi_nsec\n");
+               seq_printf(file, "# ptctest %d, flushopt %d\n", sn2_ptctest, sn2_flush_opt);
        }
 
        if (cpu < NR_CPUS && cpu_online(cpu)) {
                stat = &per_cpu(ptcstats, cpu);
-               seq_printf(file, "cpu %d %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld\n", cpu, stat->ptc_l,
+               seq_printf(file, "cpu %d %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld\n", cpu, stat->ptc_l,
                                stat->change_rid, stat->shub_ptc_flushes, stat->nodes_flushed,
                                stat->deadlocks,
                                1000 * stat->lock_itc_clocks / per_cpu(cpu_info, cpu).cyc_per_usec,
                                1000 * stat->shub_itc_clocks / per_cpu(cpu_info, cpu).cyc_per_usec,
                                1000 * stat->shub_itc_clocks_max / per_cpu(cpu_info, cpu).cyc_per_usec,
                                stat->shub_ptc_flushes_not_my_mm,
-                               stat->deadlocks2);
+                               stat->deadlocks2,
+                               stat->shub_ipi_flushes,
+                               1000 * stat->shub_ipi_flushes_itc_clocks / per_cpu(cpu_info, cpu).cyc_per_usec);
        }
        return 0;
 }
 
-static struct seq_operations sn2_ptc_seq_ops = {
+static ssize_t sn2_ptc_proc_write(struct file *file, const char __user *user, size_t count, loff_t *data)
+{
+       int cpu;
+       char optstr[64];
+
+       if (count == 0 || count > sizeof(optstr))
+               return -EINVAL;
+       if (copy_from_user(optstr, user, count))
+               return -EFAULT;
+       optstr[count - 1] = '\0';
+       sn2_flush_opt = simple_strtoul(optstr, NULL, 0);
+
+       for_each_online_cpu(cpu)
+               memset(&per_cpu(ptcstats, cpu), 0, sizeof(struct ptc_stats));
+
+       return count;
+}
+
+static const struct seq_operations sn2_ptc_seq_ops = {
        .start = sn2_ptc_seq_start,
        .next = sn2_ptc_seq_next,
        .stop = sn2_ptc_seq_stop,
@@ -434,9 +537,10 @@ static int sn2_ptc_proc_open(struct inode *inode, struct file *file)
        return seq_open(file, &sn2_ptc_seq_ops);
 }
 
-static struct file_operations proc_sn2_ptc_operations = {
+static const struct file_operations proc_sn2_ptc_operations = {
        .open = sn2_ptc_proc_open,
        .read = seq_read,
+       .write = sn2_ptc_proc_write,
        .llseek = seq_lseek,
        .release = seq_release,
 };
@@ -448,11 +552,12 @@ static int __init sn2_ptc_init(void)
        if (!ia64_platform_is("sn2"))
                return 0;
 
-       if (!(proc_sn2_ptc = create_proc_entry(PTC_BASENAME, 0444, NULL))) {
+       proc_sn2_ptc = proc_create(PTC_BASENAME, 0444,
+                                  NULL, &proc_sn2_ptc_operations);
+       if (!&proc_sn2_ptc_operations) {
                printk(KERN_ERR "unable to create %s proc entry", PTC_BASENAME);
                return -EINVAL;
        }
-       proc_sn2_ptc->proc_fops = &proc_sn2_ptc_operations;
        spin_lock_init(&sn2_global_ptc_lock);
        return 0;
 }