Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6e1af384 authored by Scott Wood's avatar Scott Wood Committed by Paul Mackerras
Browse files

[POWERPC] bootwrapper: Add dt_xlate_reg(), and use it to find serial registers.



dt_xlate_reg() uses the ranges properties of a node's parentage to find
the absolute physical address of the node's registers.

The ns16550 driver uses this when no virtual-reg property is found.

Signed-off-by: default avatarScott Wood <scottwood@freescale.com>
Signed-off-by: default avatarPaul Mackerras <paulus@samba.org>
parent dbf8eefa
Loading
Loading
Loading
Loading
+178 −0
Original line number Diff line number Diff line
@@ -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;
}
+7 −2
Original line number Diff line number Diff line
@@ -55,11 +55,16 @@ 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", &reg_base, sizeof(reg_base));
	if (n != sizeof(reg_base))
	if (n != sizeof(reg_base)) {
		if (!dt_xlate_reg(devp, 0, &reg_phys, NULL))
			return -1;

		reg_base = (void *)reg_phys;
	}

	n = getprop(devp, "reg-shift", &reg_shift, sizeof(reg_shift));
	if (n != sizeof(reg_shift))
		reg_shift = 0;
+2 −0
Original line number Diff line number Diff line
@@ -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)
{