/*
* misc.c
*
* $Id: misc.c,v 1.64.2.4 1999/07/22 03:28:03 cort Exp $
*
* Adapted for PowerPC by Gary Thomas
*
* Rewritten by Cort Dougan (cort@cs.nmt.edu)
* One day to be replaced by a single bootloader for chrp/prep/pmac. -- Cort
*/
#include <linux/types.h>
#include "../coffboot/zlib.h"
#include "asm/residual.h"
#include <linux/elf.h>
#include <linux/config.h>
#include <asm/page.h>
#include <asm/processor.h>
#include <asm/mmu.h>
#if defined(CONFIG_SERIAL_CONSOLE)
#include "ns16550.h"
struct NS16550 *com_port;
#endif /* CONFIG_SERIAL_CONSOLE */
/*
* Please send me load/board info and such data for hardware not
* listed here so I can keep track since things are getting tricky
* with the different load addrs with different firmware. This will
* help to avoid breaking the load/boot process.
* -- Cort
*/
char *avail_ram;
char *end_avail;
extern char _end[];
#ifdef CONFIG_CMDLINE
#define CMDLINE CONFIG_CMDLINE
#else
#define CMDLINE "";
#endif
char cmd_preset[] = CMDLINE;
char cmd_buf[256];
char *cmd_line = cmd_buf;
int keyb_present = 1; /* keyboard controller is present by default */
RESIDUAL hold_resid_buf;
RESIDUAL *hold_residual = &hold_resid_buf;
unsigned long initrd_start = 0, initrd_end = 0;
char *zimage_start;
int zimage_size;
char *vidmem = (char *)0xC00B8000;
int lines, cols;
int orig_x, orig_y;
void puts(const char *);
void putc(const char c);
void puthex(unsigned long val);
void _bcopy(char *src, char *dst, int len);
void * memcpy(void * __dest, __const void * __src,
int __n);
void gunzip(void *, int, unsigned char *, int *);
static int _cvt(unsigned long val, char *buf, long radix, char *digits);
unsigned char inb(int);
void pause()
{
puts("pause\n");
}
void exit()
{
puts("exit\n");
while(1);
}
static void clear_screen()
{
int i, j;
for (i = 0; i < lines; i++) {
for (j = 0; j < cols; j++) {
vidmem[((i*cols)+j)*2] = ' ';
vidmem[((i*cols)+j)*2+1] = 0x07;
}
}
}
static void scroll()
{
int i;
memcpy ( vidmem, vidmem + cols * 2, ( lines - 1 ) * cols * 2 );
for ( i = ( lines - 1 ) * cols * 2; i < lines * cols * 2; i += 2 )
vidmem[i] = ' ';
}
tstc(void)
{
#if defined(CONFIG_SERIAL_CONSOLE)
if (keyb_present)
return (CRT_tstc() || NS16550_tstc(com_port));
else
NS16550_tstc(com_port);
#else
return (CRT_tstc() );
#endif /* CONFIG_SERIAL_CONSOLE */
}
getc(void)
{
while (1) {
#if defined(CONFIG_SERIAL_CONSOLE)
if (NS16550_tstc(com_port)) return (NS16550_getc(com_port));
#endif /* CONFIG_SERIAL_CONSOLE */
if (keyb_present)
if (CRT_tstc()) return (CRT_getc());
}
}
void
putc(const char c)
{
int x,y;
#if defined(CONFIG_SERIAL_CONSOLE)
NS16550_putc(com_port, c);
if ( c == '\n' ) NS16550_putc(com_port, '\r');
#endif /* CONFIG_SERIAL_CONSOLE */
x = orig_x;
y = orig_y;
if ( c == '\n' ) {
x = 0;
if ( ++y >= lines ) {
scroll();
y--;
}
} else if (c == '\r') {
x = 0;
} else if (c == '\b') {
if (x > 0) {
x--;
}
} else {
vidmem [ ( x + cols * y ) * 2 ] = c;
if ( ++x >= cols ) {
x = 0;
if ( ++y >= lines ) {
scroll();
y--;
}
}
}
cursor(x, y);
orig_x = x;
orig_y = y;
}
void puts(const char *s)
{
int x,y;
char c;
x = orig_x;
y = orig_y;
while ( ( c = *s++ ) != '\0' ) {
#if defined(CONFIG_SERIAL_CONSOLE)
NS16550_putc(com_port, c);
if ( c == '\n' ) NS16550_putc(com_port, '\r');
#endif /* CONFIG_SERIAL_CONSOLE */
if ( c == '\n' ) {
x = 0;
if ( ++y >= lines ) {
scroll();
y--;
}
} else if (c == '\b') {
if (x > 0) {
x--;
}
} else {
vidmem [ ( x + cols * y ) * 2 ] = c;
if ( ++x >= cols ) {
x = 0;
if ( ++y >= lines ) {
scroll();
y--;
}
}
}
}
cursor(x, y);
orig_x = x;
orig_y = y;
}
void * memcpy(void * __dest, __const void * __src,
int __n)
{
int i;
char *d = (char *)__dest, *s = (char *)__src;
for (i=0;i<__n;i++) d[i] = s[i];
}
int memcmp(__const void * __dest, __const void * __src,
int __n)
{
int i;
char *d = (char *)__dest, *s = (char *)__src;
for (i=0;i<__n;i++, d++, s++)
{
if (*d != *s)
{
return (*s - *d);
}
}
return (0);
}
void error(char *x)
{
puts("\n\n");
puts(x);
puts("\n\n -- System halted");
while(1); /* Halt */
}
void *zalloc(void *x, unsigned items, unsigned size)
{
void *p = avail_ram;
size *= items;
size = (size + 7) & -8;
avail_ram += size;
if (avail_ram > end_avail) {
puts("oops... out of memory\n");
pause();
}
return p;
}
void zfree(void *x, void *addr, unsigned nb)
{
}
#define HEAD_CRC 2
#define EXTRA_FIELD 4
#define ORIG_NAME 8
#define COMMENT 0x10
#define RESERVED 0xe0
#define DEFLATED 8
void gunzip(void *dst, int dstlen, unsigned char *src, int *lenp)
{
z_stream s;
int r, i, flags;
/* skip header */
i = 10;
flags = src[3];
if (src[2] != DEFLATED || (flags & RESERVED) != 0) {
puts("bad gzipped data\n");
exit();
}
if ((flags & EXTRA_FIELD) != 0)
i = 12 + src[10] + (src[11] << 8);
if ((flags & ORIG_NAME) != 0)
while (src[i++] != 0)
;
if ((flags & COMMENT) != 0)
while (src[i++] != 0)
;
if ((flags & HEAD_CRC) != 0)
i += 2;
if (i >= *lenp) {
puts("gunzip: ran out of data in header\n");
exit();
}
s.zalloc = zalloc;
s.zfree = zfree;
r = inflateInit2(&s, -MAX_WBITS);
if (r != Z_OK) {
puts("inflateInit2 returned %d\n");
exit();
}
s.next_in = src + i;
s.avail_in = *lenp - i;
s.next_out = dst;
s.avail_out = dstlen;
r = inflate(&s, Z_FINISH);
if (r != Z_OK && r != Z_STREAM_END) {
puts("inflate returned %d\n");
exit();
}
*lenp = s.next_out - (unsigned char *) dst;
inflateEnd(&s);
}
unsigned char sanity[0x2000];
/*
* This routine is used to control the second processor on the
* Motorola dual processor platforms.
*/
void
park_cpus()
{
#ifdef __SMP__
volatile void (*go)(RESIDUAL *, int, int, char *, int);
unsigned int i;
volatile unsigned long *smp_iar = &(hold_residual->VitalProductData.SmpIar);
/* Wait for indication to continue. If the kernel
was not compiled with SMP support then the second
processor will spin forever here makeing the kernel
multiprocessor safe. */
while (*smp_iar == 0) {
for (i=0; i < 512; i++);
}
(unsigned long)go = hold_residual->VitalProductData.SmpIar;
go(hold_residual, 0, 0, cmd_line, sizeof(cmd_preset));
#else
while(1);
#endif
}
unsigned long
decompress_kernel(unsigned long load_addr, int num_words, unsigned long cksum,
RESIDUAL *residual, void *OFW_interface)
{
int timer;
extern unsigned long start;
char *cp, ch;
unsigned long i;
BATU *u;
BATL *l;
unsigned long TotalMemory;
unsigned long orig_MSR;
int dev_handle;
int mem_info[2];
int res, size;
unsigned char board_type;
unsigned char base_mod;
int start_multi = 0;
lines = 25;
cols = 80;
orig_x = 0;
orig_y = 24;
/*
* IBM's have the MMU on, so we have to disable it or
* things get really unhappy in the kernel when
* trying to setup the BATs with the MMU on
* -- Cort
*/
flush_instruction_cache();
_put_HID0(_get_HID0() & ~0x0000C000);
_put_MSR((orig_MSR = _get_MSR()) & ~0x0030);
#if defined(CONFIG_SERIAL_CONSOLE)
com_port = (struct NS16550 *)NS16550_init(0);
#endif /* CONFIG_SERIAL_CONSOLE */
vga_init(0xC0000000);
if (residual)
{
/* Is this Motorola PPCBug? */
if ((1 & residual->VitalProductData.FirmwareSupports) &&
(1 == residual->VitalProductData.FirmwareSupplier)) {
board_type = inb(0x800) & 0xF0;
/* If this is genesis 2 board then check for no
* keyboard controller and more than one processor.
*/
if (board_type == 0xe0) {
base_mod = inb(0x803);
/* if a MVME2300/2400 or a Sitka then no keyboard */
if((base_mod == 0xFA) || (base_mod == 0xF9) ||
(base_mod == 0xE1)) {
keyb_present = 0; /* no keyboard */
}
}
/* If this is a multiprocessor system then
* park the other processor so that the
* kernel knows where to find them.
*/
if (residual->MaxNumCpus > 1) {
start_multi = 1;
}
}
memcpy(hold_residual,residual,sizeof(RESIDUAL));
} else {
/* Assume 32M in the absence of more info... */
TotalMemory = 0x02000000;
/*
* This is a 'best guess' check. We want to make sure
* we don't try this on a PReP box without OF
* -- Cort
*/
while (OFW_interface && ((unsigned long)OFW_interface < 0x10000000) )
{
/* The MMU needs to be on when we call OFW */
_put_MSR(orig_MSR);
of_init(OFW_interface);
/* get handle to memory description */
res = of_finddevice("/memory@0",
&dev_handle);
// puthex(res); puts("\n");
if (res) break;
/* get the info */
// puts("get info = ");
res = of_getprop(dev_handle,
"reg",
mem_info,
sizeof(mem_info),
&size);
// puthex(res); puts(", info = "); puthex(mem_info[0]);
// puts(" "); puthex(mem_info[1]); puts("\n");
if (res) break;
TotalMemory = mem_info[1];
break;
}
hold_residual->TotalMemory = TotalMemory;
residual = hold_residual;
/* Turn MMU back off */
_put_MSR(orig_MSR & ~0x0030);
}
if (start_multi) {
hold_residual->VitalProductData.SmpIar = 0;
hold_residual->Cpus[1].CpuState = CPU_GOOD_FW;
residual->VitalProductData.SmpIar = (unsigned long)park_cpus;
residual->Cpus[1].CpuState = CPU_GOOD;
hold_residual->VitalProductData.Reserved5 = 0xdeadbeef;
}
/* assume the chunk below 8M is free */
end_avail = (char *)0x00800000;
/* tell the user where we were loaded at and where we
* were relocated to for debugging this process
*/
puts("loaded at: "); puthex(load_addr);
puts(" "); puthex((unsigned long)(load_addr + (4*num_words))); puts("\n");
if ( (unsigned long)load_addr != (unsigned long)&start )
{
puts("relocated to: "); puthex((unsigned long)&start);
puts(" ");
puthex((unsigned long)((unsigned long)&start + (4*num_words)));
puts("\n");
}
if ( residual )
{
puts("board data at: "); puthex((unsigned long)residual);
puts(" ");
puthex((unsigned long)((unsigned long)residual + sizeof(RESIDUAL)));
puts("\n");
puts("relocated to: ");
puthex((unsigned long)hold_residual);
puts(" ");
puthex((unsigned long)((unsigned long)hold_residual + sizeof(RESIDUAL)));
puts("\n");
}
/* we have to subtract 0x10000 here to correct for objdump including the
size of the elf header which we strip -- Cort */
zimage_start = (char *)(load_addr - 0x10000 + ZIMAGE_OFFSET);
zimage_size = ZIMAGE_SIZE;
if ( INITRD_OFFSET )
initrd_start = load_addr - 0x10000 + INITRD_OFFSET;
else
initrd_start = 0;
initrd_end = INITRD_SIZE + initrd_start;
/*
* Find a place to stick the zimage and initrd and
* relocate them if we have to. -- Cort
*/
avail_ram = (char *)PAGE_ALIGN((unsigned long)_end);
puts("zimage at: "); puthex((unsigned long)zimage_start);
puts(" "); puthex((unsigned long)(zimage_size+zimage_start)); puts("\n");
if ( (unsigned long)zimage_start <= 0x00800000 )
{
memcpy( (void *)avail_ram, (void *)zimage_start, zimage_size );
zimage_start = (char *)avail_ram;
puts("relocated to: "); puthex((unsigned long)zimage_start);
puts(" ");
puthex((unsigned long)zimage_size+(unsigned long)zimage_start);
puts("\n");
/* relocate initrd */
if ( initrd_start )
{
puts("initrd at: "); puthex(initrd_start);
puts(" "); puthex(initrd_end); puts("\n");
avail_ram = (char *)PAGE_ALIGN(
(unsigned long)zimage_size+(unsigned long)zimage_start);
memcpy ((void *)avail_ram, (void *)initrd_start, INITRD_SIZE );
initrd_start = (unsigned long)avail_ram;
initrd_end = initrd_start + INITRD_SIZE;
puts("relocated to: "); puthex(initrd_start);
puts(" "); puthex(initrd_end); puts("\n");
}
} else if ( initrd_start ) {
puts("initrd at: "); puthex(initrd_start);
puts(" "); puthex(initrd_end); puts("\n");
}
avail_ram = (char *)0x00400000;
end_avail = (char *)0x00800000;
puts("avail ram: "); puthex((unsigned long)avail_ram); puts(" ");
puthex((unsigned long)end_avail); puts("\n");
if (keyb_present)
CRT_tstc(); /* Forces keyboard to be initialized */
puts("\nLinux/PPC load: ");
timer = 0;
cp = cmd_line;
memcpy (cmd_line, cmd_preset, sizeof(cmd_preset));
while ( *cp ) putc(*cp++);
while (timer++ < 5*1000) {
if (tstc()) {
while ((ch = getc()) != '\n' && ch != '\r') {
if (ch == '\b') {
if (cp != cmd_line) {
cp--;
puts("\b \b");
}
} else {
*cp++ = ch;
putc(ch);
}
}
break; /* Exit 'timer' loop */
}
udelay(1000); /* 1 msec */
}
*cp = 0;
puts("\n");
/* mappings on early boot can only handle 16M */
if ( (int)(cmd_line[0]) > (16<<20))
puts("cmd_line located > 16M\n");
if ( (int)hold_residual > (16<<20))
puts("hold_residual located > 16M\n");
if ( initrd_start > (16<<20))
puts("initrd_start located > 16M\n");
puts("Uncompressing Linux...");
gunzip(0, 0x400000, zimage_start, &zimage_size);
puts("done.\n");
puts("Now booting the kernel\n");
return (unsigned long)hold_residual;
}
void puthex(unsigned long val)
{
unsigned char buf[10];
int i;
for (i = 7; i >= 0; i--)
{
buf[i] = "0123456789ABCDEF"[val & 0x0F];
val >>= 4;
}
buf[8] = '\0';
puts(buf);
}
/*
* PCI/ISA I/O support
*/
volatile unsigned char *ISA_io = (unsigned char *)0x80000000;
volatile unsigned char *ISA_mem = (unsigned char *)0xC0000000;
void
outb(int port, char val)
{
/* Ensure I/O operations complete */
__asm__ volatile("eieio");
ISA_io[port] = val;
}
unsigned char
inb(int port)
{
/* Ensure I/O operations complete */
__asm__ volatile("eieio");
return (ISA_io[port]);
}
unsigned long
local_to_PCI(unsigned long addr)
{
return ((addr & 0x7FFFFFFF) | 0x80000000);
}
void
_bcopy(char *src, char *dst, int len)
{
while (len--) *dst++ = *src++;
}
#define FALSE 0
#define TRUE 1
#include <stdarg.h>
int
strlen(char *s)
{
int len = 0;
while (*s++) len++;
return len;
}
_printk(char const *fmt, ...)
{
int ret;
va_list ap;
va_start(ap, fmt);
ret = _vprintk(putc, fmt, ap);
va_end(ap);
return (ret);
}
#define is_digit(c) ((c >= '0') && (c <= '9'))
int
_vprintk(putc, fmt0, ap)
int (*putc)();
const char *fmt0;
va_list ap;
{
char c, sign, *cp;
int left_prec, right_prec, zero_fill, length, pad, pad_on_right;
char buf[32];
long val;
while (c = *fmt0++)
{
if (c == '%')
{
c = *fmt0++;
left_prec = right_prec = pad_on_right = 0;
if (c == '-')
{
c = *fmt0++;
pad_on_right++;
}
if (c == '0')
{
zero_fill = TRUE;
c = *fmt0++;
} else
{
zero_fill = FALSE;
}
while (is_digit(c))
{
left_prec = (left_prec * 10) + (c - '0');
c = *fmt0++;
}
if (c == '.')
{
c = *fmt0++;
zero_fill++;
while (is_digit(c))
{
right_prec = (right_prec * 10) + (c - '0');
c = *fmt0++;
}
} else
{
right_prec = left_prec;
}
sign = '\0';
switch (c)
{
case 'd':
case 'x':
case 'X':
val = va_arg(ap, long);
switch (c)
{
case 'd':
if (val < 0)
{
sign = '-';
val = -val;
}
length = _cvt(val, buf, 10, "0123456789");
break;
case 'x':
length = _cvt(val, buf, 16, "0123456789abcdef");
break;
case 'X':
length = _cvt(val, buf, 16, "0123456789ABCDEF");
break;
}
cp = buf;
break;
case 's':
cp = va_arg(ap, char *);
length = strlen(cp);
break;
case 'c':
c = va_arg(ap, long /*char*/);
(*putc)(c);
continue;
default:
(*putc)('?');
}
pad = left_prec - length;
if (sign != '\0')
{
pad--;
}
if (zero_fill)
{
c = '0';
if (sign != '\0')
{
(*putc)(sign);
sign = '\0';
}
} else
{
c = ' ';
}
if (!pad_on_right)
{
while (pad-- > 0)
{
(*putc)(c);
}
}
if (sign != '\0')
{
(*putc)(sign);
}
while (length-- > 0)
{
(*putc)(c = *cp++);
if (c == '\n')
{
(*putc)('\r');
}
}
if (pad_on_right)
{
while (pad-- > 0)
{
(*putc)(c);
}
}
} else
{
(*putc)(c);
if (c == '\n')
{
(*putc)('\r');
}
}
}
}
int _cvt(unsigned long val, char *buf, long radix, char *digits)
{
char temp[80];
char *cp = temp;
int length = 0;
if (val == 0)
{ /* Special case */
*cp++ = '0';
} else
while (val)
{
*cp++ = digits[val % radix];
val /= radix;
}
while (cp != temp)
{
*buf++ = *--cp;
length++;
}
*buf = '\0';
return (length);
}
_dump_buf_with_offset(unsigned char *p, int s, unsigned char *base)
{
int i, c;
if ((unsigned int)s > (unsigned int)p)
{
s = (unsigned int)s - (unsigned int)p;
}
while (s > 0)
{
if (base)
{
_printk("%06X: ", (int)p - (int)base);
} else
{
_printk("%06X: ", p);
}
for (i = 0; i < 16; i++)
{
if (i < s)
{
_printk("%02X", p[i] & 0xFF);
} else
{
_printk(" ");
}
if ((i % 2) == 1) _printk(" ");
if ((i % 8) == 7) _printk(" ");
}
_printk(" |");
for (i = 0; i < 16; i++)
{
if (i < s)
{
c = p[i] & 0xFF;
if ((c < 0x20) || (c >= 0x7F)) c = '.';
} else
{
c = ' ';
}
_printk("%c", c);
}
_printk("|\n");
s -= 16;
p += 16;
}
}
_dump_buf(unsigned char *p, int s)
{
_printk("\n");
_dump_buf_with_offset(p, s, 0);
}