mirror of
https://github.com/torvalds/linux.git
synced 2024-11-28 07:01:32 +00:00
3463a93def
check_sal_cache_flush is used to detect broken firmware that drops pending interrupts. The old implementation schedules a timer interrupt for itself in the future by getting the current value of the Interval Timer Counter + 1000 cycles, waits for the interrupt to be pended, calls SAL_CACHE_FLUSH, and finally checks to see if the interrupt is still pending. This implementation can cause problems for virtual machine code if the process of scheduling the timer interrupt takes more than 1000 cycles; the virtual machine can end up sleeping for several hundred years while waiting for the ITC to wrap around. The fix is to use platform_send_ipi. The processor will still send an interrupt to itself, using the IA64_IPI_DM_INT delivery mode, which causes the IPI to look like an external interrupt. The rest of the SAL_CACHE_FLUSH + checking to see if the interrupt is still pending remains unchanged. This fix has been boot tested successfully on: - intel tiger2 - hp rx6600 - hp rx5670 The rx5670 has known buggy firmware, where SAL_CACHE_FLUSH drops pending interrupts. A boot test on this machine showed this message on the console: SAL: SAL_CACHE_FLUSH drops interrupts; PAL_CACHE_FLUSH will be used instead Which proves that the self-inflicted IPI approach is viable. And as expected, the other tested platforms correctly did not display the warning. Signed-off-by: Alex Chiang <achiang@hp.com> Signed-off-by: Tony Luck <tony.luck@intel.com>
406 lines
10 KiB
C
406 lines
10 KiB
C
/*
|
|
* System Abstraction Layer (SAL) interface routines.
|
|
*
|
|
* Copyright (C) 1998, 1999, 2001, 2003 Hewlett-Packard Co
|
|
* David Mosberger-Tang <davidm@hpl.hp.com>
|
|
* Copyright (C) 1999 VA Linux Systems
|
|
* Copyright (C) 1999 Walt Drummond <drummond@valinux.com>
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/string.h>
|
|
|
|
#include <asm/delay.h>
|
|
#include <asm/page.h>
|
|
#include <asm/sal.h>
|
|
#include <asm/pal.h>
|
|
|
|
__cacheline_aligned DEFINE_SPINLOCK(sal_lock);
|
|
unsigned long sal_platform_features;
|
|
|
|
unsigned short sal_revision;
|
|
unsigned short sal_version;
|
|
|
|
#define SAL_MAJOR(x) ((x) >> 8)
|
|
#define SAL_MINOR(x) ((x) & 0xff)
|
|
|
|
static struct {
|
|
void *addr; /* function entry point */
|
|
void *gpval; /* gp value to use */
|
|
} pdesc;
|
|
|
|
static long
|
|
default_handler (void)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
ia64_sal_handler ia64_sal = (ia64_sal_handler) default_handler;
|
|
ia64_sal_desc_ptc_t *ia64_ptc_domain_info;
|
|
|
|
const char *
|
|
ia64_sal_strerror (long status)
|
|
{
|
|
const char *str;
|
|
switch (status) {
|
|
case 0: str = "Call completed without error"; break;
|
|
case 1: str = "Effect a warm boot of the system to complete "
|
|
"the update"; break;
|
|
case -1: str = "Not implemented"; break;
|
|
case -2: str = "Invalid argument"; break;
|
|
case -3: str = "Call completed with error"; break;
|
|
case -4: str = "Virtual address not registered"; break;
|
|
case -5: str = "No information available"; break;
|
|
case -6: str = "Insufficient space to add the entry"; break;
|
|
case -7: str = "Invalid entry_addr value"; break;
|
|
case -8: str = "Invalid interrupt vector"; break;
|
|
case -9: str = "Requested memory not available"; break;
|
|
case -10: str = "Unable to write to the NVM device"; break;
|
|
case -11: str = "Invalid partition type specified"; break;
|
|
case -12: str = "Invalid NVM_Object id specified"; break;
|
|
case -13: str = "NVM_Object already has the maximum number "
|
|
"of partitions"; break;
|
|
case -14: str = "Insufficient space in partition for the "
|
|
"requested write sub-function"; break;
|
|
case -15: str = "Insufficient data buffer space for the "
|
|
"requested read record sub-function"; break;
|
|
case -16: str = "Scratch buffer required for the write/delete "
|
|
"sub-function"; break;
|
|
case -17: str = "Insufficient space in the NVM_Object for the "
|
|
"requested create sub-function"; break;
|
|
case -18: str = "Invalid value specified in the partition_rec "
|
|
"argument"; break;
|
|
case -19: str = "Record oriented I/O not supported for this "
|
|
"partition"; break;
|
|
case -20: str = "Bad format of record to be written or "
|
|
"required keyword variable not "
|
|
"specified"; break;
|
|
default: str = "Unknown SAL status code"; break;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
void __init
|
|
ia64_sal_handler_init (void *entry_point, void *gpval)
|
|
{
|
|
/* fill in the SAL procedure descriptor and point ia64_sal to it: */
|
|
pdesc.addr = entry_point;
|
|
pdesc.gpval = gpval;
|
|
ia64_sal = (ia64_sal_handler) &pdesc;
|
|
}
|
|
|
|
static void __init
|
|
check_versions (struct ia64_sal_systab *systab)
|
|
{
|
|
sal_revision = (systab->sal_rev_major << 8) | systab->sal_rev_minor;
|
|
sal_version = (systab->sal_b_rev_major << 8) | systab->sal_b_rev_minor;
|
|
|
|
/* Check for broken firmware */
|
|
if ((sal_revision == SAL_VERSION_CODE(49, 29))
|
|
&& (sal_version == SAL_VERSION_CODE(49, 29)))
|
|
{
|
|
/*
|
|
* Old firmware for zx2000 prototypes have this weird version number,
|
|
* reset it to something sane.
|
|
*/
|
|
sal_revision = SAL_VERSION_CODE(2, 8);
|
|
sal_version = SAL_VERSION_CODE(0, 0);
|
|
}
|
|
|
|
if (ia64_platform_is("sn2") && (sal_revision == SAL_VERSION_CODE(2, 9)))
|
|
/*
|
|
* SGI Altix has hard-coded version 2.9 in their prom
|
|
* but they actually implement 3.2, so let's fix it here.
|
|
*/
|
|
sal_revision = SAL_VERSION_CODE(3, 2);
|
|
}
|
|
|
|
static void __init
|
|
sal_desc_entry_point (void *p)
|
|
{
|
|
struct ia64_sal_desc_entry_point *ep = p;
|
|
ia64_pal_handler_init(__va(ep->pal_proc));
|
|
ia64_sal_handler_init(__va(ep->sal_proc), __va(ep->gp));
|
|
}
|
|
|
|
#ifdef CONFIG_SMP
|
|
static void __init
|
|
set_smp_redirect (int flag)
|
|
{
|
|
#ifndef CONFIG_HOTPLUG_CPU
|
|
if (no_int_routing)
|
|
smp_int_redirect &= ~flag;
|
|
else
|
|
smp_int_redirect |= flag;
|
|
#else
|
|
/*
|
|
* For CPU Hotplug we dont want to do any chipset supported
|
|
* interrupt redirection. The reason is this would require that
|
|
* All interrupts be stopped and hard bind the irq to a cpu.
|
|
* Later when the interrupt is fired we need to set the redir hint
|
|
* on again in the vector. This is cumbersome for something that the
|
|
* user mode irq balancer will solve anyways.
|
|
*/
|
|
no_int_routing=1;
|
|
smp_int_redirect &= ~flag;
|
|
#endif
|
|
}
|
|
#else
|
|
#define set_smp_redirect(flag) do { } while (0)
|
|
#endif
|
|
|
|
static void __init
|
|
sal_desc_platform_feature (void *p)
|
|
{
|
|
struct ia64_sal_desc_platform_feature *pf = p;
|
|
sal_platform_features = pf->feature_mask;
|
|
|
|
printk(KERN_INFO "SAL Platform features:");
|
|
if (!sal_platform_features) {
|
|
printk(" None\n");
|
|
return;
|
|
}
|
|
|
|
if (sal_platform_features & IA64_SAL_PLATFORM_FEATURE_BUS_LOCK)
|
|
printk(" BusLock");
|
|
if (sal_platform_features & IA64_SAL_PLATFORM_FEATURE_IRQ_REDIR_HINT) {
|
|
printk(" IRQ_Redirection");
|
|
set_smp_redirect(SMP_IRQ_REDIRECTION);
|
|
}
|
|
if (sal_platform_features & IA64_SAL_PLATFORM_FEATURE_IPI_REDIR_HINT) {
|
|
printk(" IPI_Redirection");
|
|
set_smp_redirect(SMP_IPI_REDIRECTION);
|
|
}
|
|
if (sal_platform_features & IA64_SAL_PLATFORM_FEATURE_ITC_DRIFT)
|
|
printk(" ITC_Drift");
|
|
printk("\n");
|
|
}
|
|
|
|
#ifdef CONFIG_SMP
|
|
static void __init
|
|
sal_desc_ap_wakeup (void *p)
|
|
{
|
|
struct ia64_sal_desc_ap_wakeup *ap = p;
|
|
|
|
switch (ap->mechanism) {
|
|
case IA64_SAL_AP_EXTERNAL_INT:
|
|
ap_wakeup_vector = ap->vector;
|
|
printk(KERN_INFO "SAL: AP wakeup using external interrupt "
|
|
"vector 0x%lx\n", ap_wakeup_vector);
|
|
break;
|
|
default:
|
|
printk(KERN_ERR "SAL: AP wakeup mechanism unsupported!\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void __init
|
|
chk_nointroute_opt(void)
|
|
{
|
|
char *cp;
|
|
|
|
for (cp = boot_command_line; *cp; ) {
|
|
if (memcmp(cp, "nointroute", 10) == 0) {
|
|
no_int_routing = 1;
|
|
printk ("no_int_routing on\n");
|
|
break;
|
|
} else {
|
|
while (*cp != ' ' && *cp)
|
|
++cp;
|
|
while (*cp == ' ')
|
|
++cp;
|
|
}
|
|
}
|
|
}
|
|
|
|
#else
|
|
static void __init sal_desc_ap_wakeup(void *p) { }
|
|
#endif
|
|
|
|
/*
|
|
* HP rx5670 firmware polls for interrupts during SAL_CACHE_FLUSH by reading
|
|
* cr.ivr, but it never writes cr.eoi. This leaves any interrupt marked as
|
|
* "in-service" and masks other interrupts of equal or lower priority.
|
|
*
|
|
* HP internal defect reports: F1859, F2775, F3031.
|
|
*/
|
|
static int sal_cache_flush_drops_interrupts;
|
|
|
|
static int __init
|
|
force_pal_cache_flush(char *str)
|
|
{
|
|
sal_cache_flush_drops_interrupts = 1;
|
|
return 0;
|
|
}
|
|
early_param("force_pal_cache_flush", force_pal_cache_flush);
|
|
|
|
void __init
|
|
check_sal_cache_flush (void)
|
|
{
|
|
unsigned long flags;
|
|
int cpu;
|
|
u64 vector, cache_type = 3;
|
|
struct ia64_sal_retval isrv;
|
|
|
|
if (sal_cache_flush_drops_interrupts)
|
|
return;
|
|
|
|
cpu = get_cpu();
|
|
local_irq_save(flags);
|
|
|
|
/*
|
|
* Send ourselves a timer interrupt, wait until it's reported, and see
|
|
* if SAL_CACHE_FLUSH drops it.
|
|
*/
|
|
platform_send_ipi(cpu, IA64_TIMER_VECTOR, IA64_IPI_DM_INT, 0);
|
|
|
|
while (!ia64_get_irr(IA64_TIMER_VECTOR))
|
|
cpu_relax();
|
|
|
|
SAL_CALL(isrv, SAL_CACHE_FLUSH, cache_type, 0, 0, 0, 0, 0, 0);
|
|
|
|
if (isrv.status)
|
|
printk(KERN_ERR "SAL_CAL_FLUSH failed with %ld\n", isrv.status);
|
|
|
|
if (ia64_get_irr(IA64_TIMER_VECTOR)) {
|
|
vector = ia64_get_ivr();
|
|
ia64_eoi();
|
|
WARN_ON(vector != IA64_TIMER_VECTOR);
|
|
} else {
|
|
sal_cache_flush_drops_interrupts = 1;
|
|
printk(KERN_ERR "SAL: SAL_CACHE_FLUSH drops interrupts; "
|
|
"PAL_CACHE_FLUSH will be used instead\n");
|
|
ia64_eoi();
|
|
}
|
|
|
|
local_irq_restore(flags);
|
|
put_cpu();
|
|
}
|
|
|
|
s64
|
|
ia64_sal_cache_flush (u64 cache_type)
|
|
{
|
|
struct ia64_sal_retval isrv;
|
|
|
|
if (sal_cache_flush_drops_interrupts) {
|
|
unsigned long flags;
|
|
u64 progress;
|
|
s64 rc;
|
|
|
|
progress = 0;
|
|
local_irq_save(flags);
|
|
rc = ia64_pal_cache_flush(cache_type,
|
|
PAL_CACHE_FLUSH_INVALIDATE, &progress, NULL);
|
|
local_irq_restore(flags);
|
|
return rc;
|
|
}
|
|
|
|
SAL_CALL(isrv, SAL_CACHE_FLUSH, cache_type, 0, 0, 0, 0, 0, 0);
|
|
return isrv.status;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ia64_sal_cache_flush);
|
|
|
|
void __init
|
|
ia64_sal_init (struct ia64_sal_systab *systab)
|
|
{
|
|
char *p;
|
|
int i;
|
|
|
|
if (!systab) {
|
|
printk(KERN_WARNING "Hmm, no SAL System Table.\n");
|
|
return;
|
|
}
|
|
|
|
if (strncmp(systab->signature, "SST_", 4) != 0)
|
|
printk(KERN_ERR "bad signature in system table!");
|
|
|
|
check_versions(systab);
|
|
#ifdef CONFIG_SMP
|
|
chk_nointroute_opt();
|
|
#endif
|
|
|
|
/* revisions are coded in BCD, so %x does the job for us */
|
|
printk(KERN_INFO "SAL %x.%x: %.32s %.32s%sversion %x.%x\n",
|
|
SAL_MAJOR(sal_revision), SAL_MINOR(sal_revision),
|
|
systab->oem_id, systab->product_id,
|
|
systab->product_id[0] ? " " : "",
|
|
SAL_MAJOR(sal_version), SAL_MINOR(sal_version));
|
|
|
|
p = (char *) (systab + 1);
|
|
for (i = 0; i < systab->entry_count; i++) {
|
|
/*
|
|
* The first byte of each entry type contains the type
|
|
* descriptor.
|
|
*/
|
|
switch (*p) {
|
|
case SAL_DESC_ENTRY_POINT:
|
|
sal_desc_entry_point(p);
|
|
break;
|
|
case SAL_DESC_PLATFORM_FEATURE:
|
|
sal_desc_platform_feature(p);
|
|
break;
|
|
case SAL_DESC_PTC:
|
|
ia64_ptc_domain_info = (ia64_sal_desc_ptc_t *)p;
|
|
break;
|
|
case SAL_DESC_AP_WAKEUP:
|
|
sal_desc_ap_wakeup(p);
|
|
break;
|
|
}
|
|
p += SAL_DESC_SIZE(*p);
|
|
}
|
|
|
|
}
|
|
|
|
int
|
|
ia64_sal_oemcall(struct ia64_sal_retval *isrvp, u64 oemfunc, u64 arg1,
|
|
u64 arg2, u64 arg3, u64 arg4, u64 arg5, u64 arg6, u64 arg7)
|
|
{
|
|
if (oemfunc < IA64_SAL_OEMFUNC_MIN || oemfunc > IA64_SAL_OEMFUNC_MAX)
|
|
return -1;
|
|
SAL_CALL(*isrvp, oemfunc, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(ia64_sal_oemcall);
|
|
|
|
int
|
|
ia64_sal_oemcall_nolock(struct ia64_sal_retval *isrvp, u64 oemfunc, u64 arg1,
|
|
u64 arg2, u64 arg3, u64 arg4, u64 arg5, u64 arg6,
|
|
u64 arg7)
|
|
{
|
|
if (oemfunc < IA64_SAL_OEMFUNC_MIN || oemfunc > IA64_SAL_OEMFUNC_MAX)
|
|
return -1;
|
|
SAL_CALL_NOLOCK(*isrvp, oemfunc, arg1, arg2, arg3, arg4, arg5, arg6,
|
|
arg7);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(ia64_sal_oemcall_nolock);
|
|
|
|
int
|
|
ia64_sal_oemcall_reentrant(struct ia64_sal_retval *isrvp, u64 oemfunc,
|
|
u64 arg1, u64 arg2, u64 arg3, u64 arg4, u64 arg5,
|
|
u64 arg6, u64 arg7)
|
|
{
|
|
if (oemfunc < IA64_SAL_OEMFUNC_MIN || oemfunc > IA64_SAL_OEMFUNC_MAX)
|
|
return -1;
|
|
SAL_CALL_REENTRANT(*isrvp, oemfunc, arg1, arg2, arg3, arg4, arg5, arg6,
|
|
arg7);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(ia64_sal_oemcall_reentrant);
|
|
|
|
long
|
|
ia64_sal_freq_base (unsigned long which, unsigned long *ticks_per_second,
|
|
unsigned long *drift_info)
|
|
{
|
|
struct ia64_sal_retval isrv;
|
|
|
|
SAL_CALL(isrv, SAL_FREQ_BASE, which, 0, 0, 0, 0, 0, 0);
|
|
*ticks_per_second = isrv.v0;
|
|
*drift_info = isrv.v1;
|
|
return isrv.status;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ia64_sal_freq_base);
|