| /* |
| * 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 */ |
| } |