Commit 66a45dd3 authored by Paul Mackerras's avatar Paul Mackerras

powerpc: Make COFF zImages for old 32-bit powermacs

This adds code to build zImage.coff and/or zImage.initrd.coff when
CONFIG_PPC32 and CONFIG_PPC_PMAC are defined.  It also restructures
the OF client code and adds some workarounds for OF quirks on the
older machines.
Signed-off-by: default avatarPaul Mackerras <paulus@samba.org>
parent 36874579
...@@ -25,8 +25,8 @@ HOSTCC := gcc ...@@ -25,8 +25,8 @@ HOSTCC := gcc
BOOTCFLAGS := $(HOSTCFLAGS) -fno-builtin -nostdinc -isystem \ BOOTCFLAGS := $(HOSTCFLAGS) -fno-builtin -nostdinc -isystem \
$(shell $(CROSS32CC) -print-file-name=include) -fPIC $(shell $(CROSS32CC) -print-file-name=include) -fPIC
BOOTAFLAGS := -D__ASSEMBLY__ $(BOOTCFLAGS) -traditional -nostdinc BOOTAFLAGS := -D__ASSEMBLY__ $(BOOTCFLAGS) -traditional -nostdinc
BOOTLFLAGS := -T $(srctree)/$(src)/zImage.lds
OBJCOPYFLAGS := contents,alloc,load,readonly,data OBJCOPYFLAGS := contents,alloc,load,readonly,data
OBJCOPY_COFF_ARGS := -O aixcoff-rs6000 --set-start 0x500000
zlib := infblock.c infcodes.c inffast.c inflate.c inftrees.c infutil.c zlib := infblock.c infcodes.c inffast.c inflate.c inftrees.c infutil.c
zlibheader := infblock.h infcodes.h inffast.h inftrees.h infutil.h zlibheader := infblock.h infcodes.h inffast.h inftrees.h infutil.h
...@@ -35,7 +35,7 @@ zliblinuxheader := zlib.h zconf.h zutil.h ...@@ -35,7 +35,7 @@ zliblinuxheader := zlib.h zconf.h zutil.h
$(addprefix $(obj)/,$(zlib) main.o): $(addprefix $(obj)/,$(zliblinuxheader)) $(addprefix $(obj)/,$(zlibheader)) $(addprefix $(obj)/,$(zlib) main.o): $(addprefix $(obj)/,$(zliblinuxheader)) $(addprefix $(obj)/,$(zlibheader))
#$(addprefix $(obj)/,main.o): $(addprefix $(obj)/,zlib.h) #$(addprefix $(obj)/,main.o): $(addprefix $(obj)/,zlib.h)
src-boot := string.S prom.c main.c div64.S crt0.S src-boot := crt0.S string.S prom.c stdio.c main.c div64.S
src-boot += $(zlib) src-boot += $(zlib)
src-boot := $(addprefix $(obj)/, $(src-boot)) src-boot := $(addprefix $(obj)/, $(src-boot))
obj-boot := $(addsuffix .o, $(basename $(src-boot))) obj-boot := $(addsuffix .o, $(basename $(src-boot)))
...@@ -70,7 +70,7 @@ quiet_cmd_bootas = BOOTAS $@ ...@@ -70,7 +70,7 @@ quiet_cmd_bootas = BOOTAS $@
cmd_bootas = $(CROSS32CC) -Wp,-MD,$(depfile) $(BOOTAFLAGS) -c -o $@ $< cmd_bootas = $(CROSS32CC) -Wp,-MD,$(depfile) $(BOOTAFLAGS) -c -o $@ $<
quiet_cmd_bootld = BOOTLD $@ quiet_cmd_bootld = BOOTLD $@
cmd_bootld = $(CROSS32LD) $(BOOTLFLAGS) -o $@ $(2) cmd_bootld = $(CROSS32LD) -T $(srctree)/$(src)/$(3) -o $@ $(2)
$(patsubst %.c,%.o, $(filter %.c, $(src-boot))): %.o: %.c $(patsubst %.c,%.o, $(filter %.c, $(src-boot))): %.o: %.c
$(call if_changed_dep,bootcc) $(call if_changed_dep,bootcc)
...@@ -87,8 +87,10 @@ obj-sec = $(foreach section, $(1), $(patsubst %,$(obj)/kernel-%.o, $(section))) ...@@ -87,8 +87,10 @@ obj-sec = $(foreach section, $(1), $(patsubst %,$(obj)/kernel-%.o, $(section)))
src-sec = $(foreach section, $(1), $(patsubst %,$(obj)/kernel-%.c, $(section))) src-sec = $(foreach section, $(1), $(patsubst %,$(obj)/kernel-%.c, $(section)))
gz-sec = $(foreach section, $(1), $(patsubst %,$(obj)/kernel-%.gz, $(section))) gz-sec = $(foreach section, $(1), $(patsubst %,$(obj)/kernel-%.gz, $(section)))
hostprogs-y := addnote addRamDisk hostprogs-y := addnote addRamDisk hack-coff
targets += zImage.vmode zImage.initrd.vmode zImage zImage.initrd \ targets += zImage.vmode zImage.initrd.vmode zImage zImage.initrd \
zImage.coff zImage.initrd.coff \
$(patsubst $(obj)/%,%, $(call obj-sec, $(required) $(initrd))) \ $(patsubst $(obj)/%,%, $(call obj-sec, $(required) $(initrd))) \
$(patsubst $(obj)/%,%, $(call src-sec, $(required) $(initrd))) \ $(patsubst $(obj)/%,%, $(call src-sec, $(required) $(initrd))) \
$(patsubst $(obj)/%,%, $(call gz-sec, $(required) $(initrd))) \ $(patsubst $(obj)/%,%, $(call gz-sec, $(required) $(initrd))) \
...@@ -114,6 +116,10 @@ quiet_cmd_addsection = ADDSEC $@ ...@@ -114,6 +116,10 @@ quiet_cmd_addsection = ADDSEC $@
quiet_cmd_addnote = ADDNOTE $@ quiet_cmd_addnote = ADDNOTE $@
cmd_addnote = $(obj)/addnote $@ cmd_addnote = $(obj)/addnote $@
quiet_cmd_gencoff = COFF $@
cmd_gencoff = $(OBJCOPY) $(OBJCOPY_COFF_ARGS) $@ && \
$(obj)/hack-coff $@
$(call gz-sec, $(required)): $(obj)/kernel-%.gz: % $(call gz-sec, $(required)): $(obj)/kernel-%.gz: %
$(call if_changed,gzip) $(call if_changed,gzip)
...@@ -127,22 +133,35 @@ $(call obj-sec, $(required) $(initrd)): $(obj)/kernel-%.o: $(obj)/kernel-%.c ...@@ -127,22 +133,35 @@ $(call obj-sec, $(required) $(initrd)): $(obj)/kernel-%.o: $(obj)/kernel-%.c
$(call if_changed_dep,bootcc) $(call if_changed_dep,bootcc)
$(call cmd,addsection) $(call cmd,addsection)
$(obj)/zImage.vmode: obj-boot += $(call obj-sec, $(required)) $(obj)/zImage.vmode $(obj)/zImage.coff: obj-boot += $(call obj-sec, $(required))
$(obj)/zImage.vmode: $(call obj-sec, $(required)) $(obj-boot) $(srctree)/$(src)/zImage.lds $(obj)/zImage.vmode: $(call obj-sec, $(required)) $(obj-boot) $(srctree)/$(src)/zImage.lds
$(call cmd,bootld,$(obj-boot)) $(call cmd,bootld,$(obj-boot),zImage.lds)
$(obj)/zImage.initrd.vmode: obj-boot += $(call obj-sec, $(required) $(initrd)) $(obj)/zImage.initrd.vmode $(obj)/zImage.initrd.coff: obj-boot += $(call obj-sec, $(required) $(initrd))
$(obj)/zImage.initrd.vmode: $(call obj-sec, $(required) $(initrd)) $(obj-boot) $(srctree)/$(src)/zImage.lds $(obj)/zImage.initrd.vmode: $(call obj-sec, $(required) $(initrd)) $(obj-boot) $(srctree)/$(src)/zImage.lds
$(call cmd,bootld,$(obj-boot)) $(call cmd,bootld,$(obj-boot),zImage.lds)
# For 32-bit powermacs, build the COFF images as well as the ELF images.
coffimage-$(CONFIG_PPC_PMAC)-$(CONFIG_PPC32) := $(obj)/zImage.coff
coffrdimg-$(CONFIG_PPC_PMAC)-$(CONFIG_PPC32) := $(obj)/zImage.initrd.coff
$(obj)/zImage: $(obj)/zImage.vmode $(obj)/addnote $(obj)/zImage: $(obj)/zImage.vmode $(obj)/addnote $(coffimage-y-y)
@cp -f $< $@ @cp -f $< $@
$(call if_changed,addnote) $(call if_changed,addnote)
$(obj)/zImage.initrd: $(obj)/zImage.initrd.vmode $(obj)/addnote $(obj)/zImage.initrd: $(obj)/zImage.initrd.vmode $(obj)/addnote $(coffrdimg-y-y)
@cp -f $< $@ @cp -f $< $@
$(call if_changed,addnote) $(call if_changed,addnote)
$(obj)/zImage.coff: $(call obj-sec, $(required)) $(obj-boot) $(srctree)/$(src)/zImage.coff.lds $(obj)/hack-coff
$(call cmd,bootld,$(obj-boot),zImage.coff.lds)
$(call cmd,gencoff)
$(obj)/zImage.initrd.coff: $(call obj-sec, $(required) $(initrd)) $(obj-boot) \
$(srctree)/$(src)/zImage.coff.lds $(obj)/hack-coff
$(call cmd,bootld,$(obj-boot),zImage.coff.lds)
$(call cmd,gencoff)
#----------------------------------------------------------- #-----------------------------------------------------------
# build u-boot images # build u-boot images
#----------------------------------------------------------- #-----------------------------------------------------------
......
...@@ -12,17 +12,23 @@ ...@@ -12,17 +12,23 @@
#include "ppc_asm.h" #include "ppc_asm.h"
.text .text
/* a procedure descriptor used when booting this as a COFF file */
_zimage_start_opd:
.long _zimage_start, 0, 0, 0
.globl _zimage_start .globl _zimage_start
_zimage_start: _zimage_start:
/* Work out the offset between the address we were linked at
and the address where we're running. */
bl 1f bl 1f
1: mflr r0
1:
mflr r0
lis r9,1b@ha lis r9,1b@ha
addi r9,r9,1b@l addi r9,r9,1b@l
subf. r0,r9,r0 subf. r0,r9,r0
beq 3f beq 3f /* if running at same address as linked */
/* The .got2 section contains a list of addresses, so add
the address offset onto each entry. */
lis r9,__got2_start@ha lis r9,__got2_start@ha
addi r9,r9,__got2_start@l addi r9,r9,__got2_start@l
lis r8,__got2_end@ha lis r8,__got2_end@ha
...@@ -32,15 +38,14 @@ _zimage_start: ...@@ -32,15 +38,14 @@ _zimage_start:
srwi. r8,r8,2 srwi. r8,r8,2
mtctr r8 mtctr r8
add r9,r0,r9 add r9,r0,r9
2: 2: lwz r8,0(r9)
lwz r8,0(r9)
add r8,r8,r0 add r8,r8,r0
stw r8,0(r9) stw r8,0(r9)
addi r9,r9,4 addi r9,r9,4
bdnz 2b bdnz 2b
3: /* Do a cache flush for our text, in case OF didn't */
lis r9,_start@h 3: lis r9,_start@h
add r9,r0,r9 add r9,r0,r9
lis r8,_etext@ha lis r8,_etext@ha
addi r8,r8,_etext@l addi r8,r8,_etext@l
......
/*
* hack-coff.c - hack the header of an xcoff file to fill in
* a few fields needed by the Open Firmware xcoff loader on
* Power Macs but not initialized by objcopy.
*
* Copyright (C) Paul Mackerras 1997.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include "rs6000.h"
#define AOUT_MAGIC 0x010b
#define get_16be(x) ((((unsigned char *)(x))[0] << 8) \
+ ((unsigned char *)(x))[1])
#define put_16be(x, v) (((unsigned char *)(x))[0] = (v) >> 8, \
((unsigned char *)(x))[1] = (v) & 0xff)
#define get_32be(x) ((((unsigned char *)(x))[0] << 24) \
+ (((unsigned char *)(x))[1] << 16) \
+ (((unsigned char *)(x))[2] << 8) \
+ ((unsigned char *)(x))[3])
int
main(int ac, char **av)
{
int fd;
int i, nsect;
int aoutsz;
struct external_filehdr fhdr;
AOUTHDR aout;
struct external_scnhdr shdr;
if (ac != 2) {
fprintf(stderr, "Usage: hack-coff coff-file\n");
exit(1);
}
if ((fd = open(av[1], 2)) == -1) {
perror(av[2]);
exit(1);
}
if (read(fd, &fhdr, sizeof(fhdr)) != sizeof(fhdr))
goto readerr;
i = get_16be(fhdr.f_magic);
if (i != U802TOCMAGIC && i != U802WRMAGIC && i != U802ROMAGIC) {
fprintf(stderr, "%s: not an xcoff file\n", av[1]);
exit(1);
}
aoutsz = get_16be(fhdr.f_opthdr);
if (read(fd, &aout, aoutsz) != aoutsz)
goto readerr;
nsect = get_16be(fhdr.f_nscns);
for (i = 0; i < nsect; ++i) {
if (read(fd, &shdr, sizeof(shdr)) != sizeof(shdr))
goto readerr;
if (strcmp(shdr.s_name, ".text") == 0) {
put_16be(aout.o_snentry, i+1);
put_16be(aout.o_sntext, i+1);
} else if (strcmp(shdr.s_name, ".data") == 0) {
put_16be(aout.o_sndata, i+1);
} else if (strcmp(shdr.s_name, ".bss") == 0) {
put_16be(aout.o_snbss, i+1);
}
}
put_16be(aout.magic, AOUT_MAGIC);
if (lseek(fd, (long) sizeof(struct external_filehdr), 0) == -1
|| write(fd, &aout, aoutsz) != aoutsz) {
fprintf(stderr, "%s: write error\n", av[1]);
exit(1);
}
close(fd);
exit(0);
readerr:
fprintf(stderr, "%s: read error or file too short\n", av[1]);
exit(1);
}
...@@ -21,8 +21,8 @@ extern void flush_cache(void *, unsigned long); ...@@ -21,8 +21,8 @@ extern void flush_cache(void *, unsigned long);
/* Value picked to match that used by yaboot */ /* Value picked to match that used by yaboot */
#define PROG_START 0x01400000 #define PROG_START 0x01400000 /* only used on 64-bit systems */
#define RAM_END (512<<20) // Fixme: use OF */ #define RAM_END (512<<20) /* Fixme: use OF */
#define ONE_MB 0x100000 #define ONE_MB 0x100000
extern char _start[]; extern char _start[];
...@@ -160,6 +160,17 @@ static int is_elf64(void *hdr) ...@@ -160,6 +160,17 @@ static int is_elf64(void *hdr)
elfoffset = (unsigned long)elf64ph->p_offset; elfoffset = (unsigned long)elf64ph->p_offset;
vmlinux.size = (unsigned long)elf64ph->p_filesz + elfoffset; vmlinux.size = (unsigned long)elf64ph->p_filesz + elfoffset;
vmlinux.memsize = (unsigned long)elf64ph->p_memsz + elfoffset; vmlinux.memsize = (unsigned long)elf64ph->p_memsz + elfoffset;
#if defined(PROG_START)
/*
* Maintain a "magic" minimum address. This keeps some older
* firmware platforms running.
*/
if (claim_base < PROG_START)
claim_base = PROG_START;
#endif
return 1; return 1;
} }
...@@ -206,12 +217,18 @@ void start(unsigned long a1, unsigned long a2, void *promptr, void *sp) ...@@ -206,12 +217,18 @@ void start(unsigned long a1, unsigned long a2, void *promptr, void *sp)
exit(); exit();
if (getprop(chosen_handle, "stdout", &stdout, sizeof(stdout)) != 4) if (getprop(chosen_handle, "stdout", &stdout, sizeof(stdout)) != 4)
exit(); exit();
stderr = stdout;
if (getprop(chosen_handle, "stdin", &stdin, sizeof(stdin)) != 4)
exit();
printf("\n\rzImage starting: loaded at 0x%p (sp: 0x%p)\n\r", _start, sp); printf("\n\rzImage starting: loaded at 0x%p (sp: 0x%p)\n\r", _start, sp);
/*
* The first available claim_base must be above the end of the
* the loaded kernel wrapper file (_start to _end includes the
* initrd image if it is present) and rounded up to a nice
* 1 MB boundary for good measure.
*/
claim_base = _ALIGN_UP((unsigned long)_end, ONE_MB);
vmlinuz.addr = (unsigned long)_vmlinux_start; vmlinuz.addr = (unsigned long)_vmlinux_start;
vmlinuz.size = (unsigned long)(_vmlinux_end - _vmlinux_start); vmlinuz.size = (unsigned long)(_vmlinux_end - _vmlinux_start);
...@@ -228,25 +245,6 @@ void start(unsigned long a1, unsigned long a2, void *promptr, void *sp) ...@@ -228,25 +245,6 @@ void start(unsigned long a1, unsigned long a2, void *promptr, void *sp)
exit(); exit();
} }
/*
* The first available claim_base must be above the end of the
* the loaded kernel wrapper file (_start to _end includes the
* initrd image if it is present) and rounded up to a nice
* 1 MB boundary for good measure.
*/
claim_base = _ALIGN_UP((unsigned long)_end, ONE_MB);
#if defined(PROG_START)
/*
* Maintain a "magic" minimum address. This keeps some older
* firmware platforms running.
*/
if (claim_base < PROG_START)
claim_base = PROG_START;
#endif
/* We need to claim the memsize plus the file offset since gzip /* We need to claim the memsize plus the file offset since gzip
* will expand the header (file offset), then the kernel, then * will expand the header (file offset), then the kernel, then
* possible rubbish we don't care about. But the kernel bss must * possible rubbish we don't care about. But the kernel bss must
......
...@@ -13,487 +13,153 @@ ...@@ -13,487 +13,153 @@
#include "prom.h" #include "prom.h"
int (*prom)(void *); int (*prom)(void *);
phandle chosen_handle;
ihandle stdout;
void *chosen_handle; int call_prom(const char *service, int nargs, int nret, ...)
void *stdin;
void *stdout;
void *stderr;
int
write(void *handle, void *ptr, int nb)
{ {
int i;
struct prom_args { struct prom_args {
char *service; const char *service;
int nargs; int nargs;
int nret; int nret;
void *ihandle; unsigned int args[12];
void *addr;
int len;
int actual;
} args; } args;
va_list list;
args.service = "write"; args.service = service;
args.nargs = 3; args.nargs = nargs;
args.nret = 1; args.nret = nret;
args.ihandle = handle;
args.addr = ptr;
args.len = nb;
args.actual = -1;
(*prom)(&args);
return args.actual;
}
int va_start(list, nret);
read(void *handle, void *ptr, int nb) for (i = 0; i < nargs; i++)
{ args.args[i] = va_arg(list, unsigned int);
struct prom_args { va_end(list);
char *service;
int nargs;
int nret;
void *ihandle;
void *addr;
int len;
int actual;
} args;
args.service = "read"; for (i = 0; i < nret; i++)
args.nargs = 3; args.args[nargs+i] = 0;
args.nret = 1;
args.ihandle = handle;
args.addr = ptr;
args.len = nb;
args.actual = -1;
(*prom)(&args);
return args.actual;
}
void if (prom(&args) < 0)
exit() return -1;
{
struct prom_args {
char *service;
} args;
for (;;) {
args.service = "exit";
(*prom)(&args);
}
}
void
pause(void)
{
struct prom_args {
char *service;
} args;
args.service = "enter"; return (nret > 0)? args.args[nargs]: 0;
(*prom)(&args);
} }
void * int call_prom_ret(const char *service, int nargs, int nret,
finddevice(const char *name) unsigned int *rets, ...)
{
struct prom_args {
char *service;
int nargs;
int nret;
const char *devspec;
void *phandle;
} args;
args.service = "finddevice";
args.nargs = 1;
args.nret = 1;
args.devspec = name;
args.phandle = (void *) -1;
(*prom)(&args);
return args.phandle;
}
void *
claim(unsigned long virt, unsigned long size, unsigned long align)
{
struct prom_args {
char *service;
int nargs;
int nret;
unsigned int virt;
unsigned int size;
unsigned int align;
void *ret;
} args;
args.service = "claim";
args.nargs = 3;
args.nret = 1;
args.virt = virt;
args.size = size;
args.align = align;
(*prom)(&args);
return args.ret;
}
int
getprop(void *phandle, const char *name, void *buf, int buflen)
{ {
int i;
struct prom_args { struct prom_args {
char *service; const char *service;
int nargs; int nargs;
int nret; int nret;
void *phandle; unsigned int args[12];
const char *name;
void *buf;
int buflen;
int size;
} args; } args;
va_list list;
args.service = "getprop"; args.service = service;
args.nargs = 4; args.nargs = nargs;
args.nret = 1; args.nret = nret;
args.phandle = phandle;
args.name = name;
args.buf = buf;
args.buflen = buflen;
args.size = -1;
(*prom)(&args);
return args.size;
}
int va_start(list, rets);
putc(int c, void *f) for (i = 0; i < nargs; i++)
{ args.args[i] = va_arg(list, unsigned int);
char ch = c; va_end(list);
if (c == '\n') for (i = 0; i < nret; i++)
putc('\r', f); args.args[nargs+i] = 0;
return write(f, &ch, 1) == 1? c: -1;
}
int if (prom(&args) < 0)
putchar(int c) return -1;
{
return putc(c, stdout);
}
int if (rets != (void *) 0)
fputs(char *str, void *f) for (i = 1; i < nret; ++i)
{ rets[i-1] = args.args[nargs+i];
int n = strlen(str);
return write(f, str, n) == n? 0: -1; return (nret > 0)? args.args[nargs]: 0;
} }
size_t strnlen(const char * s, size_t count) int write(void *handle, void *ptr, int nb)
{ {
const char *sc; return call_prom("write", 3, 1, handle, ptr, nb);
for (sc = s; count-- && *sc != '\0'; ++sc)
/* nothing */;
return sc - s;
} }
extern unsigned int __div64_32(unsigned long long *dividend, /*
unsigned int divisor); * Older OF's require that when claiming a specific range of addresses,
* we claim the physical space in the /memory node and the virtual
/* The unnecessary pointer compare is there * space in the chosen mmu node, and then do a map operation to
* to check for type safety (n must be 64bit) * map virtual to physical.
*/ */
# define do_div(n,base) ({ \ static int need_map = -1;
unsigned int __base = (base); \ static ihandle chosen_mmu;
unsigned int __rem; \ static phandle memory;
(void)(((typeof((n)) *)0) == ((unsigned long long *)0)); \
if (((n) >> 32) == 0) { \
__rem = (unsigned int)(n) % __base; \
(n) = (unsigned int)(n) / __base; \
} else \
__rem = __div64_32(&(n), __base); \
__rem; \
})
static int skip_atoi(const char **s)
{
int i, c;
for (i = 0; '0' <= (c = **s) && c <= '9'; ++*s) /* returns true if s2 is a prefix of s1 */
i = i*10 + c - '0'; static int string_match(const char *s1, const char *s2)
return i;
}
#define ZEROPAD 1 /* pad with zero */
#define SIGN 2 /* unsigned/signed long */
#define PLUS 4 /* show plus */
#define SPACE 8 /* space if plus */
#define LEFT 16 /* left justified */
#define SPECIAL 32 /* 0x */
#define LARGE 64 /* use 'ABCDEF' instead of 'abcdef' */
static char * number(char * str, unsigned long long num, int base, int size, int precision, int type)
{ {
char c,sign,tmp[66]; for (; *s2; ++s2)
const char *digits="0123456789abcdefghijklmnopqrstuvwxyz"; if (*s1++ != *s2)
int i;
if (type & LARGE)
digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (type & LEFT)
type &= ~ZEROPAD;
if (base < 2 || base > 36)
return 0; return 0;
c = (type & ZEROPAD) ? '0' : ' '; return 1;
sign = 0;
if (type & SIGN) {
if ((signed long long)num < 0) {
sign = '-';
num = - (signed long long)num;
size--;
} else if (type & PLUS) {
sign = '+';
size--;
} else if (type & SPACE) {
sign = ' ';
size--;
}
}
if (type & SPECIAL) {
if (base == 16)
size -= 2;
else if (base == 8)
size--;
}
i = 0;
if (num == 0)
tmp[i++]='0';
else while (num != 0) {
tmp[i++] = digits[do_div(num, base)];
}
if (i > precision)
precision = i;
size -= precision;
if (!(type&(ZEROPAD+LEFT)))
while(size-->0)
*str++ = ' ';
if (sign)
*str++ = sign;
if (type & SPECIAL) {
if (base==8)
*str++ = '0';
else if (base==16) {
*str++ = '0';
*str++ = digits[33];
}
}
if (!(type & LEFT))
while (size-- > 0)
*str++ = c;
while (i < precision--)
*str++ = '0';
while (i-- > 0)
*str++ = tmp[i];
while (size-- > 0)
*str++ = ' ';
return str;
} }
int vsprintf(char *buf, const char *fmt, va_list args) static int check_of_version(void)
{ {
int len; phandle oprom, chosen;
unsigned long long num; char version[64];
int i, base;
char * str;
const char *s;
int flags; /* flags to number() */
int field_width; /* width of output field */
int precision; /* min. # of digits for integers; max
number of chars for from string */
int qualifier; /* 'h', 'l', or 'L' for integer fields */
/* 'z' support added 23/7/1999 S.H. */
/* 'z' changed to 'Z' --davidm 1/25/99 */
oprom = finddevice("/openprom");
for (str=buf ; *fmt ; ++fmt) { if (oprom == (phandle) -1)
if (*fmt != '%') { return 0;
*str++ = *fmt; if (getprop(oprom, "model", version, sizeof(version)) <= 0)
continue; return 0;
} version[sizeof(version)-1] = 0;
printf("OF version = '%s'\r\n", version);
/* process flags */ if (!string_match(version, "Open Firmware, 1.")
flags = 0; && !string_match(version, "FirmWorks,3."))
repeat: return 0;
++fmt; /* this also skips first '%' */ chosen = finddevice("/chosen");
switch (*fmt) { if (chosen == (phandle) -1) {
case '-': flags |= LEFT; goto repeat; chosen = finddevice("/chosen@0");
case '+': flags |= PLUS; goto repeat; if (chosen == (phandle) -1) {
case ' ': flags |= SPACE; goto repeat; printf("no chosen\n");
case '#': flags |= SPECIAL; goto repeat; return 0;
case '0': flags |= ZEROPAD; goto repeat;
}
/* get field width */
field_width = -1;
if ('0' <= *fmt && *fmt <= '9')
field_width = skip_atoi(&fmt);
else if (*fmt == '*') {
++fmt;
/* it's the next argument */
field_width = va_arg(args, int);
if (field_width < 0) {
field_width = -field_width;
flags |= LEFT;
}
}
/* get the precision */
precision = -1;
if (*fmt == '.') {
++fmt;
if ('0' <= *fmt && *fmt <= '9')
precision = skip_atoi(&fmt);
else if (*fmt == '*') {
++fmt;
/* it's the next argument */
precision = va_arg(args, int);
}
if (precision < 0)
precision = 0;
}
/* get the conversion qualifier */
qualifier = -1;
if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L' || *fmt =='Z') {
qualifier = *fmt;
++fmt;
}
/* default base */
base = 10;
switch (*fmt) {
case 'c':
if (!(flags & LEFT))
while (--field_width > 0)
*str++ = ' ';
*str++ = (unsigned char) va_arg(args, int);
while (--field_width > 0)
*str++ = ' ';
continue;
case 's':
s = va_arg(args, char *);
if (!s)
s = "<NULL>";
len = strnlen(s, precision);
if (!(flags & LEFT))
while (len < field_width--)
*str++ = ' ';
for (i = 0; i < len; ++i)
*str++ = *s++;
while (len < field_width--)
*str++ = ' ';
continue;
case 'p':
if (field_width == -1) {
field_width = 2*sizeof(void *);
flags |= ZEROPAD;
} }
str = number(str,
(unsigned long) va_arg(args, void *), 16,
field_width, precision, flags);
continue;
case 'n':
if (qualifier == 'l') {
long * ip = va_arg(args, long *);
*ip = (str - buf);
} else if (qualifier == 'Z') {
size_t * ip = va_arg(args, size_t *);
*ip = (str - buf);
} else {
int * ip = va_arg(args, int *);
*ip = (str - buf);
} }
continue; if (getprop(chosen, "mmu", &chosen_mmu, sizeof(chosen_mmu)) <= 0) {
printf("no mmu\n");
case '%': return 0;
*str++ = '%';
continue;
/* integer number formats - set up the flags and "break" */
case 'o':
base = 8;
break;
case 'X':
flags |= LARGE;
case 'x':
base = 16;
break;
case 'd':
case 'i':
flags |= SIGN;
case 'u':
break;
default:
*str++ = '%';
if (*fmt)
*str++ = *fmt;
else
--fmt;
continue;
} }
if (qualifier == 'l') { memory = (ihandle) call_prom("open", 1, 1, "/memory");
num = va_arg(args, unsigned long); if (memory == (ihandle) -1) {
if (flags & SIGN) memory = (ihandle) call_prom("open", 1, 1, "/memory@0");
num = (signed long) num; if (memory == (ihandle) -1) {
} else if (qualifier == 'Z') { printf("no memory node\n");
num = va_arg(args, size_t); return 0;
} else if (qualifier == 'h') {
num = (unsigned short) va_arg(args, int);
if (flags & SIGN)
num = (signed short) num;
} else {
num = va_arg(args, unsigned int);
if (flags & SIGN)
num = (signed int) num;
} }
str = number(str, num, base, field_width, precision, flags);
} }
*str = '\0'; printf("old OF detected\r\n");
return str-buf; return 1;
} }
int sprintf(char * buf, const char *fmt, ...) void *claim(unsigned long virt, unsigned long size, unsigned long align)
{ {
va_list args; int ret;
int i; unsigned int result;
va_start(args, fmt);
i=vsprintf(buf,fmt,args);
va_end(args);
return i;
}
static char sprint_buf[1024];
int if (need_map < 0)
printf(const char *fmt, ...) need_map = check_of_version();
{ if (align || !need_map)
va_list args; return (void *) call_prom("claim", 3, 1, virt, size, align);
int n;
va_start(args, fmt); ret = call_prom_ret("call-method", 5, 2, &result, "claim", memory,
n = vsprintf(sprint_buf, fmt, args); align, size, virt);
va_end(args); if (ret != 0 || result == -1)
write(stdout, sprint_buf, n); return (void *) -1;
return n; ret = call_prom_ret("call-method", 5, 2, &result, "claim", chosen_mmu,
align, size, virt);
/* 0x12 == coherent + read/write */
ret = call_prom("call-method", 6, 1, "map", chosen_mmu,
0x12, size, virt, virt);
return (void *) virt;
} }
#ifndef _PPC_BOOT_PROM_H_ #ifndef _PPC_BOOT_PROM_H_
#define _PPC_BOOT_PROM_H_ #define _PPC_BOOT_PROM_H_
typedef void *phandle;
typedef void *ihandle;
extern int (*prom) (void *); extern int (*prom) (void *);
extern void *chosen_handle; extern phandle chosen_handle;
extern ihandle stdout;
extern void *stdin; int call_prom(const char *service, int nargs, int nret, ...);
extern void *stdout; int call_prom_ret(const char *service, int nargs, int nret,
extern void *stderr; unsigned int *rets, ...);
extern int write(void *handle, void *ptr, int nb); extern int write(void *handle, void *ptr, int nb);
extern int read(void *handle, void *ptr, int nb); extern void *claim(unsigned long virt, unsigned long size, unsigned long aln);
extern void exit(void);
extern void pause(void); static inline void exit(void)
extern void *finddevice(const char *); {
extern void *claim(unsigned long virt, unsigned long size, unsigned long align); call_prom("exit", 0, 0);
extern int getprop(void *phandle, const char *name, void *buf, int buflen); }
static inline phandle finddevice(const char *name)
{
return (phandle) call_prom("finddevice", 1, 1, name);
}
static inline int getprop(void *phandle, const char *name,
void *buf, int buflen)
{
return call_prom("getprop", 4, 1, phandle, name, buf, buflen);
}
#endif /* _PPC_BOOT_PROM_H_ */ #endif /* _PPC_BOOT_PROM_H_ */
/* IBM RS/6000 "XCOFF" file definitions for BFD.
Copyright (C) 1990, 1991 Free Software Foundation, Inc.
FIXME: Can someone provide a transliteration of this name into ASCII?
Using the following chars caused a compiler warning on HIUX (so I replaced
them with octal escapes), and isn't useful without an understanding of what
character set it is.
Written by Mimi Ph\373\364ng-Th\345o V\365 of IBM
and John Gilmore of Cygnus Support. */
/********************** FILE HEADER **********************/
struct external_filehdr {
char f_magic[2]; /* magic number */
char f_nscns[2]; /* number of sections */
char f_timdat[4]; /* time & date stamp */
char f_symptr[4]; /* file pointer to symtab */
char f_nsyms[4]; /* number of symtab entries */
char f_opthdr[2]; /* sizeof(optional hdr) */
char f_flags[2]; /* flags */
};
/* IBM RS/6000 */
#define U802WRMAGIC 0730 /* writeable text segments **chh** */
#define U802ROMAGIC 0735 /* readonly sharable text segments */
#define U802TOCMAGIC 0737 /* readonly text segments and TOC */
#define BADMAG(x) \
((x).f_magic != U802ROMAGIC && (x).f_magic != U802WRMAGIC && \
(x).f_magic != U802TOCMAGIC)
#define FILHDR struct external_filehdr
#define FILHSZ 20
/********************** AOUT "OPTIONAL HEADER" **********************/
typedef struct
{
unsigned char magic[2]; /* type of file */
unsigned char vstamp[2]; /* version stamp */
unsigned char tsize[4]; /* text size in bytes, padded to FW bdry */
unsigned char dsize[4]; /* initialized data " " */
unsigned char bsize[4]; /* uninitialized data " " */
unsigned char entry[4]; /* entry pt. */
unsigned char text_start[4]; /* base of text used for this file */
unsigned char data_start[4]; /* base of data used for this file */
unsigned char o_toc[4]; /* address of TOC */
unsigned char o_snentry[2]; /* section number of entry point */
unsigned char o_sntext[2]; /* section number of .text section */
unsigned char o_sndata[2]; /* section number of .data section */
unsigned char o_sntoc[2]; /* section number of TOC */
unsigned char o_snloader[2]; /* section number of .loader section */
unsigned char o_snbss[2]; /* section number of .bss section */
unsigned char o_algntext[2]; /* .text alignment */
unsigned char o_algndata[2]; /* .data alignment */
unsigned char o_modtype[2]; /* module type (??) */
unsigned char o_cputype[2]; /* cpu type */
unsigned char o_maxstack[4]; /* max stack size (??) */
unsigned char o_maxdata[4]; /* max data size (??) */
unsigned char o_resv2[12]; /* reserved */
}
AOUTHDR;
#define AOUTSZ 72
#define SMALL_AOUTSZ (28)
#define AOUTHDRSZ 72
#define RS6K_AOUTHDR_OMAGIC 0x0107 /* old: text & data writeable */
#define RS6K_AOUTHDR_NMAGIC 0x0108 /* new: text r/o, data r/w */
#define RS6K_AOUTHDR_ZMAGIC 0x010B /* paged: text r/o, both page-aligned */
/********************** SECTION HEADER **********************/
struct external_scnhdr {
char s_name[8]; /* section name */
char s_paddr[4]; /* physical address, aliased s_nlib */
char s_vaddr[4]; /* virtual address */
char s_size[4]; /* section size */
char s_scnptr[4]; /* file ptr to raw data for section */
char s_relptr[4]; /* file ptr to relocation */
char s_lnnoptr[4]; /* file ptr to line numbers */
char s_nreloc[2]; /* number of relocation entries */
char s_nlnno[2]; /* number of line number entries*/
char s_flags[4]; /* flags */
};
/*
* names of "special" sections
*/
#define _TEXT ".text"
#define _DATA ".data"
#define _BSS ".bss"
#define _PAD ".pad"
#define _LOADER ".loader"
#define SCNHDR struct external_scnhdr
#define SCNHSZ 40
/* XCOFF uses a special .loader section with type STYP_LOADER. */
#define STYP_LOADER 0x1000
/* XCOFF uses a special .debug section with type STYP_DEBUG. */
#define STYP_DEBUG 0x2000
/* XCOFF handles line number or relocation overflow by creating
another section header with STYP_OVRFLO set. */
#define STYP_OVRFLO 0x8000
/********************** LINE NUMBERS **********************/
/* 1 line number entry for every "breakpointable" source line in a section.
* Line numbers are grouped on a per function basis; first entry in a function
* grouping will have l_lnno = 0 and in place of physical address will be the
* symbol table index of the function name.
*/
struct external_lineno {
union {
char l_symndx[4]; /* function name symbol index, iff l_lnno == 0*/
char l_paddr[4]; /* (physical) address of line number */
} l_addr;
char l_lnno[2]; /* line number */
};
#define LINENO struct external_lineno
#define LINESZ 6
/********************** SYMBOLS **********************/
#define E_SYMNMLEN 8 /* # characters in a symbol name */
#define E_FILNMLEN 14 /* # characters in a file name */
#define E_DIMNUM 4 /* # array dimensions in auxiliary entry */
struct external_syment
{
union {
char e_name[E_SYMNMLEN];
struct {
char e_zeroes[4];
char e_offset[4];
} e;
} e;
char e_value[4];
char e_scnum[2];
char e_type[2];
char e_sclass[1];
char e_numaux[1];
};
#define N_BTMASK (017)
#define N_TMASK (060)
#define N_BTSHFT (4)
#define N_TSHIFT (2)
union external_auxent {
struct {
char x_tagndx[4]; /* str, un, or enum tag indx */
union {
struct {
char x_lnno[2]; /* declaration line number */
char x_size[2]; /* str/union/array size */
} x_lnsz;
char x_fsize[4]; /* size of function */
} x_misc;
union {
struct { /* if ISFCN, tag, or .bb */
char x_lnnoptr[4]; /* ptr to fcn line # */
char x_endndx[4]; /* entry ndx past block end */
} x_fcn;
struct { /* if ISARY, up to 4 dimen. */
char x_dimen[E_DIMNUM][2];
} x_ary;
} x_fcnary;
char x_tvndx[2]; /* tv index */
} x_sym;
union {
char x_fname[E_FILNMLEN];
struct {
char x_zeroes[4];
char x_offset[4];
} x_n;
} x_file;
struct {
char x_scnlen[4]; /* section length */
char x_nreloc[2]; /* # relocation entries */
char x_nlinno[2]; /* # line numbers */
} x_scn;
struct {
char x_tvfill[4]; /* tv fill value */
char x_tvlen[2]; /* length of .tv */
char x_tvran[2][2]; /* tv range */
} x_tv; /* info about .tv section (in auxent of symbol .tv)) */
struct {
unsigned char x_scnlen[4];
unsigned char x_parmhash[4];
unsigned char x_snhash[2];
unsigned char x_smtyp[1];
unsigned char x_smclas[1];
unsigned char x_stab[4];
unsigned char x_snstab[2];
} x_csect;
};
#define SYMENT struct external_syment
#define SYMESZ 18
#define AUXENT union external_auxent
#define AUXESZ 18
#define DBXMASK 0x80 /* for dbx storage mask */
#define SYMNAME_IN_DEBUG(symptr) ((symptr)->n_sclass & DBXMASK)
/********************** RELOCATION DIRECTIVES **********************/
struct external_reloc {
char r_vaddr[4];
char r_symndx[4];
char r_size[1];
char r_type[1];
};
#define RELOC struct external_reloc
#define RELSZ 10
#define DEFAULT_DATA_SECTION_ALIGNMENT 4
#define DEFAULT_BSS_SECTION_ALIGNMENT 4
#define DEFAULT_TEXT_SECTION_ALIGNMENT 4
/* For new sections we havn't heard of before */
#define DEFAULT_SECTION_ALIGNMENT 4
/*
* Copyright (C) Paul Mackerras 1997.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <stdarg.h>
#include <stddef.h>
#include "string.h"
#include "stdio.h"
#include "prom.h"
size_t strnlen(const char * s, size_t count)
{
const char *sc;
for (sc = s; count-- && *sc != '\0'; ++sc)
/* nothing */;
return sc - s;
}
extern unsigned int __div64_32(unsigned long long *dividend,
unsigned int divisor);
/* The unnecessary pointer compare is there
* to check for type safety (n must be 64bit)
*/
# define do_div(n,base) ({ \
unsigned int __base = (base); \
unsigned int __rem; \
(void)(((typeof((n)) *)0) == ((unsigned long long *)0)); \
if (((n) >> 32) == 0) { \
__rem = (unsigned int)(n) % __base; \
(n) = (unsigned int)(n) / __base; \
} else \
__rem = __div64_32(&(n), __base); \
__rem; \
})
static int skip_atoi(const char **s)
{
int i, c;
for (i = 0; '0' <= (c = **s) && c <= '9'; ++*s)
i = i*10 + c - '0';
return i;
}
#define ZEROPAD 1 /* pad with zero */
#define SIGN 2 /* unsigned/signed long */
#define PLUS 4 /* show plus */
#define SPACE 8 /* space if plus */
#define LEFT 16 /* left justified */
#define SPECIAL 32 /* 0x */
#define LARGE 64 /* use 'ABCDEF' instead of 'abcdef' */
static char * number(char * str, unsigned long long num, int base, int size, int precision, int type)
{
char c,sign,tmp[66];
const char *digits="0123456789abcdefghijklmnopqrstuvwxyz";
int i;
if (type & LARGE)
digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (type & LEFT)
type &= ~ZEROPAD;
if (base < 2 || base > 36)
return 0;
c = (type & ZEROPAD) ? '0' : ' ';
sign = 0;
if (type & SIGN) {
if ((signed long long)num < 0) {
sign = '-';
num = - (signed long long)num;
size--;
} else if (type & PLUS) {
sign = '+';
size--;
} else if (type & SPACE) {
sign = ' ';
size--;
}
}
if (type & SPECIAL) {
if (base == 16)
size -= 2;
else if (base == 8)
size--;
}
i = 0;
if (num == 0)
tmp[i++]='0';
else while (num != 0) {
tmp[i++] = digits[do_div(num, base)];
}
if (i > precision)
precision = i;
size -= precision;
if (!(type&(ZEROPAD+LEFT)))
while(size-->0)
*str++ = ' ';
if (sign)
*str++ = sign;
if (type & SPECIAL) {
if (base==8)
*str++ = '0';
else if (base==16) {
*str++ = '0';
*str++ = digits[33];
}
}
if (!(type & LEFT))
while (size-- > 0)
*str++ = c;
while (i < precision--)
*str++ = '0';
while (i-- > 0)
*str++ = tmp[i];
while (size-- > 0)
*str++ = ' ';
return str;
}
int vsprintf(char *buf, const char *fmt, va_list args)
{
int len;
unsigned long long num;
int i, base;
char * str;
const char *s;
int flags; /* flags to number() */
int field_width; /* width of output field */
int precision; /* min. # of digits for integers; max
number of chars for from string */
int qualifier; /* 'h', 'l', or 'L' for integer fields */
/* 'z' support added 23/7/1999 S.H. */
/* 'z' changed to 'Z' --davidm 1/25/99 */
for (str=buf ; *fmt ; ++fmt) {
if (*fmt != '%') {
*str++ = *fmt;
continue;
}
/* process flags */
flags = 0;
repeat:
++fmt; /* this also skips first '%' */
switch (*fmt) {
case '-': flags |= LEFT; goto repeat;
case '+': flags |= PLUS; goto repeat;
case ' ': flags |= SPACE; goto repeat;
case '#': flags |= SPECIAL; goto repeat;
case '0': flags |= ZEROPAD; goto repeat;
}
/* get field width */
field_width = -1;
if ('0' <= *fmt && *fmt <= '9')
field_width = skip_atoi(&fmt);
else if (*fmt == '*') {
++fmt;
/* it's the next argument */
field_width = va_arg(args, int);
if (field_width < 0) {
field_width = -field_width;
flags |= LEFT;
}
}
/* get the precision */
precision = -1;
if (*fmt == '.') {
++fmt;
if ('0' <= *fmt && *fmt <= '9')
precision = skip_atoi(&fmt);
else if (*fmt == '*') {
++fmt;
/* it's the next argument */
precision = va_arg(args, int);
}
if (precision < 0)
precision = 0;
}
/* get the conversion qualifier */
qualifier = -1;
if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L' || *fmt =='Z') {
qualifier = *fmt;
++fmt;
}
/* default base */
base = 10;
switch (*fmt) {
case 'c':
if (!(flags & LEFT))
while (--field_width > 0)
*str++ = ' ';
*str++ = (unsigned char) va_arg(args, int);
while (--field_width > 0)
*str++ = ' ';
continue;
case 's':
s = va_arg(args, char *);
if (!s)
s = "<NULL>";
len = strnlen(s, precision);
if (!(flags & LEFT))
while (len < field_width--)
*str++ = ' ';
for (i = 0; i < len; ++i)
*str++ = *s++;
while (len < field_width--)
*str++ = ' ';
continue;
case 'p':
if (field_width == -1) {
field_width = 2*sizeof(void *);
flags |= ZEROPAD;
}
str = number(str,
(unsigned long) va_arg(args, void *), 16,
field_width, precision, flags);
continue;
case 'n':
if (qualifier == 'l') {
long * ip = va_arg(args, long *);
*ip = (str - buf);
} else if (qualifier == 'Z') {
size_t * ip = va_arg(args, size_t *);
*ip = (str - buf);
} else {
int * ip = va_arg(args, int *);
*ip = (str - buf);
}
continue;
case '%':
*str++ = '%';
continue;
/* integer number formats - set up the flags and "break" */
case 'o':
base = 8;
break;
case 'X':
flags |= LARGE;
case 'x':
base = 16;
break;
case 'd':
case 'i':
flags |= SIGN;
case 'u':
break;
default:
*str++ = '%';
if (*fmt)
*str++ = *fmt;
else
--fmt;
continue;
}
if (qualifier == 'l') {
num = va_arg(args, unsigned long);
if (flags & SIGN)
num = (signed long) num;
} else if (qualifier == 'Z') {
num = va_arg(args, size_t);
} else if (qualifier == 'h') {
num = (unsigned short) va_arg(args, int);
if (flags & SIGN)
num = (signed short) num;
} else {
num = va_arg(args, unsigned int);
if (flags & SIGN)
num = (signed int) num;
}
str = number(str, num, base, field_width, precision, flags);
}
*str = '\0';
return str-buf;
}
int sprintf(char * buf, const char *fmt, ...)
{
va_list args;
int i;
va_start(args, fmt);
i=vsprintf(buf,fmt,args);
va_end(args);
return i;
}
static char sprint_buf[1024];
int
printf(const char *fmt, ...)
{
va_list args;
int n;
va_start(args, fmt);
n = vsprintf(sprint_buf, fmt, args);
va_end(args);
write(stdout, sprint_buf, n);
return n;
}
...@@ -7,10 +7,4 @@ extern int sprintf(char *buf, const char *fmt, ...); ...@@ -7,10 +7,4 @@ extern int sprintf(char *buf, const char *fmt, ...);
extern int vsprintf(char *buf, const char *fmt, va_list args); extern int vsprintf(char *buf, const char *fmt, va_list args);
extern int putc(int c, void *f);
extern int putchar(int c);
extern int getchar(void);
extern int fputs(char *str, void *f);
#endif /* _PPC_BOOT_STDIO_H_ */ #endif /* _PPC_BOOT_STDIO_H_ */
OUTPUT_ARCH(powerpc:common)
ENTRY(_start)
SECTIONS
{
. = (5*1024*1024);
_start = .;
.text :
{
*(.text)
*(.fixup)
}
_etext = .;
. = ALIGN(4096);
.data :
{
*(.rodata*)
*(.data*)
*(.sdata*)
__got2_start = .;
*(.got2)
__got2_end = .;
_vmlinux_start = .;
*(.kernel:vmlinux.strip)
_vmlinux_end = .;
_initrd_start = .;
*(.kernel:initrd)
_initrd_end = .;
}
. = ALIGN(4096);
_edata = .;
__bss_start = .;
.bss :
{
*(.sbss)
*(.bss)
}
_end = . ;
/DISCARD/ :
{
*(.comment)
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment