Commit a83f9823 authored by David S. Miller's avatar David S. Miller

[SPARC]: Fix OF register translations under sub-PCI busses.

There is an implicit assumption in the code that ranges will translate
to something that can fit in 2 32-bit cells, or a 64-bit value.  For
certain kinds of things below PCI this isn't necessarily true.

Here is what the relevant OF device hierarchy looks like for one of
the serial controllers on an Ultra5:

    Node 0xf005f1e0
        ranges:      00000000.00000000.00000000.000001fe.01000000.00000000.01000000
                     01000000.00000000.00000000.000001fe.02000000.00000000.01000000
                     02000000.00000000.00000000.000001ff.00000000.00000001.00000000
                     03000000.00000000.00000000.000001ff.00000000.00000001.00000000
        device_type:  'pci'
        model:  'SUNW,sabre'

        Node 0xf005f9d4
            device_type:  'pci'
            model:  'SUNW,simba'

           Node 0xf0060d24
                ranges:  00000010.00000000 82010810.00000000.f0000000 01000000
			 00000014.00000000 82010814.00000000.f1000000 00800000
                name:  'ebus'

                Node 0xf0062dac
                    reg:  00000014.003083f8.00000008 --> 0x1ff.f13083f8
                    device_type:  'serial'
                    name:  'su'

So the correct translation here is:

1) Match "su" register to second ranges entry of 'ebus', which translates
   into a PCI triplet "82010814.00000000.f1000000" of size 00800000, which
   gives us "82010814.00000000.f13083f8".

2) Pass-through "SUNW,simba" since it lacks ranges property

3) Match "82010814.00000000.f13083f8" to third ranges property of PCI
   controller node 'SUNW,sabre', and we arrive at the final physical
   MMIO address of "0x1fff13083f8".

Due to the 2-cell assumption, we couldn't translate to a PCI 3-cell
value, and we couldn't perform a pass-thru on it either.

It was easiest to just stop splitting the ranges application operation
between two methods, ->map and ->translate, and just let ->map do all
the work.  That way it would work purely on 32-bit cell arrays instead
of having to "return" some value like a u64.

