mirror of
https://gitlab.rtems.org/rtems/rtos/rtems.git
synced 2025-11-16 12:34:45 +00:00
548 lines
14 KiB
C
548 lines
14 KiB
C
/*
|
|
* head.S -- Bootloader Entry point
|
|
*
|
|
* Copyright (C) 1998, 1999 Gabriel Paubert, paubert@iram.es
|
|
*
|
|
* Modified to compile in RTEMS development environment
|
|
* by Eric Valette
|
|
*
|
|
* Copyright (C) 1999 Eric Valette. valette@crf.canon.fr
|
|
*
|
|
* The license and distribution terms for this file may be
|
|
* found in the file LICENSE in this distribution or at
|
|
* http://www.rtems.org/license/LICENSE.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/types.h>
|
|
#include <string.h>
|
|
#include "bootldr.h"
|
|
#include <libcpu/spr.h>
|
|
#include "zlib.h"
|
|
#include <libcpu/byteorder.h>
|
|
#include <rtems/bspIo.h>
|
|
#include <bsp.h>
|
|
|
|
#include <rtems.h>
|
|
|
|
/* to align the pointer to the (next) page boundary */
|
|
#define PAGE_ALIGN(addr) (((addr) + PAGE_MASK) & ~PAGE_MASK)
|
|
|
|
SPR_RO(PPC_PVR)
|
|
|
|
struct inode;
|
|
struct wait_queue;
|
|
struct buffer_head;
|
|
typedef struct { int counter; } atomic_t;
|
|
|
|
typedef struct page {
|
|
/* these must be first (free area handling) */
|
|
struct page *next;
|
|
struct page *prev;
|
|
struct inode *inode;
|
|
unsigned long offset;
|
|
struct page *next_hash;
|
|
atomic_t count;
|
|
unsigned long flags; /* atomic flags, some possibly updated asynchronously */
|
|
struct wait_queue *wait;
|
|
struct page **pprev_hash;
|
|
struct buffer_head * buffers;
|
|
} mem_map_t;
|
|
|
|
extern opaque mm_private, pci_private, v86_private, console_private;
|
|
|
|
#define CONSOLE_ON_SERIAL "console=ttyS0"
|
|
|
|
extern struct console_io vacuum_console_functions;
|
|
extern opaque log_console_setup, serial_console_setup, vga_console_setup;
|
|
|
|
boot_data __bd = {0, 0, 0, 0, 0, 0, 0, 0,
|
|
32, 0, 0, 0, 0, 0, 0,
|
|
&mm_private,
|
|
NULL,
|
|
&pci_private,
|
|
NULL,
|
|
&v86_private,
|
|
"root=/dev/hdc1"
|
|
};
|
|
|
|
static void exit(void) __attribute__((noreturn));
|
|
|
|
static void exit(void) {
|
|
printk("\nOnly way out is to press the reset button!\n");
|
|
asm volatile("": : :"memory");
|
|
while(1);
|
|
}
|
|
|
|
void hang(const char *s, u_long x, ctxt *p) {
|
|
u_long *r1;
|
|
#ifdef DEBUG
|
|
print_all_maps("\nMemory mappings at exception time:\n");
|
|
#endif
|
|
printk("%s %lx NIP: %p LR: %p\n"
|
|
"Callback trace (stack:return address)\n",
|
|
s, x, (void *) p->nip, (void *) p->lr);
|
|
asm volatile("lwz %0,0(1); lwz %0,0(%0); lwz %0,0(%0)": "=b" (r1));
|
|
while(r1) {
|
|
printk(" %p:%p\n", r1, (void *) r1[1]);
|
|
r1 = (u_long *) *r1;
|
|
}
|
|
exit();
|
|
};
|
|
|
|
static void *zalloc(void *x, unsigned items, unsigned size)
|
|
{
|
|
void *p = salloc(items*size);
|
|
|
|
if (!p) {
|
|
printk("oops... not enough memory for gunzip\n");
|
|
}
|
|
return p;
|
|
}
|
|
|
|
static void zfree(void *x, void *addr, unsigned nb)
|
|
{
|
|
sfree(addr);
|
|
}
|
|
|
|
#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) {
|
|
printk("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) {
|
|
printk("gunzip: ran out of data in header\n");
|
|
exit();
|
|
}
|
|
|
|
s.zalloc = zalloc;
|
|
s.zfree = zfree;
|
|
r = inflateInit2(&s, -MAX_WBITS);
|
|
if (r != Z_OK) {
|
|
printk("inflateInit2 returned %d\n", r);
|
|
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) {
|
|
printk("inflate returned %d\n", r);
|
|
exit();
|
|
}
|
|
*lenp = s.next_out - (unsigned char *) dst;
|
|
inflateEnd(&s);
|
|
}
|
|
|
|
void decompress_kernel(int kernel_size, void * zimage_start, int len,
|
|
void * initrd_start, int initrd_len ) {
|
|
u_char *parea;
|
|
RESIDUAL* rescopy;
|
|
int zimage_size= len;
|
|
|
|
/* That's a mess, we have to copy the residual data twice just in
|
|
* case it happens to be in the low memory area where the kernel
|
|
* is going to be unpacked. Later we have to copy it back to
|
|
* lower addresses because only the lowest part of memory is mapped
|
|
* during boot.
|
|
*/
|
|
parea=__palloc(kernel_size, PA_LOW);
|
|
if(!parea) {
|
|
printk("Not enough memory to uncompress the kernel.");
|
|
exit();
|
|
}
|
|
|
|
rescopy=salloc(sizeof(RESIDUAL));
|
|
/* Let us hope that residual data is aligned on word boundary */
|
|
*rescopy = *bd->residual;
|
|
bd->residual = (void *)PAGE_ALIGN(kernel_size);
|
|
|
|
/* Note that this clears the bss as a side effect, so some code
|
|
* with ugly special case for SMP could be removed from the kernel!
|
|
*/
|
|
memset(parea, 0, kernel_size);
|
|
printk("\nUncompressing the kernel...\n");
|
|
|
|
gunzip(parea, kernel_size, zimage_start, &zimage_size);
|
|
|
|
bd->of_entry = 0;
|
|
bd->load_address = 0;
|
|
bd->r6 = (char *)bd->residual+PAGE_ALIGN(sizeof(RESIDUAL));
|
|
bd->r7 = bd->r6+strlen(bd->cmd_line);
|
|
if ( initrd_len ) {
|
|
/* We have to leave some room for the hash table and for the
|
|
* whole array of struct page. The hash table would be better
|
|
* located at the end of memory if possible. With some bridges
|
|
* DMA from the last pages of memory is slower because
|
|
* prefetching from PCI has to be disabled to avoid accessing
|
|
* non existing memory. So it is the ideal place to put the
|
|
* hash table.
|
|
*/
|
|
unsigned tmp = rescopy->TotalMemory;
|
|
/* It's equivalent to tmp & (-tmp), but using the negation
|
|
* operator on unsigned variables looks so ugly.
|
|
*/
|
|
if ((tmp & (~tmp+1)) != tmp) tmp <<= 1; /* Next power of 2 */
|
|
tmp /= 256; /* Size of hash table */
|
|
if (tmp> (2<<20)) tmp=2<<20;
|
|
tmp = tmp*2 + 0x40000; /* Alignment can double size + 256 kB */
|
|
tmp += (rescopy->TotalMemory / PAGE_SIZE)
|
|
* sizeof(struct page);
|
|
bd->load_address = (void *)PAGE_ALIGN((int)bd->r7 + tmp);
|
|
bd->of_entry = (char *)bd->load_address+initrd_len;
|
|
}
|
|
#ifdef DEBUG
|
|
printk("Kernel at 0x%p, size=0x%x\n", NULL, kernel_size);
|
|
printk("Initrd at 0x%p, size=0x%x\n",bd->load_address, initrd_len);
|
|
printk("Residual data at 0x%p\n", bd->residual);
|
|
printk("Command line at 0x%p\n",bd->r6);
|
|
#endif
|
|
printk("done\nNow booting...\n");
|
|
MMUoff(); /* We need to access address 0 ! */
|
|
codemove(0, parea, kernel_size, bd->cache_lsize);
|
|
codemove(bd->residual, rescopy, sizeof(RESIDUAL), bd->cache_lsize);
|
|
codemove(bd->r6, bd->cmd_line, sizeof(bd->cmd_line), bd->cache_lsize);
|
|
/* codemove checks for 0 length */
|
|
codemove(bd->load_address, initrd_start, initrd_len, bd->cache_lsize);
|
|
}
|
|
|
|
static int ticks_per_ms=0;
|
|
|
|
/*
|
|
* This is based on rtems_bsp_delay from libcpu
|
|
*/
|
|
void
|
|
boot_udelay(uint32_t _microseconds)
|
|
{
|
|
uint32_t start, ticks, now;
|
|
|
|
ticks = _microseconds * ticks_per_ms / 1000;
|
|
CPU_Get_timebase_low( start );
|
|
do {
|
|
CPU_Get_timebase_low( now );
|
|
} while (now - start < ticks);
|
|
}
|
|
|
|
void
|
|
setup_hw(void)
|
|
{
|
|
char *cp, ch;
|
|
register RESIDUAL * res;
|
|
/* PPC_DEVICE * nvram; */
|
|
struct pci_dev *default_vga;
|
|
int timer, err;
|
|
u_short default_vga_cmd;
|
|
|
|
res=bd->residual;
|
|
default_vga=NULL;
|
|
default_vga_cmd = 0;
|
|
|
|
#define vpd res->VitalProductData
|
|
if (_read_PPC_PVR()>>16 != 1) {
|
|
if ( res && vpd.ProcessorBusHz ) {
|
|
ticks_per_ms = vpd.ProcessorBusHz/
|
|
(vpd.TimeBaseDivisor ? vpd.TimeBaseDivisor : 4000);
|
|
} else {
|
|
ticks_per_ms = 16500; /* assume 66 MHz on bus */
|
|
}
|
|
}
|
|
|
|
select_console(CONSOLE_LOG);
|
|
|
|
/* We check that the keyboard is present and immediately
|
|
* select the serial console if not.
|
|
*/
|
|
#if defined(BSP_KBD_IOBASE)
|
|
err = kbdreset();
|
|
if (err) select_console(CONSOLE_SERIAL);
|
|
#else
|
|
err = 1;
|
|
select_console(CONSOLE_SERIAL);
|
|
#endif
|
|
|
|
printk("\nModel: %s\nSerial: %s\n"
|
|
"Processor/Bus frequencies (Hz): %ld/%ld\n"
|
|
"Time Base Divisor: %ld\n"
|
|
"Memory Size: %lx\n"
|
|
"Residual: %lx (length %lu)\n",
|
|
vpd.PrintableModel,
|
|
vpd.Serial,
|
|
vpd.ProcessorHz,
|
|
vpd.ProcessorBusHz,
|
|
(vpd.TimeBaseDivisor ? vpd.TimeBaseDivisor : 4000),
|
|
res->TotalMemory,
|
|
(unsigned long)res,
|
|
res->ResidualLength);
|
|
|
|
/* This reconfigures all the PCI subsystem */
|
|
pci_init();
|
|
|
|
/* The Motorola NT firmware does not set the correct mem size */
|
|
if ( vpd.FirmwareSupplier == 0x10000 ) {
|
|
int memsize;
|
|
memsize = find_max_mem(bd->pci_devices);
|
|
if ( memsize != res->TotalMemory ) {
|
|
printk("Changed Memory size from %lx to %x\n",
|
|
res->TotalMemory, memsize);
|
|
res->TotalMemory = memsize;
|
|
res->GoodMemory = memsize;
|
|
}
|
|
}
|
|
#define ENABLE_VGA_USAGE
|
|
#undef ENABLE_VGA_USAGE
|
|
#ifdef ENABLE_VGA_USAGE
|
|
/* Find the primary VGA device, chosing the first one found
|
|
* if none is enabled. The basic loop structure has been copied
|
|
* from linux/drivers/char/bttv.c by Alan Cox.
|
|
*/
|
|
for (p = bd->pci_devices; p; p = p->next) {
|
|
u_short cmd;
|
|
if (p->class != PCI_CLASS_NOT_DEFINED_VGA &&
|
|
((p->class) >> 16 != PCI_BASE_CLASS_DISPLAY))
|
|
continue;
|
|
if (p->bus->number != 0) {
|
|
printk("VGA device not on bus 0 not initialized!\n");
|
|
continue;
|
|
}
|
|
/* Only one can be active in text mode, which for now will
|
|
* be assumed as equivalent to having I/O response enabled.
|
|
*/
|
|
pci_bootloader_read_config_word(p, PCI_COMMAND, &cmd);
|
|
if(cmd & PCI_COMMAND_IO || !default_vga) {
|
|
default_vga=p;
|
|
default_vga_cmd=cmd;
|
|
}
|
|
}
|
|
|
|
/* Disable the enabled VGA device, if any. */
|
|
if (default_vga)
|
|
pci_bootloader_write_config_word(default_vga, PCI_COMMAND,
|
|
default_vga_cmd&
|
|
~(PCI_COMMAND_IO|PCI_COMMAND_MEMORY));
|
|
init_v86();
|
|
/* Same loop copied from bttv.c, this time doing the serious work */
|
|
for (p = bd->pci_devices; p; p = p->next) {
|
|
u_short cmd;
|
|
if (p->class != PCI_CLASS_NOT_DEFINED_VGA &&
|
|
((p->class) >> 16 != PCI_BASE_CLASS_DISPLAY))
|
|
continue;
|
|
if (p->bus->number != 0) continue;
|
|
pci_bootloader_read_config_word(p, PCI_COMMAND, &cmd);
|
|
pci_bootloader_write_config_word(p, PCI_COMMAND,
|
|
cmd|PCI_COMMAND_IO|PCI_COMMAND_MEMORY);
|
|
printk("Calling the emulator.\n");
|
|
em86_main(p);
|
|
pci_bootloader_write_config_word(p, PCI_COMMAND, cmd);
|
|
}
|
|
|
|
cleanup_v86_mess();
|
|
#endif
|
|
/* Reenable the primary VGA device */
|
|
if (default_vga) {
|
|
pci_bootloader_write_config_word(default_vga, PCI_COMMAND,
|
|
default_vga_cmd|
|
|
(PCI_COMMAND_IO|PCI_COMMAND_MEMORY));
|
|
if (err) {
|
|
printk("Keyboard error %d, using serial console!\n",
|
|
err);
|
|
} else {
|
|
select_console(CONSOLE_VGA);
|
|
}
|
|
} else if (!err) {
|
|
select_console(CONSOLE_SERIAL);
|
|
if (bd->cmd_line[0] == '\0') {
|
|
strcat(&bd->cmd_line[0], CONSOLE_ON_SERIAL);
|
|
}
|
|
else {
|
|
int s = strlen (bd->cmd_line);
|
|
bd->cmd_line[s + 1] = ' ';
|
|
bd->cmd_line[s + 2] = '\0';
|
|
strcat(&bd->cmd_line[0], CONSOLE_ON_SERIAL);
|
|
}
|
|
}
|
|
#if 0
|
|
/* In the future we may use the NVRAM to store default
|
|
* kernel parameters.
|
|
*/
|
|
nvram=residual_find_device(~0UL, NULL, SystemPeripheral, NVRAM,
|
|
~0UL, 0);
|
|
if (nvram) {
|
|
PnP_TAG_PACKET * pkt;
|
|
switch (nvram->DevId.Interface) {
|
|
case IndirectNVRAM:
|
|
pkt=PnP_find_packet(res->DevicePnpHeap
|
|
+nvram->AllocatedOffset,
|
|
)
|
|
}
|
|
}
|
|
#endif
|
|
|
|
printk("\nRTEMS " RTEMS_VERSION "/PPC load: ");
|
|
timer = 0;
|
|
cp = bd->cmd_line+strlen(bd->cmd_line);
|
|
while (timer++ < 5*1000) {
|
|
if (debug_tstc()) {
|
|
while ((ch = debug_getc()) != '\n' && ch != '\r') {
|
|
if (ch == '\b' || ch == 0177) {
|
|
if (cp != bd->cmd_line) {
|
|
cp--;
|
|
printk("\b \b");
|
|
}
|
|
} else {
|
|
*cp++ = ch;
|
|
debug_putc(ch);
|
|
}
|
|
}
|
|
break; /* Exit 'timer' loop */
|
|
}
|
|
boot_udelay(1000); /* 1 msec */
|
|
}
|
|
*cp = 0;
|
|
}
|
|
|
|
/* Functions to deal with the residual data */
|
|
static int same_DevID(unsigned short vendor,
|
|
unsigned short Number,
|
|
unsigned char * str)
|
|
{
|
|
static unsigned const char hexdigit[]="0123456789ABCDEF";
|
|
if (strlen((char*)str)!=7) return 0;
|
|
if ( ( ((vendor>>10)&0x1f)+'A'-1 == str[0]) &&
|
|
( ((vendor>>5)&0x1f)+'A'-1 == str[1]) &&
|
|
( (vendor&0x1f)+'A'-1 == str[2]) &&
|
|
(hexdigit[(Number>>12)&0x0f] == str[3]) &&
|
|
(hexdigit[(Number>>8)&0x0f] == str[4]) &&
|
|
(hexdigit[(Number>>4)&0x0f] == str[5]) &&
|
|
(hexdigit[Number&0x0f] == str[6]) ) return 1;
|
|
return 0;
|
|
}
|
|
|
|
PPC_DEVICE *residual_find_device(unsigned long BusMask,
|
|
unsigned char * DevID,
|
|
int BaseType,
|
|
int SubType,
|
|
int Interface,
|
|
int n)
|
|
{
|
|
int i;
|
|
RESIDUAL *res = bd->residual;
|
|
if ( !res || !res->ResidualLength ) return NULL;
|
|
for (i=0; i<res->ActualNumDevices; i++) {
|
|
#define Dev res->Devices[i].DeviceId
|
|
if ( (Dev.BusId&BusMask) &&
|
|
(BaseType==-1 || Dev.BaseType==BaseType) &&
|
|
(SubType==-1 || Dev.SubType==SubType) &&
|
|
(Interface==-1 || Dev.Interface==Interface) &&
|
|
(DevID==NULL || same_DevID((Dev.DevId>>16)&0xffff,
|
|
Dev.DevId&0xffff, DevID)) &&
|
|
!(n--) ) return res->Devices+i;
|
|
#undef Dev
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
PnP_TAG_PACKET *PnP_find_packet(unsigned char *p,
|
|
unsigned packet_tag,
|
|
int n)
|
|
{
|
|
unsigned mask, masked_tag, size;
|
|
if(!p) return 0;
|
|
if (tag_type(packet_tag)) mask=0xff; else mask=0xF8;
|
|
masked_tag = packet_tag&mask;
|
|
for(; *p != END_TAG; p+=size) {
|
|
if ((*p & mask) == masked_tag && !(n--))
|
|
return (PnP_TAG_PACKET *) p;
|
|
if (tag_type(*p))
|
|
size=ld_le16((unsigned short *)(p+1))+3;
|
|
else
|
|
size=tag_small_count(*p)+1;
|
|
}
|
|
return 0; /* not found */
|
|
}
|
|
|
|
PnP_TAG_PACKET *PnP_find_small_vendor_packet(unsigned char *p,
|
|
unsigned packet_type,
|
|
int n)
|
|
{
|
|
int next=0;
|
|
while (p) {
|
|
p = (unsigned char *) PnP_find_packet(p, 0x70, next);
|
|
if (p && p[1]==packet_type && !(n--))
|
|
return (PnP_TAG_PACKET *) p;
|
|
next = 1;
|
|
};
|
|
return 0; /* not found */
|
|
}
|
|
|
|
PnP_TAG_PACKET *PnP_find_large_vendor_packet(unsigned char *p,
|
|
unsigned packet_type,
|
|
int n)
|
|
{
|
|
int next=0;
|
|
while (p) {
|
|
p = (unsigned char *) PnP_find_packet(p, 0x84, next);
|
|
if (p && p[3]==packet_type && !(n--))
|
|
return (PnP_TAG_PACKET *) p;
|
|
next = 1;
|
|
};
|
|
return 0; /* not found */
|
|
}
|
|
|
|
/* Find out the amount of installed memory. For MPC105 and IBM 660 this
|
|
* can be done by finding the bank with the highest memory ending address
|
|
*/
|
|
int
|
|
find_max_mem( struct pci_dev *dev )
|
|
{
|
|
u_char banks,tmp;
|
|
int i, top, max;
|
|
|
|
max = 0;
|
|
for ( ; dev; dev = dev->next) {
|
|
if ( ((dev->vendor == PCI_VENDOR_ID_MOTOROLA) &&
|
|
(dev->device == PCI_DEVICE_ID_MOTOROLA_MPC105)) ||
|
|
((dev->vendor == PCI_VENDOR_ID_IBM) &&
|
|
(dev->device == 0x0037/*IBM 660 Bridge*/)) ) {
|
|
pci_bootloader_read_config_byte(dev, 0xa0, &banks);
|
|
for (i = 0; i < 8; i++) {
|
|
if ( banks & (1<<i) ) {
|
|
pci_bootloader_read_config_byte(dev, 0x90+i, &tmp);
|
|
top = tmp;
|
|
pci_bootloader_read_config_byte(dev, 0x98+i, &tmp);
|
|
top |= (tmp&3)<<8;
|
|
if ( top > max ) max = top;
|
|
}
|
|
}
|
|
if ( max ) return ((max+1)<<20);
|
|
else return(0);
|
|
}
|
|
}
|
|
return(0);
|
|
}
|