patch-2.3.2 linux/arch/alpha/kernel/smp.c
Next file: linux/arch/alpha/kernel/time.c
Previous file: linux/arch/alpha/kernel/signal.c
Back to the patch index
Back to the overall index
- Lines: 1552
- Date:
Fri May 14 12:41:23 1999
- Orig file:
v2.3.1/linux/arch/alpha/kernel/smp.c
- Orig date:
Mon May 10 09:55:21 1999
diff -u --recursive --new-file v2.3.1/linux/arch/alpha/kernel/smp.c linux/arch/alpha/kernel/smp.c
@@ -18,6 +18,7 @@
#include <asm/ptrace.h>
#include <asm/atomic.h>
+#include <asm/io.h>
#include <asm/irq.h>
#include <asm/bitops.h>
#include <asm/pgtable.h>
@@ -29,6 +30,8 @@
#include <asm/unistd.h>
#include "proto.h"
+#include "irq.h"
+
#define DEBUG_SMP 0
#if DEBUG_SMP
@@ -37,62 +40,44 @@
#define DBGS(args)
#endif
-struct ipi_msg_flush_tb_struct {
- volatile unsigned int flush_tb_mask;
- union {
- struct mm_struct * flush_mm;
- struct vm_area_struct * flush_vma;
- } p;
- unsigned long flush_addr;
- unsigned long flush_end;
-};
-
-static struct ipi_msg_flush_tb_struct ipi_msg_flush_tb __cacheline_aligned;
-static spinlock_t flush_tb_lock = SPIN_LOCK_UNLOCKED;
-
+/* A collection of per-processor data. */
struct cpuinfo_alpha cpu_data[NR_CPUS];
-spinlock_t ticker_lock = SPIN_LOCK_UNLOCKED;
-spinlock_t kernel_flag = SPIN_LOCK_UNLOCKED;
-
-unsigned int boot_cpu_id = 0;
-static int smp_activated = 0;
+/* A collection of single bit ipi messages. */
+static struct {
+ unsigned long bits __cacheline_aligned;
+} ipi_data[NR_CPUS];
-int smp_found_config = 0; /* Have we found an SMP box */
-static int max_cpus = -1;
+enum ipi_message_type {
+ IPI_RESCHEDULE,
+ IPI_CALL_FUNC,
+ IPI_CPU_STOP,
+};
-unsigned int cpu_present_map = 0;
+spinlock_t kernel_flag __cacheline_aligned = SPIN_LOCK_UNLOCKED;
-int smp_num_cpus = 1;
-int smp_num_probed = 0; /* Internal processor count */
+/* Set to a secondary's cpuid when it comes online. */
+static unsigned long smp_secondary_alive;
-int smp_threads_ready = 0;
-volatile unsigned long cpu_callin_map[NR_CPUS] = {0,};
-volatile unsigned long smp_spinning[NR_CPUS] = { 0, };
+unsigned long cpu_present_mask; /* Which cpus ids came online. */
+static int max_cpus = -1; /* Command-line limitation. */
+int smp_boot_cpuid; /* Which processor we booted from. */
+int smp_num_probed; /* Internal processor count */
+int smp_num_cpus = 1; /* Number that came online. */
+int smp_threads_ready; /* True once the per process idle is forked. */
cycles_t cacheflush_time;
-unsigned int prof_multiplier[NR_CPUS];
-unsigned int prof_counter[NR_CPUS];
-
-volatile int ipi_bits[NR_CPUS] __cacheline_aligned;
-
-unsigned long boot_cpu_palrev;
-
-volatile int smp_commenced = 0;
-volatile int smp_processors_ready = 0;
-
-volatile int cpu_number_map[NR_CPUS];
-volatile int cpu_logical_map[NR_CPUS];
+int cpu_number_map[NR_CPUS];
+int __cpu_logical_map[NR_CPUS];
extern void calibrate_delay(void);
-extern struct thread_struct * original_pcb_ptr;
-
-static void smp_setup_percpu_timer(void);
-static void secondary_cpu_start(int, struct task_struct *);
-static void send_cpu_msg(char *, int);
+extern asmlinkage void entInt(void);
-/* Process bootcommand SMP options, like "nosmp" and "maxcpus=" */
+
+/*
+ * Process bootcommand SMP options, like "nosmp" and "maxcpus=".
+ */
void __init
smp_setup(char *str, int *ints)
{
@@ -102,100 +87,87 @@
max_cpus = 0;
}
-static void __init
-smp_store_cpu_info(int id)
+/*
+ * Called by both boot and secondaries to move global data into
+ * per-processor storage.
+ */
+static inline void __init
+smp_store_cpu_info(int cpuid)
{
- /* This is it on Alpha, so far. */
- cpu_data[id].loops_per_sec = loops_per_sec;
+ cpu_data[cpuid].loops_per_sec = loops_per_sec;
}
-void __init
-smp_commence(void)
+/*
+ * Ideally sets up per-cpu profiling hooks. Doesn't do much now...
+ */
+static inline void __init
+smp_setup_percpu_timer(int cpuid)
{
- /* Lets the callin's below out of their loop. */
- mb();
- smp_commenced = 1;
+ cpu_data[cpuid].prof_counter = 1;
+ cpu_data[cpuid].prof_multiplier = 1;
+
+#ifdef NOT_YET_PROFILING
+ load_profile_irq(mid_xlate[cpu], lvl14_resolution);
+ if (cpu == smp_boot_cpuid)
+ enable_pil_irq(14);
+#endif
}
+/*
+ * Where secondaries begin a life of C.
+ */
void __init
smp_callin(void)
{
int cpuid = hard_smp_processor_id();
DBGS(("CALLIN %d state 0x%lx\n", cpuid, current->state));
-#ifdef HUH
- local_flush_cache_all();
- local_flush_tlb_all();
-#endif
-#if 0
- set_irq_udt(mid_xlate[boot_cpu_id]);
-#endif
+
+ /* Turn on machine checks. */
+ wrmces(7);
+
+ /* Set trap vectors. */
+ trap_init();
+
+ /* Set interrupt vector. */
+ wrent(entInt, 0);
+
+ /* Setup the scheduler for this processor. */
+ init_idle();
/* Get our local ticker going. */
- smp_setup_percpu_timer();
+ smp_setup_percpu_timer(cpuid);
-#if 0
+ /* Must have completely accurate bogos. */
+ __sti();
calibrate_delay();
-#endif
smp_store_cpu_info(cpuid);
-#ifdef HUH
- local_flush_cache_all();
- local_flush_tlb_all();
-#endif
/* Allow master to continue. */
- set_bit(cpuid, (unsigned long *)&cpu_callin_map[cpuid]);
-#ifdef HUH
- local_flush_cache_all();
- local_flush_tlb_all();
-#endif
-
-#ifdef NOT_YET
- while(!task[cpuid] || current_set[cpuid] != task[cpuid])
- barrier();
-#endif
+ wmb();
+ smp_secondary_alive = cpuid;
-#ifdef HUH
- local_flush_cache_all();
- local_flush_tlb_all();
-#endif
-#if 0
- __sti();
-#endif
-}
-
-asmlinkage int __init
-start_secondary(void *unused)
-{
- extern asmlinkage void entInt(void);
- extern void paging_init_secondary(void);
+ /* Wait for the go code. */
+ while (!smp_threads_ready)
+ barrier();
- wrmces(7);
- paging_init_secondary();
- trap_init();
- wrent(entInt, 0);
+ printk(KERN_INFO "SMP: commencing CPU %d current %p\n",
+ cpuid, current);
- smp_callin();
- while (!smp_commenced)
- barrier();
-#if 1
- printk("start_secondary: commencing CPU %d current %p\n",
- hard_smp_processor_id(), current);
-#endif
+ /* Do nothing. */
cpu_idle(NULL);
}
+
+/*
+ * Rough estimation for SMP scheduling, this is the number of cycles it
+ * takes for a fully memory-limited process to flush the SMP-local cache.
+ *
+ * We are not told how much cache there is, so we have to guess.
+ */
static void __init
smp_tune_scheduling (void)
{
- /*
- * Rough estimation for SMP scheduling, this is the number of
- * cycles it takes for a fully memory-limited process to flush
- * the SMP-local cache.
- *
- * We are not told how much cache there is, so we have to guess.
- */
-
struct percpu_struct *cpu;
unsigned long on_chip_cache;
unsigned long freq;
@@ -231,259 +203,159 @@
cacheflush_time = freq / 1024 * on_chip_cache / 5000;
}
-
/*
- * Cycle through the processors sending START msgs to boot each.
+ * Send a message to a secondary's console. "START" is one such
+ * interesting message. ;-)
*/
-void __init
-smp_boot_cpus(void)
+static void
+send_secondary_console_msg(char *str, int cpuid)
{
- int cpucount = 0;
- int i, first, prev;
-
- printk("Entering SMP Mode.\n");
-
-#if 0
- __sti();
-#endif
-
- for(i=0; i < NR_CPUS; i++) {
- cpu_number_map[i] = -1;
- cpu_logical_map[i] = -1;
- prof_counter[i] = 1;
- prof_multiplier[i] = 1;
- ipi_bits[i] = 0;
- }
-
- cpu_number_map[boot_cpu_id] = 0;
- cpu_logical_map[0] = boot_cpu_id;
- current->processor = boot_cpu_id; /* ??? */
+ struct percpu_struct *cpu;
+ register char *cp1, *cp2;
+ unsigned long cpumask;
+ size_t len;
+ long timeout;
- smp_store_cpu_info(boot_cpu_id);
- smp_tune_scheduling();
-#ifdef NOT_YET
- printk("CPU%d: ", boot_cpu_id);
- print_cpu_info(&cpu_data[boot_cpu_id]);
- set_irq_udt(mid_xlate[boot_cpu_id]);
-#endif
- smp_setup_percpu_timer();
-#ifdef HUH
- local_flush_cache_all();
-#endif
- if (smp_num_probed == 1)
- return; /* Not an MP box. */
+ cpu = (struct percpu_struct *)
+ ((char*)hwrpb
+ + hwrpb->processor_offset
+ + cpuid * hwrpb->processor_size);
-#if NOT_YET
- /*
- * If SMP should be disabled, then really disable it!
- */
- if (!max_cpus)
- {
- smp_found_config = 0;
- printk(KERN_INFO "SMP mode deactivated.\n");
- }
-#endif
+ cpumask = (1L << cpuid);
+ if (hwrpb->txrdy & cpumask)
+ goto delay1;
+ ready1:
- for (i = 0; i < NR_CPUS; i++) {
+ cp2 = str;
+ len = strlen(cp2);
+ *(unsigned int *)&cpu->ipc_buffer[0] = len;
+ cp1 = (char *) &cpu->ipc_buffer[1];
+ memcpy(cp1, cp2, len);
- if (i == boot_cpu_id)
- continue;
+ /* atomic test and set */
+ wmb();
+ set_bit(cpuid, &hwrpb->rxrdy);
- if (cpu_present_map & (1 << i)) {
- struct task_struct *idle;
- int timeout;
-
- /* Cook up an idler for this guy. */
- kernel_thread(start_secondary, NULL, CLONE_PID);
- idle = task[++cpucount];
- if (!idle)
- panic("No idle process for CPU %d", i);
- idle->processor = i;
-
- DBGS(("smp_boot_cpus: CPU %d state 0x%lx flags 0x%lx\n",
- i, idle->state, idle->flags));
-
- /* whirrr, whirrr, whirrrrrrrrr... */
-#ifdef HUH
- local_flush_cache_all();
-#endif
- secondary_cpu_start(i, idle);
+ if (hwrpb->txrdy & cpumask)
+ goto delay2;
+ ready2:
+ return;
- /* wheee... it's going... wait for 5 secs...*/
- for (timeout = 0; timeout < 50000; timeout++) {
- if (cpu_callin_map[i])
- break;
- udelay(100);
- }
- if (cpu_callin_map[i]) {
- /* Another "Red Snapper". */
- cpu_number_map[i] = cpucount;
- cpu_logical_map[cpucount] = i;
- } else {
- cpucount--;
- printk("smp_boot_cpus: Processor %d"
- " is stuck 0x%lx.\n", i, idle->flags);
- }
- }
- if (!(cpu_callin_map[i])) {
- cpu_present_map &= ~(1 << i);
- cpu_number_map[i] = -1;
- }
- }
-#ifdef HUH
- local_flush_cache_all();
-#endif
- if (cpucount == 0) {
- printk("smp_boot_cpus: ERROR - only one Processor found.\n");
- cpu_present_map = (1 << smp_processor_id());
- } else {
- unsigned long bogosum = 0;
- for (i = 0; i < NR_CPUS; i++) {
- if (cpu_present_map & (1 << i))
- bogosum += cpu_data[i].loops_per_sec;
- }
- printk("smp_boot_cpus: Total of %d Processors activated"
- " (%lu.%02lu BogoMIPS).\n",
- cpucount + 1,
- (bogosum + 2500)/500000,
- ((bogosum + 2500)/5000)%100);
- smp_activated = 1;
- smp_num_cpus = cpucount + 1;
+delay1:
+ /* Wait one second. Note that jiffies aren't ticking yet. */
+ for (timeout = 100000; timeout > 0; --timeout) {
+ if (!(hwrpb->txrdy & cpumask))
+ goto ready1;
+ udelay(10);
+ barrier();
}
+ goto timeout;
- /* Setup CPU list for IRQ distribution scheme. */
- first = prev = -1;
- for (i = 0; i < NR_CPUS; i++) {
- if (cpu_present_map & (1 << i)) {
- if (first == -1)
- first = i;
- if (prev != -1)
- cpu_data[i].next = i;
- prev = i;
- }
+delay2:
+ /* Wait one second. */
+ for (timeout = 100000; timeout > 0; --timeout) {
+ if (!(hwrpb->txrdy & cpumask))
+ goto ready2;
+ udelay(10);
+ barrier();
}
- cpu_data[prev].next = first;
+ goto timeout;
- /* Ok, they are spinning and ready to go. */
- smp_processors_ready = 1;
+timeout:
+ printk("Processor %x not ready\n", cpuid);
+ return;
}
-static void __init
-smp_setup_percpu_timer(void)
+/*
+ * A secondary console wants to send a message. Receive it.
+ */
+static void
+recv_secondary_console_msg(void)
{
- int cpu = smp_processor_id();
-
- prof_counter[cpu] = prof_multiplier[cpu] = 1;
-#ifdef NOT_YET
- load_profile_irq(mid_xlate[cpu], lvl14_resolution);
- if (cpu == boot_cpu_id)
- enable_pil_irq(14);
-#endif
-}
-
-extern void update_one_process(struct task_struct *p, unsigned long ticks,
- unsigned long user, unsigned long system,
- int cpu);
+ int mycpu, i, cnt;
+ unsigned long txrdy = hwrpb->txrdy;
+ char *cp1, *cp2, buf[80];
+ struct percpu_struct *cpu;
-void
-smp_percpu_timer_interrupt(struct pt_regs *regs)
-{
- int cpu = smp_processor_id();
+ DBGS(("recv_secondary_console_msg: TXRDY 0x%lx.\n", txrdy));
-#ifdef NOT_YET
- clear_profile_irq(mid_xlate[cpu]);
- if(!user_mode(regs))
- alpha_do_profile(regs->pc);
-#endif
+ mycpu = hard_smp_processor_id();
- if (!--prof_counter[cpu]) {
- int user = user_mode(regs);
- if (current->pid) {
- update_one_process(current, 1, user, !user, cpu);
+ for (i = 0; i < NR_CPUS; i++) {
+ if (!(txrdy & (1L << i)))
+ continue;
- if (--current->counter < 0) {
- current->counter = 0;
- current->need_resched = 1;
- }
+ DBGS(("recv_secondary_console_msg: "
+ "TXRDY contains CPU %d.\n", i));
- spin_lock(&ticker_lock);
- if (user) {
- if (current->priority < DEF_PRIORITY) {
- kstat.cpu_nice++;
- kstat.per_cpu_nice[cpu]++;
- } else {
- kstat.cpu_user++;
- kstat.per_cpu_user[cpu]++;
- }
- } else {
- kstat.cpu_system++;
- kstat.per_cpu_system[cpu]++;
- }
- spin_unlock(&ticker_lock);
- }
- prof_counter[cpu] = prof_multiplier[cpu];
- }
-}
+ cpu = (struct percpu_struct *)
+ ((char*)hwrpb
+ + hwrpb->processor_offset
+ + i * hwrpb->processor_size);
-int __init
-setup_profiling_timer(unsigned int multiplier)
-{
-#ifdef NOT_YET
- int i;
- unsigned long flags;
+ printk(KERN_INFO "recv_secondary_console_msg: on %d from %d"
+ " HALT_REASON 0x%lx FLAGS 0x%lx\n",
+ mycpu, i, cpu->halt_reason, cpu->flags);
- /* Prevent level14 ticker IRQ flooding. */
- if((!multiplier) || (lvl14_resolution / multiplier) < 500)
- return -EINVAL;
+ cnt = cpu->ipc_buffer[0] >> 32;
+ if (cnt <= 0 || cnt >= 80)
+ strcpy(buf, "<<< BOGUS MSG >>>");
+ else {
+ cp1 = (char *) &cpu->ipc_buffer[11];
+ cp2 = buf;
+ strcpy(cp2, cp1);
+
+ while ((cp2 = strchr(cp2, '\r')) != 0) {
+ *cp2 = ' ';
+ if (cp2[1] == '\n')
+ cp2[1] = ' ';
+ }
+ }
- save_and_cli(flags);
- for(i = 0; i < NR_CPUS; i++) {
- if(cpu_present_map & (1 << i)) {
- load_profile_irq(mid_xlate[i], lvl14_resolution / multip
-lier);
- prof_multiplier[i] = multiplier;
- }
+ printk(KERN_INFO "recv_secondary_console_msg: on %d "
+ "message is '%s'\n", mycpu, buf);
}
- restore_flags(flags);
- return 0;
-
-#endif
- return -EINVAL;
-}
-
-/* Only broken Intel needs this, thus it should not even be
- referenced globally. */
-
-void __init
-initialize_secondary(void)
-{
+ hwrpb->txrdy = 0;
}
-static void __init
+/*
+ * Convince the console to have a secondary cpu begin execution.
+ */
+static int __init
secondary_cpu_start(int cpuid, struct task_struct *idle)
{
struct percpu_struct *cpu;
- int timeout;
+ struct pcb_struct *hwpcb;
+ long timeout;
cpu = (struct percpu_struct *)
((char*)hwrpb
+ hwrpb->processor_offset
+ cpuid * hwrpb->processor_size);
+ hwpcb = (struct pcb_struct *) cpu->hwpcb;
- /* Set context to idle thread this CPU will use when running
- assumption is that the idle thread is all set to go... ??? */
- memcpy(&cpu->hwpcb[0], &idle->tss, sizeof(struct pcb_struct));
- cpu->hwpcb[4] = cpu->hwpcb[0]; /* UNIQUE set to KSP ??? */
+ /* Initialize the CPU's HWPCB to something just good enough for
+ us to get started. Immediately after starting, we'll swpctx
+ to the target idle task's tss. Reuse the stack in the mean
+ time. Precalculate the target PCBB. */
+ hwpcb->ksp = (unsigned long) idle + sizeof(union task_union) - 16;
+ hwpcb->usp = 0;
+ hwpcb->ptbr = idle->tss.ptbr;
+ hwpcb->pcc = 0;
+ hwpcb->asn = 0;
+ hwpcb->unique = virt_to_phys(&idle->tss);
+ hwpcb->flags = idle->tss.pal_flags;
+ hwpcb->res1 = hwpcb->res2 = 0;
- DBGS(("KSP 0x%lx PTBR 0x%lx VPTBR 0x%lx\n",
- cpu->hwpcb[0], cpu->hwpcb[2], hwrpb->vptb));
+ DBGS(("KSP 0x%lx PTBR 0x%lx VPTBR 0x%lx UNIQUE 0x%lx\n",
+ hwpcb->ksp, hwpcb->ptbr, hwrpb->vptb, hwcpb->unique));
DBGS(("Starting secondary cpu %d: state 0x%lx pal_flags 0x%lx\n",
cpuid, idle->state, idle->tss.pal_flags));
/* Setup HWRPB fields that SRM uses to activate secondary CPU */
- hwrpb->CPU_restart = __start_cpu;
- hwrpb->CPU_restart_data = (unsigned long) idle;
+ hwrpb->CPU_restart = __smp_callin;
+ hwrpb->CPU_restart_data = (unsigned long) __smp_callin;
/* Recalculate and update the HWRPB checksum */
hwrpb_update_checksum(hwrpb);
@@ -495,99 +367,97 @@
/* SRM III 3.4.1.3 */
cpu->flags |= 0x22; /* turn on Context Valid and Restart Capable */
cpu->flags &= ~1; /* turn off Bootstrap In Progress */
- mb();
+ wmb();
- send_cpu_msg("START\r\n", cpuid);
+ send_secondary_console_msg("START\r\n", cpuid);
- /* now, we wait... */
- for (timeout = 10000; !(cpu->flags & 1); timeout--) {
- if (timeout <= 0) {
- printk("Processor %d failed to start\n", cpuid);
- /* needed for pset_info to work */
-#if 0
- ipc_processor_enable(cpu_to_processor(cpunum));
-#endif
- return;
- }
- mdelay(1);
+ /* Wait 1 second for an ACK from the console. Note that jiffies
+ aren't ticking yet. */
+ for (timeout = 100000; timeout > 0; timeout--) {
+ if (cpu->flags & 1)
+ goto started;
+ udelay(10);
barrier();
}
+ printk(KERN_ERR "SMP: Processor %d failed to start.\n", cpuid);
+ return -1;
+
+started:
DBGS(("secondary_cpu_start: SUCCESS for CPU %d!!!\n", cpuid));
+ return 0;
}
-static void
-send_cpu_msg(char *str, int cpuid)
+/*
+ * Bring one cpu online.
+ */
+static int __init
+smp_boot_one_cpu(int cpuid, int cpunum)
{
- struct percpu_struct *cpu;
- register char *cp1, *cp2;
- unsigned long cpumask;
- size_t len;
- int timeout;
-
- cpu = (struct percpu_struct *)
- ((char*)hwrpb
- + hwrpb->processor_offset
- + cpuid * hwrpb->processor_size);
-
- cpumask = (1L << cpuid);
- if (hwrpb->txrdy & cpumask)
- goto delay1;
- ready1:
-
- cp2 = str;
- len = strlen(cp2);
- *(unsigned int *)&cpu->ipc_buffer[0] = len;
- cp1 = (char *) &cpu->ipc_buffer[1];
- memcpy(cp1, cp2, len);
-
- /* atomic test and set */
- set_bit(cpuid, &hwrpb->rxrdy);
+ struct task_struct *idle;
+ long timeout;
- if (hwrpb->txrdy & cpumask)
- goto delay2;
- ready2:
- return;
-
-delay1:
- for (timeout = 10000; timeout > 0; --timeout) {
- if (!(hwrpb->txrdy & cpumask))
- goto ready1;
- udelay(100);
+ /* Cook up an idler for this guy. Note that the address we give
+ to kernel_thread is irrelevant -- it's going to start where
+ HWRPB.CPU_restart says to start. But this gets all the other
+ task-y sort of data structures set up like we wish. */
+ kernel_thread((void *)__smp_callin, NULL, CLONE_PID|CLONE_VM);
+ idle = task[cpunum];
+ if (!idle)
+ panic("No idle process for CPU %d", cpuid);
+ idle->processor = cpuid;
+
+ /* Schedule the first task manually. */
+ /* ??? Ingo, what is this? */
+ idle->has_cpu = 1;
+
+ DBGS(("smp_boot_one_cpu: CPU %d state 0x%lx flags 0x%lx\n",
+ cpuid, idle->state, idle->flags));
+
+ /* The secondary will change this once it is happy. Note that
+ secondary_cpu_start contains the necessary memory barrier. */
+ smp_secondary_alive = -1;
+
+ /* Whirrr, whirrr, whirrrrrrrrr... */
+ if (secondary_cpu_start(cpuid, idle))
+ return -1;
+
+ /* We've been acked by the console; wait one second for the task
+ to start up for real. Note that jiffies aren't ticking yet. */
+ for (timeout = 0; timeout < 100000; timeout++) {
+ if (smp_secondary_alive != -1)
+ goto alive;
+ udelay(10);
barrier();
}
- goto timeout;
-
-delay2:
- for (timeout = 10000; timeout > 0; --timeout) {
- if (!(hwrpb->txrdy & cpumask))
- goto ready2;
- udelay(100);
- barrier();
- }
- goto timeout;
-timeout:
- printk("Processor %x not ready\n", cpuid);
- return;
+ printk(KERN_ERR "SMP: Processor %d is stuck.\n", cpuid);
+ return -1;
+
+alive:
+ /* Another "Red Snapper". */
+ cpu_number_map[cpuid] = cpunum;
+ __cpu_logical_map[cpunum] = cpuid;
+ return 0;
}
/*
- * setup_smp()
- *
- * called from arch/alpha/kernel/setup.c:setup_arch() when __SMP__ defined
+ * Called from setup_arch. Detect an SMP system and which processors
+ * are present.
*/
void __init
setup_smp(void)
{
struct percpu_struct *cpubase, *cpu;
int i;
-
- boot_cpu_id = hard_smp_processor_id();
- if (boot_cpu_id != 0) {
- printk("setup_smp: boot_cpu_id != 0 (%d).\n", boot_cpu_id);
+
+ smp_boot_cpuid = hard_smp_processor_id();
+ if (smp_boot_cpuid != 0) {
+ printk(KERN_WARNING "SMP: Booting off cpu %d instead of 0?\n",
+ smp_boot_cpuid);
}
if (hwrpb->nr_processors > 1) {
+ int boot_cpu_palrev;
DBGS(("setup_smp: nr_processors %ld\n",
hwrpb->nr_processors));
@@ -601,10 +471,9 @@
((char *)cpubase + i*hwrpb->processor_size);
if ((cpu->flags & 0x1cc) == 0x1cc) {
smp_num_probed++;
- /* assume here that "whami" == index */
- cpu_present_map |= (1 << i);
- if (i != boot_cpu_id)
- cpu->pal_revision = boot_cpu_palrev;
+ /* Assume here that "whami" == index */
+ cpu_present_mask |= (1L << i);
+ cpu->pal_revision = boot_cpu_palrev;
}
DBGS(("setup_smp: CPU %d: flags 0x%lx type 0x%lx\n",
@@ -614,76 +483,249 @@
}
} else {
smp_num_probed = 1;
- cpu_present_map = (1 << boot_cpu_id);
+ cpu_present_mask = (1L << smp_boot_cpuid);
}
- printk("setup_smp: %d CPUs probed, cpu_present_map 0x%x,"
- " boot_cpu_id %d\n",
- smp_num_probed, cpu_present_map, boot_cpu_id);
+
+ printk(KERN_INFO "SMP: %d CPUs probed -- cpu_present_mask = %lx\n",
+ smp_num_probed, cpu_present_mask);
}
-static void
-secondary_console_message(void)
+/*
+ * Called by smp_init bring all the secondaries online and hold them.
+ */
+void __init
+smp_boot_cpus(void)
{
- int mycpu, i, cnt;
- unsigned long txrdy = hwrpb->txrdy;
- char *cp1, *cp2, buf[80];
- struct percpu_struct *cpu;
+ int cpu_count, i;
+ unsigned long bogosum;
- DBGS(("secondary_console_message: TXRDY 0x%lx.\n", txrdy));
+ /* Take care of some initial bookkeeping. */
+ memset(cpu_number_map, -1, sizeof(cpu_number_map));
+ memset(__cpu_logical_map, -1, sizeof(__cpu_logical_map));
+ memset(ipi_data, 0, sizeof(ipi_data));
+
+ cpu_number_map[smp_boot_cpuid] = 0;
+ __cpu_logical_map[0] = smp_boot_cpuid;
+ current->processor = smp_boot_cpuid;
- mycpu = hard_smp_processor_id();
+ smp_store_cpu_info(smp_boot_cpuid);
+ smp_tune_scheduling();
+ smp_setup_percpu_timer(smp_boot_cpuid);
+
+ init_idle();
+
+ /* Nothing to do on a UP box, or when told not to. */
+ if (smp_num_probed == 1 || max_cpus == 0) {
+ printk(KERN_INFO "SMP mode deactivated.\n");
+ return;
+ }
+ printk(KERN_INFO "SMP starting up secondaries.\n");
+
+ cpu_count = 1;
for (i = 0; i < NR_CPUS; i++) {
- if (!(txrdy & (1L << i)))
+ if (i == smp_boot_cpuid)
continue;
- DBGS(("secondary_console_message: "
- "TXRDY contains CPU %d.\n", i));
+ if (((cpu_present_mask >> i) & 1) == 0)
+ continue;
- cpu = (struct percpu_struct *)
- ((char*)hwrpb
- + hwrpb->processor_offset
- + i * hwrpb->processor_size);
+ if (smp_boot_one_cpu(i, cpu_count))
+ continue;
- printk("secondary_console_message: on %d from %d"
- " HALT_REASON 0x%lx FLAGS 0x%lx\n",
- mycpu, i, cpu->halt_reason, cpu->flags);
+ cpu_count++;
+ }
- cnt = cpu->ipc_buffer[0] >> 32;
- if (cnt <= 0 || cnt >= 80)
- strcpy(buf, "<<< BOGUS MSG >>>");
- else {
- cp1 = (char *) &cpu->ipc_buffer[11];
- cp2 = buf;
- strcpy(cp2, cp1);
-
- while ((cp2 = strchr(cp2, '\r')) != 0) {
- *cp2 = ' ';
- if (cp2[1] == '\n')
- cp2[1] = ' ';
- }
- }
+ if (cpu_count == 1) {
+ printk(KERN_ERR "SMP: Only one lonely processor alive.\n");
+ return;
+ }
+
+ bogosum = 0;
+ for (i = 0; i < NR_CPUS; i++) {
+ if (cpu_present_mask & (1L << i))
+ bogosum += cpu_data[i].loops_per_sec;
+ }
+ printk(KERN_INFO "SMP: Total of %d processors activated "
+ "(%lu.%02lu BogoMIPS).\n",
+ cpu_count, (bogosum + 2500) / 500000,
+ ((bogosum + 2500) / 5000) % 100);
+
+ smp_num_cpus = cpu_count;
+}
+
+/*
+ * Called by smp_init to release the blocking online cpus once they
+ * are all started.
+ */
+void __init
+smp_commence(void)
+{
+ /* smp_init sets smp_threads_ready -- that's enough. */
+ mb();
+}
+
+/*
+ * Only broken Intel needs this, thus it should not even be
+ * referenced globally.
+ */
+
+void __init
+initialize_secondary(void)
+{
+}
+
+
+extern void update_one_process(struct task_struct *p, unsigned long ticks,
+ unsigned long user, unsigned long system,
+ int cpu);
+
+void
+smp_percpu_timer_interrupt(struct pt_regs *regs)
+{
+ int cpu = smp_processor_id();
+ int user = user_mode(regs);
+ struct cpuinfo_alpha *data = &cpu_data[cpu];
+
+#ifdef NOT_YET_PROFILING
+ clear_profile_irq(mid_xlate[cpu]);
+ if (!user)
+ alpha_do_profile(regs->pc);
+#endif
+
+ if (!--data->prof_counter) {
+ /* We need to make like a normal interrupt -- otherwise
+ timer interrupts ignore the global interrupt lock,
+ which would be a Bad Thing. */
+ irq_enter(cpu, TIMER_IRQ);
+
+ update_one_process(current, 1, user, !user, cpu);
+ if (current->pid) {
+ if (--current->counter < 0) {
+ current->counter = 0;
+ current->need_resched = 1;
+ }
+
+ if (user) {
+ if (current->priority < DEF_PRIORITY) {
+ kstat.cpu_nice++;
+ kstat.per_cpu_nice[cpu]++;
+ } else {
+ kstat.cpu_user++;
+ kstat.per_cpu_user[cpu]++;
+ }
+ } else {
+ kstat.cpu_system++;
+ kstat.per_cpu_system[cpu]++;
+ }
+ }
- printk("secondary_console_message: on %d message is '%s'\n",
- mycpu, buf);
+ data->prof_counter = data->prof_multiplier;
+ irq_exit(cpu, TIMER_IRQ);
}
+}
- hwrpb->txrdy = 0;
+int __init
+setup_profiling_timer(unsigned int multiplier)
+{
+#ifdef NOT_YET_PROFILING
+ int i;
+ unsigned long flags;
+
+ /* Prevent level14 ticker IRQ flooding. */
+ if((!multiplier) || (lvl14_resolution / multiplier) < 500)
+ return -EINVAL;
+
+ save_and_cli(flags);
+ for (i = 0; i < NR_CPUS; i++) {
+ if (cpu_present_mask & (1L << i)) {
+ load_profile_irq(mid_xlate[i],
+ lvl14_resolution / multiplier);
+ prof_multiplier[i] = multiplier;
+ }
+ }
+ restore_flags(flags);
+
+ return 0;
+#else
+ return -EINVAL;
+#endif
}
-enum ipi_message_type {
- IPI_TLB_ALL,
- IPI_TLB_MM,
- IPI_TLB_PAGE,
- IPI_RESCHEDULE,
- IPI_CPU_STOP
+
+static void
+send_ipi_message(unsigned long to_whom, enum ipi_message_type operation)
+{
+ long i, j;
+
+ /* Reduce the number of memory barriers by doing two loops,
+ one to set the bits, one to invoke the interrupts. */
+
+ mb(); /* Order out-of-band data and bit setting. */
+
+ for (i = 0, j = 1; i < NR_CPUS; ++i, j <<= 1) {
+ if (to_whom & j)
+ set_bit(operation, &ipi_data[i].bits);
+ }
+
+ mb(); /* Order bit setting and interrupt. */
+
+ for (i = 0, j = 1; i < NR_CPUS; ++i, j <<= 1) {
+ if (to_whom & j)
+ wripir(i);
+ }
+}
+
+/* Structure and data for smp_call_function. This is designed to
+ minimize static memory requirements. Plus it looks cleaner. */
+
+struct smp_call_struct {
+ void (*func) (void *info);
+ void *info;
+ long wait;
+ atomic_t unstarted_count;
+ atomic_t unfinished_count;
};
+static struct smp_call_struct *smp_call_function_data;
+
+/* Atomicly drop data into a shared pointer. The pointer is free if
+ it is initially locked. If retry, spin until free. */
+
+static inline int
+pointer_lock (void *lock, void *data, int retry)
+{
+ void *old, *tmp;
+
+ mb();
+again:
+ /* Compare and swap with zero. */
+ asm volatile (
+ "1: ldq_l %0,%1\n"
+ " mov %3,%2\n"
+ " bne %0,2f\n"
+ " stq_c %2,%1\n"
+ " beq %2,1b\n"
+ "2:"
+ : "=&r"(old), "=m"(*(void **)lock), "=&r"(tmp)
+ : "r"(data)
+ : "memory");
+
+ if (old == 0)
+ return 0;
+ if (! retry)
+ return -EBUSY;
+
+ while (*(void **)lock)
+ schedule();
+ goto again;
+}
+
void
handle_ipi(struct pt_regs *regs)
{
int this_cpu = smp_processor_id();
- volatile int * pending_ipis = &ipi_bits[this_cpu];
+ unsigned long *pending_ipis = &ipi_data[this_cpu].bits;
unsigned long ops;
DBGS(("handle_ipi: on CPU %d ops 0x%x PC 0x%lx\n",
@@ -699,190 +741,189 @@
ops &= ~which;
which = ffz(~which);
- if (which < IPI_RESCHEDULE) {
- if (which == IPI_TLB_ALL)
- tbia();
- else if (which == IPI_TLB_MM) {
- struct mm_struct * mm;
- mm = ipi_msg_flush_tb.p.flush_mm;
- if (mm == current->mm)
- flush_tlb_current(mm);
- }
- else /* IPI_TLB_PAGE */ {
- struct vm_area_struct * vma;
- struct mm_struct * mm;
- unsigned long addr;
-
- vma = ipi_msg_flush_tb.p.flush_vma;
- mm = vma->vm_mm;
- addr = ipi_msg_flush_tb.flush_addr;
-
- if (mm == current->mm)
- flush_tlb_current_page(mm, vma, addr);
- }
- clear_bit(this_cpu, &ipi_msg_flush_tb.flush_tb_mask);
- }
- else if (which == IPI_RESCHEDULE) {
+ if (which == IPI_RESCHEDULE) {
/* Reschedule callback. Everything to be done
is done by the interrupt return path. */
}
+ else if (which == IPI_CALL_FUNC) {
+ struct smp_call_struct *data;
+ void (*func)(void *info);
+ void *info;
+ int wait;
+
+ data = smp_call_function_data;
+ func = data->func;
+ info = data->info;
+ wait = data->wait;
+
+ /* Notify the sending CPU that the data has been
+ received, and execution is about to begin. */
+ mb();
+ atomic_dec (&data->unstarted_count);
+
+ /* At this point the structure may be gone unless
+ wait is true. */
+ (*func)(info);
+
+ /* Notify the sending CPU that the task is done. */
+ mb();
+ if (wait) atomic_dec (&data->unfinished_count);
+ }
else if (which == IPI_CPU_STOP) {
halt();
}
else {
- printk(KERN_CRIT "unknown_ipi() on CPU %d: %lu\n",
+ printk(KERN_CRIT "Unknown IPI on CPU %d: %lu\n",
this_cpu, which);
}
} while (ops);
+
mb(); /* Order data access and bit testing. */
}
cpu_data[this_cpu].ipi_count++;
if (hwrpb->txrdy)
- secondary_console_message();
+ recv_secondary_console_msg();
}
-static void
-send_ipi_message(unsigned long to_whom, enum ipi_message_type operation)
+void
+smp_send_reschedule(int cpu)
{
- long i, j;
-
- /* Reduce the number of memory barriers by doing two loops,
- one to set the bits, one to invoke the interrupts. */
-
- mb(); /* Order out-of-band data and bit setting. */
-
- for (i = 0, j = 1; i < NR_CPUS; ++i, j <<= 1) {
- if (to_whom & j)
- set_bit(operation, &ipi_bits[i]);
- }
-
- mb(); /* Order bit setting and interrupt. */
+ send_ipi_message(1L << cpu, IPI_RESCHEDULE);
+}
- for (i = 0, j = 1; i < NR_CPUS; ++i, j <<= 1) {
- if (to_whom & j)
- wripir(i);
- }
+void
+smp_send_stop(void)
+{
+ unsigned long to_whom = cpu_present_mask ^ (1L << smp_processor_id());
+ send_ipi_message(to_whom, IPI_CPU_STOP);
}
+/*
+ * Run a function on all other CPUs.
+ * <func> The function to run. This must be fast and non-blocking.
+ * <info> An arbitrary pointer to pass to the function.
+ * <retry> If true, keep retrying until ready.
+ * <wait> If true, wait until function has completed on other CPUs.
+ * [RETURNS] 0 on success, else a negative status code.
+ *
+ * Does not return until remote CPUs are nearly ready to execute <func>
+ * or are or have executed.
+ */
+
int
-smp_info(char *buffer)
+smp_call_function (void (*func) (void *info), void *info, int retry, int wait)
{
- long i;
- unsigned long sum = 0;
- for (i = 0; i < NR_CPUS; i++)
- sum += cpu_data[i].ipi_count;
+ unsigned long to_whom = cpu_present_mask ^ (1L << smp_processor_id());
+ struct smp_call_struct data;
+ long timeout;
+
+ data.func = func;
+ data.info = info;
+ data.wait = wait;
+ atomic_set(&data.unstarted_count, smp_num_cpus - 1);
+ atomic_set(&data.unfinished_count, smp_num_cpus - 1);
+
+ /* Aquire the smp_call_function_data mutex. */
+ if (pointer_lock(&smp_call_function_data, &data, retry))
+ return -EBUSY;
+
+ /* Send a message to all other CPUs. */
+ send_ipi_message(to_whom, IPI_CALL_FUNC);
+
+ /* Wait for a minimal response. */
+ timeout = jiffies + HZ;
+ while (atomic_read (&data.unstarted_count) > 0
+ && time_before (jiffies, timeout))
+ barrier();
- return sprintf(buffer, "CPUs probed %d active %d map 0x%x IPIs %ld\n",
- smp_num_probed, smp_num_cpus, cpu_present_map, sum);
-}
+ /* We either got one or timed out -- clear the lock. */
+ mb();
+ smp_call_function_data = 0;
+ if (atomic_read (&data.unstarted_count) > 0)
+ return -ETIMEDOUT;
+
+ /* Wait for a complete response, if needed. */
+ if (wait) {
+ while (atomic_read (&data.unfinished_count) > 0)
+ barrier();
+ }
-void
-smp_send_reschedule(int cpu)
-{
- send_ipi_message(1 << cpu, IPI_RESCHEDULE);
+ return 0;
}
-void
-smp_send_stop(void)
+static void
+ipi_flush_tlb_all(void *ignored)
{
- unsigned long to_whom = cpu_present_map ^ (1 << smp_processor_id());
- send_ipi_message(to_whom, IPI_CPU_STOP);
+ tbia();
}
void
flush_tlb_all(void)
{
- unsigned long to_whom = cpu_present_map ^ (1 << smp_processor_id());
- long timeout = 1000000;
-
- spin_lock(&flush_tb_lock);
-
- ipi_msg_flush_tb.flush_tb_mask = to_whom;
- send_ipi_message(to_whom, IPI_TLB_ALL);
tbia();
- while (ipi_msg_flush_tb.flush_tb_mask && --timeout) {
- udelay(1);
- barrier();
- }
-
- if (timeout == 0) {
- printk("flush_tlb_all: STUCK on CPU %d mask 0x%x\n",
- smp_processor_id(),
- ipi_msg_flush_tb.flush_tb_mask);
- ipi_msg_flush_tb.flush_tb_mask = 0;
+ /* Although we don't have any data to pass, we do want to
+ synchronize with the other processors. */
+ if (smp_call_function(ipi_flush_tlb_all, NULL, 1, 1)) {
+ printk(KERN_CRIT "flush_tlb_all: timed out\n");
}
+}
- spin_unlock(&flush_tb_lock);
+static void
+ipi_flush_tlb_mm(void *x)
+{
+ struct mm_struct *mm = (struct mm_struct *) x;
+ if (mm == current->mm)
+ flush_tlb_current(mm);
}
void
flush_tlb_mm(struct mm_struct *mm)
{
- unsigned long to_whom = cpu_present_map ^ (1 << smp_processor_id());
- long timeout = 1000000;
-
- spin_lock(&flush_tb_lock);
-
- ipi_msg_flush_tb.flush_tb_mask = to_whom;
- ipi_msg_flush_tb.p.flush_mm = mm;
- send_ipi_message(to_whom, IPI_TLB_MM);
-
- if (mm != current->mm)
- flush_tlb_other(mm);
- else
+ if (mm == current->mm)
flush_tlb_current(mm);
+ else
+ flush_tlb_other(mm);
- while (ipi_msg_flush_tb.flush_tb_mask && --timeout) {
- udelay(1);
- barrier();
+ if (smp_call_function(ipi_flush_tlb_mm, mm, 1, 1)) {
+ printk(KERN_CRIT "flush_tlb_mm: timed out\n");
}
+}
- if (timeout == 0) {
- printk("flush_tlb_mm: STUCK on CPU %d mask 0x%x\n",
- smp_processor_id(),
- ipi_msg_flush_tb.flush_tb_mask);
- ipi_msg_flush_tb.flush_tb_mask = 0;
- }
+struct flush_tlb_page_struct {
+ struct vm_area_struct *vma;
+ struct mm_struct *mm;
+ unsigned long addr;
+};
- spin_unlock(&flush_tb_lock);
+static void
+ipi_flush_tlb_page(void *x)
+{
+ struct flush_tlb_page_struct *data = (struct flush_tlb_page_struct *)x;
+ if (data->mm == current->mm)
+ flush_tlb_current_page(data->mm, data->vma, data->addr);
}
void
flush_tlb_page(struct vm_area_struct *vma, unsigned long addr)
{
- int cpu = smp_processor_id();
- unsigned long to_whom = cpu_present_map ^ (1 << cpu);
- struct mm_struct * mm = vma->vm_mm;
- int timeout = 1000000;
-
- spin_lock(&flush_tb_lock);
-
- ipi_msg_flush_tb.flush_tb_mask = to_whom;
- ipi_msg_flush_tb.p.flush_vma = vma;
- ipi_msg_flush_tb.flush_addr = addr;
- send_ipi_message(to_whom, IPI_TLB_PAGE);
-
- if (mm != current->mm)
- flush_tlb_other(mm);
- else
- flush_tlb_current_page(mm, vma, addr);
+ struct flush_tlb_page_struct data;
+ struct mm_struct *mm = vma->vm_mm;
- while (ipi_msg_flush_tb.flush_tb_mask && --timeout) {
- udelay(1);
- barrier();
- }
+ data.vma = vma;
+ data.mm = mm;
+ data.addr = addr;
- if (timeout == 0) {
- printk("flush_tlb_page: STUCK on CPU %d mask 0x%x\n",
- smp_processor_id(),
- ipi_msg_flush_tb.flush_tb_mask);
- ipi_msg_flush_tb.flush_tb_mask = 0;
+ if (mm == current->mm)
+ flush_tlb_current_page(mm, vma, addr);
+ else
+ flush_tlb_other(mm);
+
+ if (smp_call_function(ipi_flush_tlb_page, &data, 1, 1)) {
+ printk(KERN_CRIT "flush_tlb_page: timed out\n");
}
-
- spin_unlock(&flush_tb_lock);
}
void
@@ -892,6 +933,20 @@
flush_tlb_mm(mm);
}
+
+int
+smp_info(char *buffer)
+{
+ long i;
+ unsigned long sum = 0;
+ for (i = 0; i < NR_CPUS; i++)
+ sum += cpu_data[i].ipi_count;
+
+ return sprintf(buffer, "CPUs probed %d active %d map 0x%lx IPIs %ld\n",
+ smp_num_probed, smp_num_cpus, cpu_present_mask, sum);
+}
+
+
#if DEBUG_SPINLOCK
#ifdef MANAGE_SPINLOCK_IPL
@@ -932,17 +987,16 @@
spin_lock(spinlock_t * lock)
{
long tmp;
- long stuck = 1<<27;
+ long stuck;
void *inline_pc = __builtin_return_address(0);
unsigned long started = jiffies;
int printed = 0;
int cpu = smp_processor_id();
long old_ipl = spinlock_raise_ipl(lock);
+ stuck = 1L << 28;
try_again:
- stuck = 0x10000000; /* was 4G, now 256M */
-
/* Use sub-sections to put the actual loop at the end
of this object file's text section so as to perfect
branch prediction. */
@@ -961,19 +1015,16 @@
" blbs %0,2b\n"
" br 1b\n"
".previous"
- : "=r" (tmp),
- "=m" (__dummy_lock(lock)),
- "=r" (stuck)
- : "2" (stuck));
+ : "=r" (tmp), "=m" (__dummy_lock(lock)), "=r" (stuck)
+ : "1" (__dummy_lock(lock)), "2" (stuck));
if (stuck < 0) {
- if (!printed) {
- printk("spinlock stuck at %p(%d) owner %s at %p\n",
- inline_pc, cpu, lock->task->comm,
- lock->previous);
- printed = 1;
- }
- stuck = 1<<30;
+ printk(KERN_WARNING
+ "spinlock stuck at %p(%d) owner %s at %p(%d) st %ld\n",
+ inline_pc, cpu, lock->task->comm, lock->previous,
+ lock->task->processor, lock->task->state);
+ stuck = 1L << 36;
+ printed = 1;
goto try_again;
}
@@ -984,7 +1035,7 @@
lock->task = current;
if (printed) {
- printk("spinlock grabbed at %p(%d) %ld ticks\n",
+ printk(KERN_WARNING "spinlock grabbed at %p(%d) %ld ticks\n",
inline_pc, cpu, jiffies - started);
}
}
@@ -1006,7 +1057,7 @@
return ret;
}
#endif /* DEBUG_SPINLOCK */
-
+
#if DEBUG_RWLOCK
void write_lock(rwlock_t * lock)
{
@@ -1038,18 +1089,17 @@
" blt %1,8b\n"
" br 1b\n"
".previous"
- : "=m" (__dummy_lock(lock)), "=&r" (regx), "=&r" (regy)
- , "=&r" (stuck_lock), "=&r" (stuck_reader)
- : "0" (__dummy_lock(lock))
- , "3" (stuck_lock), "4" (stuck_reader)
- );
+ : "=m" (__dummy_lock(lock)), "=&r" (regx), "=&r" (regy),
+ "=&r" (stuck_lock), "=&r" (stuck_reader)
+ : "0" (__dummy_lock(lock)), "3" (stuck_lock), "4" (stuck_reader));
if (stuck_lock < 0) {
- printk("write_lock stuck at %p\n", inline_pc);
+ printk(KERN_WARNING "write_lock stuck at %p\n", inline_pc);
goto try_again;
}
if (stuck_reader < 0) {
- printk("write_lock stuck on readers at %p\n", inline_pc);
+ printk(KERN_WARNING "write_lock stuck on readers at %p\n",
+ inline_pc);
goto try_again;
}
}
@@ -1079,11 +1129,10 @@
" br 1b\n"
".previous"
: "=m" (__dummy_lock(lock)), "=&r" (regx), "=&r" (stuck_lock)
- : "0" (__dummy_lock(lock)), "2" (stuck_lock)
- );
+ : "0" (__dummy_lock(lock)), "2" (stuck_lock));
if (stuck_lock < 0) {
- printk("read_lock stuck at %p\n", inline_pc);
+ printk(KERN_WARNING "read_lock stuck at %p\n", inline_pc);
goto try_again;
}
}
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)