It's still not %100 correct because the out-of-range check is still
done using the 64 least significant bits of the range and address.
But it does work for all the cases I've thrown at it so far.
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 9bbd952e
...@@ -183,7 +183,7 @@ struct bus_type of_bus_type = { ...@@ -183,7 +183,7 @@ struct bus_type of_bus_type = {
}; };
EXPORT_SYMBOL(of_bus_type); EXPORT_SYMBOL(of_bus_type);
static inline u64 of_read_addr(u32 *cell, int size) static inline u64 of_read_addr(const u32 *cell, int size)
{ {
u64 r = 0; u64 r = 0;
while (size--) while (size--)
...@@ -209,8 +209,8 @@ struct of_bus { ...@@ -209,8 +209,8 @@ struct of_bus {
int (*match)(struct device_node *parent); int (*match)(struct device_node *parent);
void (*count_cells)(struct device_node *child, void (*count_cells)(struct device_node *child,
int *addrc, int *sizec); int *addrc, int *sizec);
u64 (*map)(u32 *addr, u32 *range, int na, int ns, int pna); int (*map)(u32 *addr, const u32 *range,
int (*translate)(u32 *addr, u64 offset, int na); int na, int ns, int pna);
unsigned int (*get_flags)(u32 *addr); unsigned int (*get_flags)(u32 *addr);
}; };
...@@ -224,27 +224,49 @@ static void of_bus_default_count_cells(struct device_node *dev, ...@@ -224,27 +224,49 @@ static void of_bus_default_count_cells(struct device_node *dev,
get_cells(dev, addrc, sizec); get_cells(dev, addrc, sizec);
} }
static u64 of_bus_default_map(u32 *addr, u32 *range, int na, int ns, int pna) /* Make sure the least significant 64-bits are in-range. Even
* for 3 or 4 cell values it is a good enough approximation.
*/
static int of_out_of_range(const u32 *addr, const u32 *base,
const u32 *size, int na, int ns)
{ {
u64 cp, s, da; u64 a = of_read_addr(addr, na);
u64 b = of_read_addr(base, na);
if (a < b)
return 1;
cp = of_read_addr(range, na); b += of_read_addr(size, ns);
s = of_read_addr(range + na + pna, ns); if (a >= b)
da = of_read_addr(addr, na); return 1;
if (da < cp || da >= (cp + s)) return 0;
return OF_BAD_ADDR;
return da - cp;
} }
static int of_bus_default_translate(u32 *addr, u64 offset, int na) static int of_bus_default_map(u32 *addr, const u32 *range,
int na, int ns, int pna)
{ {
u64 a = of_read_addr(addr, na); u32 result[OF_MAX_ADDR_CELLS];
memset(addr, 0, na * 4); int i;
a += offset;
if (na > 1) if (ns > 2) {
addr[na - 2] = a >> 32; printk("of_device: Cannot handle size cells (%d) > 2.", ns);
addr[na - 1] = a & 0xffffffffu; return -EINVAL;
}
if (of_out_of_range(addr, range, range + na + pna, na, ns))
return -EINVAL;
/* Start with the parent range base. */
memcpy(result, range + na, pna * 4);
/* Add in the child address offset. */
for (i = 0; i < na; i++)
result[pna - 1 - i] +=
(addr[na - 1 - i] -
range[na - 1 - i]);
memcpy(addr, result, pna * 4);
return 0; return 0;
} }
...@@ -254,14 +276,26 @@ static unsigned int of_bus_default_get_flags(u32 *addr) ...@@ -254,14 +276,26 @@ static unsigned int of_bus_default_get_flags(u32 *addr)
return IORESOURCE_MEM; return IORESOURCE_MEM;
} }
/* /*
* PCI bus specific translator * PCI bus specific translator
*/ */
static int of_bus_pci_match(struct device_node *np) static int of_bus_pci_match(struct device_node *np)
{ {
return !strcmp(np->type, "pci") || !strcmp(np->type, "pciex"); if (!strcmp(np->type, "pci") || !strcmp(np->type, "pciex")) {
/* Do not do PCI specific frobbing if the
* PCI bridge lacks a ranges property. We
* want to pass it through up to the next
* parent as-is, not with the PCI translate
* method which chops off the top address cell.
*/
if (!of_find_property(np, "ranges", NULL))
return 0;
return 1;
}
return 0;
} }
static void of_bus_pci_count_cells(struct device_node *np, static void of_bus_pci_count_cells(struct device_node *np,
...@@ -273,27 +307,32 @@ static void of_bus_pci_count_cells(struct device_node *np, ...@@ -273,27 +307,32 @@ static void of_bus_pci_count_cells(struct device_node *np,
*sizec = 2; *sizec = 2;
} }
static u64 of_bus_pci_map(u32 *addr, u32 *range, int na, int ns, int pna) static int of_bus_pci_map(u32 *addr, const u32 *range,
int na, int ns, int pna)
{ {
u64 cp, s, da; u32 result[OF_MAX_ADDR_CELLS];
int i;
/* Check address type match */ /* Check address type match */
if ((addr[0] ^ range[0]) & 0x03000000) if ((addr[0] ^ range[0]) & 0x03000000)
return OF_BAD_ADDR; return -EINVAL;
/* Read address values, skipping high cell */ if (of_out_of_range(addr + 1, range + 1, range + na + pna,
cp = of_read_addr(range + 1, na - 1); na - 1, ns))
s = of_read_addr(range + na + pna, ns); return -EINVAL;
da = of_read_addr(addr + 1, na - 1);
if (da < cp || da >= (cp + s)) /* Start with the parent range base. */
return OF_BAD_ADDR; memcpy(result, range + na, pna * 4);
return da - cp;
}
static int of_bus_pci_translate(u32 *addr, u64 offset, int na) /* Add in the child address offset, skipping high cell. */
{ for (i = 0; i < na - 1; i++)
return of_bus_default_translate(addr + 1, offset, na - 1); result[pna - 1 - i] +=
(addr[na - 1 - i] -
range[na - 1 - i]);
memcpy(addr, result, pna * 4);
return 0;
} }
static unsigned int of_bus_pci_get_flags(u32 *addr) static unsigned int of_bus_pci_get_flags(u32 *addr)
...@@ -332,16 +371,11 @@ static void of_bus_sbus_count_cells(struct device_node *child, ...@@ -332,16 +371,11 @@ static void of_bus_sbus_count_cells(struct device_node *child,
*sizec = 1; *sizec = 1;
} }
static u64 of_bus_sbus_map(u32 *addr, u32 *range, int na, int ns, int pna) static int of_bus_sbus_map(u32 *addr, const u32 *range, int na, int ns, int pna)
{ {
return of_bus_default_map(addr, range, na, ns, pna); return of_bus_default_map(addr, range, na, ns, pna);
} }
static int of_bus_sbus_translate(u32 *addr, u64 offset, int na)
{
return of_bus_default_translate(addr, offset, na);
}
static unsigned int of_bus_sbus_get_flags(u32 *addr) static unsigned int of_bus_sbus_get_flags(u32 *addr)
{ {
return IORESOURCE_MEM; return IORESOURCE_MEM;
...@@ -360,7 +394,6 @@ static struct of_bus of_busses[] = { ...@@ -360,7 +394,6 @@ static struct of_bus of_busses[] = {
.match = of_bus_pci_match, .match = of_bus_pci_match,
.count_cells = of_bus_pci_count_cells, .count_cells = of_bus_pci_count_cells,
.map = of_bus_pci_map, .map = of_bus_pci_map,
.translate = of_bus_pci_translate,
.get_flags = of_bus_pci_get_flags, .get_flags = of_bus_pci_get_flags,
}, },
/* SBUS */ /* SBUS */
...@@ -370,7 +403,6 @@ static struct of_bus of_busses[] = { ...@@ -370,7 +403,6 @@ static struct of_bus of_busses[] = {
.match = of_bus_sbus_match, .match = of_bus_sbus_match,
.count_cells = of_bus_sbus_count_cells, .count_cells = of_bus_sbus_count_cells,
.map = of_bus_sbus_map, .map = of_bus_sbus_map,
.translate = of_bus_sbus_translate,
.get_flags = of_bus_sbus_get_flags, .get_flags = of_bus_sbus_get_flags,
}, },
/* Default */ /* Default */
...@@ -380,7 +412,6 @@ static struct of_bus of_busses[] = { ...@@ -380,7 +412,6 @@ static struct of_bus of_busses[] = {
.match = NULL, .match = NULL,
.count_cells = of_bus_default_count_cells, .count_cells = of_bus_default_count_cells,
.map = of_bus_default_map, .map = of_bus_default_map,
.translate = of_bus_default_translate,
.get_flags = of_bus_default_get_flags, .get_flags = of_bus_default_get_flags,
}, },
}; };
...@@ -405,33 +436,34 @@ static int __init build_one_resource(struct device_node *parent, ...@@ -405,33 +436,34 @@ static int __init build_one_resource(struct device_node *parent,
u32 *ranges; u32 *ranges;
unsigned int rlen; unsigned int rlen;
int rone; int rone;
u64 offset = OF_BAD_ADDR;
ranges = of_get_property(parent, "ranges", &rlen); ranges = of_get_property(parent, "ranges", &rlen);
if (ranges == NULL || rlen == 0) { if (ranges == NULL || rlen == 0) {
offset = of_read_addr(addr, na); u32 result[OF_MAX_ADDR_CELLS];
memset(addr, 0, pna * 4); int i;
goto finish;
memset(result, 0, pna * 4);
for (i = 0; i < na; i++)
result[pna - 1 - i] =
addr[na - 1 - i];
memcpy(addr, result, pna * 4);
return 0;
} }
/* Now walk through the ranges */ /* Now walk through the ranges */
rlen /= 4; rlen /= 4;
rone = na + pna + ns; rone = na + pna + ns;
for (; rlen >= rone; rlen -= rone, ranges += rone) { for (; rlen >= rone; rlen -= rone, ranges += rone) {
offset = bus->map(addr, ranges, na, ns, pna); if (!bus->map(addr, ranges, na, ns, pna))
if (offset != OF_BAD_ADDR) return 0;
break;
} }
if (offset == OF_BAD_ADDR)
return 1;
memcpy(addr, ranges + na, 4 * pna);
finish: return 1;
/* Translate it into parent bus space */
return pbus->translate(addr, offset, pna);
} }
static int of_resource_verbose;
static void __init build_device_resources(struct of_device *op, static void __init build_device_resources(struct of_device *op,
struct device *parent) struct device *parent)
{ {
...@@ -497,7 +529,8 @@ static void __init build_device_resources(struct of_device *op, ...@@ -497,7 +529,8 @@ static void __init build_device_resources(struct of_device *op,
pbus = of_match_bus(pp); pbus = of_match_bus(pp);
pbus->count_cells(dp, &pna, &pns); pbus->count_cells(dp, &pna, &pns);
if (build_one_resource(dp, bus, pbus, addr, dna, dns, pna)) if (build_one_resource(dp, bus, pbus, addr,
dna, dns, pna))
break; break;
dna = pna; dna = pna;
...@@ -507,6 +540,12 @@ static void __init build_device_resources(struct of_device *op, ...@@ -507,6 +540,12 @@ static void __init build_device_resources(struct of_device *op,
build_res: build_res:
memset(r, 0, sizeof(*r)); memset(r, 0, sizeof(*r));
if (of_resource_verbose)
printk("%s reg[%d] -> %llx\n",
op->node->full_name, index,
result);
if (result != OF_BAD_ADDR) { if (result != OF_BAD_ADDR) {
r->start = result & 0xffffffff; r->start = result & 0xffffffff;
r->end = result + size - 1; r->end = result + size - 1;
...@@ -643,6 +682,18 @@ static int __init of_bus_driver_init(void) ...@@ -643,6 +682,18 @@ static int __init of_bus_driver_init(void)
postcore_initcall(of_bus_driver_init); postcore_initcall(of_bus_driver_init);
static int __init of_debug(char *str)
{
int val = 0;
get_option(&str, &val);
if (val & 1)
of_resource_verbose = 1;
return 1;
}
__setup("of_debug=", of_debug);
int of_register_driver(struct of_platform_driver *drv, struct bus_type *bus) int of_register_driver(struct of_platform_driver *drv, struct bus_type *bus)
{ {
/* initialize common driver fields */ /* initialize common driver fields */
......
This diff is collapsed.
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