diff options
-rw-r--r-- | arch/powerpc/boot/devtree.c | 178 | ||||
-rw-r--r-- | arch/powerpc/boot/ns16550.c | 9 | ||||
-rw-r--r-- | arch/powerpc/boot/ops.h | 2 |
3 files changed, 187 insertions, 2 deletions
diff --git a/arch/powerpc/boot/devtree.c b/arch/powerpc/boot/devtree.c index 708cadebeb4..23492d7fb55 100644 --- a/arch/powerpc/boot/devtree.c +++ b/arch/powerpc/boot/devtree.c @@ -109,3 +109,181 @@ void __dt_fixup_mac_addresses(u32 startindex, ...) } va_end(ap); } + +#define MAX_ADDR_CELLS 4 +#define MAX_RANGES 8 + +static void get_reg_format(void *node, u32 *naddr, u32 *nsize) +{ + if (getprop(node, "#address-cells", naddr, 4) != 4) + *naddr = 2; + if (getprop(node, "#size-cells", nsize, 4) != 4) + *nsize = 1; +} + +static void copy_val(u32 *dest, u32 *src, int naddr) +{ + memset(dest, 0, (MAX_ADDR_CELLS - naddr) * 4); + memcpy(dest, src, naddr * 4); +} + +static int sub_reg(u32 *reg, u32 *sub) +{ + int i, borrow = 0; + + for (i = 0; i < MAX_ADDR_CELLS; i++) { + int prev_borrow = borrow; + borrow = reg[i] < sub[i] + prev_borrow; + reg[i] -= sub[i] + prev_borrow; + } + + return !borrow; +} + +static int add_reg(u32 *reg, u32 *add) +{ + int i, carry = 0; + + for (i = 0; i < MAX_ADDR_CELLS; i++) { + u64 tmp = (u64)reg[i] + add[i] + carry; + carry = tmp >> 32; + reg[i] = (u32)tmp; + } + + return !carry; +} + +/* It is assumed that if the first byte of reg fits in a + * range, then the whole reg block fits. + */ +static int compare_reg(u32 *reg, u32 *range, u32 *rangesize) +{ + int i; + u32 end; + + for (i = 0; i < MAX_ADDR_CELLS; i++) { + if (reg[i] < range[i]) + return 0; + if (reg[i] > range[i]) + break; + } + + for (i = 0; i < MAX_ADDR_CELLS; i++) { + end = range[i] + rangesize[i]; + + if (reg[i] < end) + break; + if (reg[i] > end) + return 0; + } + + return reg[i] != end; +} + +/* reg must be MAX_ADDR_CELLS */ +static int find_range(u32 *reg, u32 *ranges, int nregaddr, + int naddr, int nsize, int buflen) +{ + int nrange = nregaddr + naddr + nsize; + int i; + + for (i = 0; i + nrange <= buflen; i += nrange) { + u32 range_addr[MAX_ADDR_CELLS]; + u32 range_size[MAX_ADDR_CELLS]; + + copy_val(range_addr, ranges + i, naddr); + copy_val(range_size, ranges + i + nregaddr + naddr, nsize); + + if (compare_reg(reg, range_addr, range_size)) + return i; + } + + return -1; +} + +/* Currently only generic buses without special encodings are supported. + * In particular, PCI is not supported. Also, only the beginning of the + * reg block is tracked; size is ignored except in ranges. + */ +int dt_xlate_reg(void *node, int res, unsigned long *addr, + unsigned long *size) +{ + u32 last_addr[MAX_ADDR_CELLS]; + u32 this_addr[MAX_ADDR_CELLS]; + u32 buf[MAX_ADDR_CELLS * MAX_RANGES * 3]; + void *parent; + u64 ret_addr, ret_size; + u32 naddr, nsize, prev_naddr; + int buflen, offset; + + parent = get_parent(node); + if (!parent) + return 0; + + get_reg_format(parent, &naddr, &nsize); + + if (nsize > 2) + return 0; + + buflen = getprop(node, "reg", buf, sizeof(buf)) / 4; + offset = (naddr + nsize) * res; + + if (buflen < offset + naddr + nsize) + return 0; + + copy_val(last_addr, buf + offset, naddr); + + ret_size = buf[offset + naddr]; + if (nsize == 2) { + ret_size <<= 32; + ret_size |= buf[offset + naddr + 1]; + } + + while ((node = get_parent(node))) { + prev_naddr = naddr; + + get_reg_format(node, &naddr, &nsize); + + buflen = getprop(node, "ranges", buf, sizeof(buf)); + if (buflen < 0) + continue; + if (buflen > sizeof(buf)) + return 0; + + offset = find_range(last_addr, buf, prev_naddr, + naddr, nsize, buflen / 4); + + if (offset < 0) + return 0; + + copy_val(this_addr, buf + offset, prev_naddr); + + if (!sub_reg(last_addr, this_addr)) + return 0; + + copy_val(this_addr, buf + offset + prev_naddr, naddr); + + if (!add_reg(last_addr, this_addr)) + return 0; + } + + if (naddr > 2) + return 0; + + ret_addr = last_addr[0]; + if (naddr == 2) { + ret_addr <<= 32; + ret_addr |= last_addr[1]; + } + + if (sizeof(void *) == 4 && + (ret_addr >= 0x100000000ULL || ret_size > 0x100000000ULL || + ret_addr + ret_size > 0x100000000ULL)) + return 0; + + *addr = ret_addr; + if (size) + *size = ret_size; + + return 1; +} diff --git a/arch/powerpc/boot/ns16550.c b/arch/powerpc/boot/ns16550.c index 1ffe72e35cd..f8f1b2f3141 100644 --- a/arch/powerpc/boot/ns16550.c +++ b/arch/powerpc/boot/ns16550.c @@ -55,10 +55,15 @@ static u8 ns16550_tstc(void) int ns16550_console_init(void *devp, struct serial_console_data *scdp) { int n; + unsigned long reg_phys; n = getprop(devp, "virtual-reg", ®_base, sizeof(reg_base)); - if (n != sizeof(reg_base)) - return -1; + if (n != sizeof(reg_base)) { + if (!dt_xlate_reg(devp, 0, ®_phys, NULL)) + return -1; + + reg_base = (void *)reg_phys; + } n = getprop(devp, "reg-shift", ®_shift, sizeof(reg_shift)); if (n != sizeof(reg_shift)) diff --git a/arch/powerpc/boot/ops.h b/arch/powerpc/boot/ops.h index 8008d612402..fbd9030d660 100644 --- a/arch/powerpc/boot/ops.h +++ b/arch/powerpc/boot/ops.h @@ -82,6 +82,8 @@ int ns16550_console_init(void *devp, struct serial_console_data *scdp); void *simple_alloc_init(char *base, u32 heap_size, u32 granularity, u32 max_allocs); extern void flush_cache(void *, unsigned long); +int dt_xlate_reg(void *node, int res, unsigned long *addr, + unsigned long *size); static inline void *finddevice(const char *name) { |