blob: b78ef691a142e00782cb569d66fb95f37200b767 [file] [log] [blame]
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) Hewlett-Packard (Paul Bame) paul_bame@hp.com
*/
#include <stdarg.h>
#include <stddef.h>
#include <asm/pdc.h>
#include "bootloader.h"
#undef PAGE0
#define PAGE0 ((struct zeropage *)0x00000000)
#define DEFAULT_W 0x2
#define DEFAULT_E 0x1
#define PDC_PSW 21
#define PDC_RETURN_MASK 0
#define PDC_RETURN_DEFAULTS 1
#define PDC_SET_DEFAULTS 2
#define PDC_STABLE 10
#define HPHW_A_DIRECT 5
#define MUX_SVERSION 0x0d
void die(const char *s)
{
puts(s);
puts("\n");
}
static int firmware_is_wide;
static long long mem_pdc;
void
firmware_init(int started_wide)
{
mem_pdc = PAGE0->mem_pdc;
mem_pdc |= (unsigned long long) PAGE0->mem_pdc_hi << 32;
firmware_is_wide = started_wide;
}
/* pdc_result[] is big enough for either narrow or wide calls */
static unsigned pdc_result[64] __attribute__ ((aligned (8)));
static char iodc_string[512] __attribute__ ((aligned (64)));
struct wide_stack {
unsigned long long arg0;
unsigned long long arg1;
unsigned long long arg2;
unsigned long long arg3;
unsigned long long arg4;
unsigned long long arg5;
unsigned long long arg6;
unsigned long long arg7;
unsigned long long arg8;
unsigned long long arg9;
unsigned long long arg10;
unsigned long long arg11;
unsigned long long arg12;
unsigned long long arg13;
unsigned long long frame_marker[2]; /* rp, previous sp */
unsigned long long sp;
/* in reality, there's nearly 8k of stack after this */
};
static int
firmware_call(unsigned long long fn, ...)
{
va_list args;
int r;
if (firmware_is_wide)
{
extern struct wide_stack real_stack;
extern unsigned int real64_call_asm(unsigned long long *,
unsigned long long *,
unsigned long long);
va_start(args, fn);
real_stack.arg0 = va_arg(args, unsigned long);
real_stack.arg1 = va_arg(args, unsigned long);
real_stack.arg2 = va_arg(args, unsigned long);
real_stack.arg3 = va_arg(args, unsigned long);
real_stack.arg4 = va_arg(args, unsigned long);
real_stack.arg5 = va_arg(args, unsigned long);
real_stack.arg6 = va_arg(args, unsigned long);
real_stack.arg7 = va_arg(args, unsigned long);
real_stack.arg8 = va_arg(args, unsigned long);
real_stack.arg9 = va_arg(args, unsigned long);
real_stack.arg10 = va_arg(args, unsigned long);
real_stack.arg11 = va_arg(args, unsigned long);
real_stack.arg12 = va_arg(args, unsigned long);
real_stack.arg13 = va_arg(args, unsigned long);
va_end(args);
r = real64_call_asm(&real_stack.sp, &real_stack.arg0, fn);
}
else
{
typedef int (*firmware_entry)();
unsigned long arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
arg9, arg10, arg11, arg12, arg13;
va_start(args, fn);
arg0 = va_arg(args, unsigned long);
arg1 = va_arg(args, unsigned long);
arg2 = va_arg(args, unsigned long);
arg3 = va_arg(args, unsigned long);
arg4 = va_arg(args, unsigned long);
arg5 = va_arg(args, unsigned long);
arg6 = va_arg(args, unsigned long);
arg7 = va_arg(args, unsigned long);
arg8 = va_arg(args, unsigned long);
arg9 = va_arg(args, unsigned long);
arg10 = va_arg(args, unsigned long);
arg11 = va_arg(args, unsigned long);
arg12 = va_arg(args, unsigned long);
arg13 = va_arg(args, unsigned long);
va_end(args);
r = (*(firmware_entry) (unsigned int) fn) (arg0, arg1, arg2, arg3, arg4,
arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13);
}
return r;
}
void
convert_from_wide(unsigned *retbuf)
{
if (firmware_is_wide)
{
*retbuf = *(unsigned long long *)retbuf;
}
}
/* flag=true means enable default wide mode.
*/
int
pdc_default_width(int wide)
{
int r;
int mask;
/* Ask firmware which default PSW bits we're allowed to set */
r = firmware_call(mem_pdc, PDC_PSW, PDC_RETURN_MASK, pdc_result);
convert_from_wide(pdc_result);
switch(r)
{
case 0: /* PDC call worked */
mask = pdc_result[0];
if (wide && ((mask & DEFAULT_W) == 0))
{
die("Firmware does not allow selection of default wide mode.\n"
"Are you trying to boot a 64-bit kernel on a 32-bit box?");
return 1;
}
/* get the current default bit settings */
firmware_call(mem_pdc, PDC_PSW, PDC_RETURN_DEFAULTS, pdc_result);
convert_from_wide(pdc_result);
if (wide)
{
if ((mask & DEFAULT_W) == 0) {
die("Firmware does not allow selection of default wide mode.\n"
"Are you trying to boot a 64-bit kernel on a 32-bit box?");
return 1;
}
pdc_result[0] |= DEFAULT_W;
}
else
{
pdc_result[0] &= ~DEFAULT_W;
}
pdc_result[0] &= ~DEFAULT_E;
/* Ask firmware to set the W bit appropriately */
r = firmware_call(mem_pdc, PDC_PSW, PDC_SET_DEFAULTS, pdc_result[0]);
if (r < 0)
{
printf("PDC_SET_DEFAULTS returns error %d\n", r);
die("Requested default wide/narrow mode not set");
return 1;
}
if (0) printf("Set default PSW W bit to %d\n", wide);
break;
case -2: /* unsupported PDC call */
default:
/* assume that when this fails, it's an older machine which */
/* doesn't support wide mode */
if (wide)
{
die("Can't select default wide mode, PDC_PSW call does not work");
return 1;
}
else /* narrow */
{
if (0) printf("Warning: narrow mode requested, PDC_PSW call fails\n");
}
break;
}
return 0;
}
int
pdc_os_bits()
{
int r;
int osbits = 0x2; /* default to 32-bit OS */
r = firmware_call(mem_pdc, PDC_MODEL, PDC_MODEL_CAPABILITIES, pdc_result);
convert_from_wide(pdc_result);
if (r < 0)
{
if (0) printf("Annoyance: Firmware does not support PDC_MODEL_CAPABILITIES call\n");
}
else
{
osbits = pdc_result[0];
/* printf("Firmware OS bits = %d\n", osbits); */
}
return osbits;
}
int
pdc_cons_duplex()
{
return (PAGE0->mem_cons.cl_class == CL_DUPLEX);
}
int
pdc_cons_mux(int *is_mux)
{
int r;
unsigned char hw_type; /* 5 bits used */
unsigned int sversion; /* 20 bits used */
unsigned long pdc_result2[32] __attribute__ ((aligned (8)));
*is_mux = 0;
r = firmware_call(mem_pdc, PDC_IODC, PDC_IODC_READ,
pdc_result, PAGE0->mem_cons.hpa,
0, pdc_result2, 32);
if (r >= 0)
{
unsigned char iodc_data[8];
memcpy(&iodc_data, pdc_result2, 8);
hw_type = iodc_data[3] & 0x1f;
sversion = ((iodc_data[4] & 0x0f) << 16) | (iodc_data[5] << 8) | iodc_data[6];
if(hw_type == HPHW_A_DIRECT && sversion == MUX_SVERSION)
*is_mux = 1;
return PDC_OK;
}
return r; /* r < 0; error */
}
int
pdc_iodc_cin(char *buf, int size)
{
int r;
struct pz_device *in = pdc_cons_duplex() ?
&PAGE0->mem_cons : &PAGE0->mem_kbd;
if (size >= sizeof iodc_string)
asm("\npdc_iodc_cin_test1fail: b,n .");
r = firmware_call(in->iodc_io,
in->hpa, ENTRY_IO_CIN,
in->spa, in->dp.layers,
pdc_result, 0, iodc_string, size, 0);
if (r >= 0)
{
convert_from_wide(pdc_result);
memcpy(buf, iodc_string, pdc_result[0]);
return pdc_result[0]; /* count */
}
return r; /* r < 0; error */
}
void
pdc_iodc_cout(const char *s, int size)
{
int r;
if (s[0] == 0)
/* this test is usually the one to catch overwriting palo with kernel */
asm("\npdc_iodc_cout_test1fail: b,n .");
if (size >= sizeof iodc_string)
asm("\npdc_iodc_cout_test2fail: b,n .");
memcpy(iodc_string, s, size);
if (s[0] != iodc_string[0] || s[0] == 0)
asm("\npdc_iodc_cout_test3fail: b,n .");
r = firmware_call(PAGE0->mem_cons.iodc_io,
PAGE0->mem_cons.hpa, ENTRY_IO_COUT,
PAGE0->mem_cons.spa, PAGE0->mem_cons.dp.layers,
pdc_result, 0, iodc_string, size, 0);
if (r != 0)
asm("\npdc_iodc_cout_test4fail: b,n .");
}
int
pdc_iodc_bootin(unsigned devaddr, char *memaddr, unsigned size)
{
int r;
r = firmware_call(PAGE0->mem_boot.iodc_io,
PAGE0->mem_boot.hpa, ENTRY_IO_BOOTIN,
PAGE0->mem_boot.spa, PAGE0->mem_boot.dp.layers,
pdc_result, devaddr, memaddr, size, size);
if (r == 3) /* EOF */
{
return 0; /* count = 0 at EOF */
}
else if (r >= 0)
{
convert_from_wide(pdc_result);
return pdc_result[0]; /* count */
}
return r; /* r < 0; error */
}
int
pdc_read_conspath(unsigned char *memaddr)
{
int r;
r = firmware_call(mem_pdc, PDC_STABLE, 0, 96, memaddr, 8);
if (r >= 0)
{
convert_from_wide(pdc_result);
return pdc_result[0]; /* count */
}
return r; /* r < 0; error */
}