| /* mdesc.c: Sun4V machine description handling. |
| * |
| * Copyright (C) 2007 David S. Miller <davem@davemloft.net> |
| */ |
| #include <linux/kernel.h> |
| #include <linux/types.h> |
| #include <linux/bootmem.h> |
| #include <linux/log2.h> |
| |
| #include <asm/hypervisor.h> |
| #include <asm/mdesc.h> |
| #include <asm/prom.h> |
| #include <asm/oplib.h> |
| #include <asm/smp.h> |
| |
| /* Unlike the OBP device tree, the machine description is a full-on |
| * DAG. An arbitrary number of ARCs are possible from one |
| * node to other nodes and thus we can't use the OBP device_node |
| * data structure to represent these nodes inside of the kernel. |
| * |
| * Actually, it isn't even a DAG, because there are back pointers |
| * which create cycles in the graph. |
| * |
| * mdesc_hdr and mdesc_elem describe the layout of the data structure |
| * we get from the Hypervisor. |
| */ |
| struct mdesc_hdr { |
| u32 version; /* Transport version */ |
| u32 node_sz; /* node block size */ |
| u32 name_sz; /* name block size */ |
| u32 data_sz; /* data block size */ |
| }; |
| |
| struct mdesc_elem { |
| u8 tag; |
| #define MD_LIST_END 0x00 |
| #define MD_NODE 0x4e |
| #define MD_NODE_END 0x45 |
| #define MD_NOOP 0x20 |
| #define MD_PROP_ARC 0x61 |
| #define MD_PROP_VAL 0x76 |
| #define MD_PROP_STR 0x73 |
| #define MD_PROP_DATA 0x64 |
| u8 name_len; |
| u16 resv; |
| u32 name_offset; |
| union { |
| struct { |
| u32 data_len; |
| u32 data_offset; |
| } data; |
| u64 val; |
| } d; |
| }; |
| |
| static struct mdesc_hdr *main_mdesc; |
| static struct mdesc_node *allnodes; |
| |
| static struct mdesc_node *allnodes_tail; |
| static unsigned int unique_id; |
| |
| static struct mdesc_node **mdesc_hash; |
| static unsigned int mdesc_hash_size; |
| |
| static inline unsigned int node_hashfn(u64 node) |
| { |
| return ((unsigned int) (node ^ (node >> 8) ^ (node >> 16))) |
| & (mdesc_hash_size - 1); |
| } |
| |
| static inline void hash_node(struct mdesc_node *mp) |
| { |
| struct mdesc_node **head = &mdesc_hash[node_hashfn(mp->node)]; |
| |
| mp->hash_next = *head; |
| *head = mp; |
| |
| if (allnodes_tail) { |
| allnodes_tail->allnodes_next = mp; |
| allnodes_tail = mp; |
| } else { |
| allnodes = allnodes_tail = mp; |
| } |
| } |
| |
| static struct mdesc_node *find_node(u64 node) |
| { |
| struct mdesc_node *mp = mdesc_hash[node_hashfn(node)]; |
| |
| while (mp) { |
| if (mp->node == node) |
| return mp; |
| |
| mp = mp->hash_next; |
| } |
| return NULL; |
| } |
| |
| struct property *md_find_property(const struct mdesc_node *mp, |
| const char *name, |
| int *lenp) |
| { |
| struct property *pp; |
| |
| for (pp = mp->properties; pp != 0; pp = pp->next) { |
| if (strcasecmp(pp->name, name) == 0) { |
| if (lenp) |
| *lenp = pp->length; |
| break; |
| } |
| } |
| return pp; |
| } |
| EXPORT_SYMBOL(md_find_property); |
| |
| /* |
| * Find a property with a given name for a given node |
| * and return the value. |
| */ |
| const void *md_get_property(const struct mdesc_node *mp, const char *name, |
| int *lenp) |
| { |
| struct property *pp = md_find_property(mp, name, lenp); |
| return pp ? pp->value : NULL; |
| } |
| EXPORT_SYMBOL(md_get_property); |
| |
| struct mdesc_node *md_find_node_by_name(struct mdesc_node *from, |
| const char *name) |
| { |
| struct mdesc_node *mp; |
| |
| mp = from ? from->allnodes_next : allnodes; |
| for (; mp != NULL; mp = mp->allnodes_next) { |
| if (strcmp(mp->name, name) == 0) |
| break; |
| } |
| return mp; |
| } |
| EXPORT_SYMBOL(md_find_node_by_name); |
| |
| static unsigned int mdesc_early_allocated; |
| |
| static void * __init mdesc_early_alloc(unsigned long size) |
| { |
| void *ret; |
| |
| ret = __alloc_bootmem(size, SMP_CACHE_BYTES, 0UL); |
| if (ret == NULL) { |
| prom_printf("MDESC: alloc of %lu bytes failed.\n", size); |
| prom_halt(); |
| } |
| |
| memset(ret, 0, size); |
| |
| mdesc_early_allocated += size; |
| |
| return ret; |
| } |
| |
| static unsigned int __init count_arcs(struct mdesc_elem *ep) |
| { |
| unsigned int ret = 0; |
| |
| ep++; |
| while (ep->tag != MD_NODE_END) { |
| if (ep->tag == MD_PROP_ARC) |
| ret++; |
| ep++; |
| } |
| return ret; |
| } |
| |
| static void __init mdesc_node_alloc(u64 node, struct mdesc_elem *ep, const char *names) |
| { |
| unsigned int num_arcs = count_arcs(ep); |
| struct mdesc_node *mp; |
| |
| mp = mdesc_early_alloc(sizeof(*mp) + |
| (num_arcs * sizeof(struct mdesc_arc))); |
| mp->name = names + ep->name_offset; |
| mp->node = node; |
| mp->unique_id = unique_id++; |
| mp->num_arcs = num_arcs; |
| |
| hash_node(mp); |
| } |
| |
| static inline struct mdesc_elem *node_block(struct mdesc_hdr *mdesc) |
| { |
| return (struct mdesc_elem *) (mdesc + 1); |
| } |
| |
| static inline void *name_block(struct mdesc_hdr *mdesc) |
| { |
| return ((void *) node_block(mdesc)) + mdesc->node_sz; |
| } |
| |
| static inline void *data_block(struct mdesc_hdr *mdesc) |
| { |
| return ((void *) name_block(mdesc)) + mdesc->name_sz; |
| } |
| |
| /* In order to avoid recursion (the graph can be very deep) we use a |
| * two pass algorithm. First we allocate all the nodes and hash them. |
| * Then we iterate over each node, filling in the arcs and properties. |
| */ |
| static void __init build_all_nodes(struct mdesc_hdr *mdesc) |
| { |
| struct mdesc_elem *start, *ep; |
| struct mdesc_node *mp; |
| const char *names; |
| void *data; |
| u64 last_node; |
| |
| start = ep = node_block(mdesc); |
| last_node = mdesc->node_sz / 16; |
| |
| names = name_block(mdesc); |
| |
| while (1) { |
| u64 node = ep - start; |
| |
| if (ep->tag == MD_LIST_END) |
| break; |
| |
| if (ep->tag != MD_NODE) { |
| prom_printf("MDESC: Inconsistent element list.\n"); |
| prom_halt(); |
| } |
| |
| mdesc_node_alloc(node, ep, names); |
| |
| if (ep->d.val >= last_node) { |
| printk("MDESC: Warning, early break out of node scan.\n"); |
| printk("MDESC: Next node [%lu] last_node [%lu].\n", |
| node, last_node); |
| break; |
| } |
| |
| ep = start + ep->d.val; |
| } |
| |
| data = data_block(mdesc); |
| for (mp = allnodes; mp; mp = mp->allnodes_next) { |
| struct mdesc_elem *ep = start + mp->node; |
| struct property **link = &mp->properties; |
| unsigned int this_arc = 0; |
| |
| ep++; |
| while (ep->tag != MD_NODE_END) { |
| switch (ep->tag) { |
| case MD_PROP_ARC: { |
| struct mdesc_node *target; |
| |
| if (this_arc >= mp->num_arcs) { |
| prom_printf("MDESC: ARC overrun [%u:%u]\n", |
| this_arc, mp->num_arcs); |
| prom_halt(); |
| } |
| target = find_node(ep->d.val); |
| if (!target) { |
| printk("MDESC: Warning, arc points to " |
| "missing node, ignoring.\n"); |
| break; |
| } |
| mp->arcs[this_arc].name = |
| (names + ep->name_offset); |
| mp->arcs[this_arc].arc = target; |
| this_arc++; |
| break; |
| } |
| |
| case MD_PROP_VAL: |
| case MD_PROP_STR: |
| case MD_PROP_DATA: { |
| struct property *p = mdesc_early_alloc(sizeof(*p)); |
| |
| p->unique_id = unique_id++; |
| p->name = (char *) names + ep->name_offset; |
| if (ep->tag == MD_PROP_VAL) { |
| p->value = &ep->d.val; |
| p->length = 8; |
| } else { |
| p->value = data + ep->d.data.data_offset; |
| p->length = ep->d.data.data_len; |
| } |
| *link = p; |
| link = &p->next; |
| break; |
| } |
| |
| case MD_NOOP: |
| break; |
| |
| default: |
| printk("MDESC: Warning, ignoring unknown tag type %02x\n", |
| ep->tag); |
| } |
| ep++; |
| } |
| } |
| } |
| |
| static unsigned int __init count_nodes(struct mdesc_hdr *mdesc) |
| { |
| struct mdesc_elem *ep = node_block(mdesc); |
| struct mdesc_elem *end; |
| unsigned int cnt = 0; |
| |
| end = ((void *)ep) + mdesc->node_sz; |
| while (ep < end) { |
| if (ep->tag == MD_NODE) |
| cnt++; |
| ep++; |
| } |
| return cnt; |
| } |
| |
| static void __init report_platform_properties(void) |
| { |
| struct mdesc_node *pn = md_find_node_by_name(NULL, "platform"); |
| const char *s; |
| const u64 *v; |
| |
| if (!pn) { |
| prom_printf("No platform node in machine-description.\n"); |
| prom_halt(); |
| } |
| |
| s = md_get_property(pn, "banner-name", NULL); |
| printk("PLATFORM: banner-name [%s]\n", s); |
| s = md_get_property(pn, "name", NULL); |
| printk("PLATFORM: name [%s]\n", s); |
| |
| v = md_get_property(pn, "hostid", NULL); |
| if (v) |
| printk("PLATFORM: hostid [%08lx]\n", *v); |
| v = md_get_property(pn, "serial#", NULL); |
| if (v) |
| printk("PLATFORM: serial# [%08lx]\n", *v); |
| v = md_get_property(pn, "stick-frequency", NULL); |
| printk("PLATFORM: stick-frequency [%08lx]\n", *v); |
| v = md_get_property(pn, "mac-address", NULL); |
| if (v) |
| printk("PLATFORM: mac-address [%lx]\n", *v); |
| v = md_get_property(pn, "watchdog-resolution", NULL); |
| if (v) |
| printk("PLATFORM: watchdog-resolution [%lu ms]\n", *v); |
| v = md_get_property(pn, "watchdog-max-timeout", NULL); |
| if (v) |
| printk("PLATFORM: watchdog-max-timeout [%lu ms]\n", *v); |
| v = md_get_property(pn, "max-cpus", NULL); |
| if (v) |
| printk("PLATFORM: max-cpus [%lu]\n", *v); |
| } |
| |
| static int inline find_in_proplist(const char *list, const char *match, int len) |
| { |
| while (len > 0) { |
| int l; |
| |
| if (!strcmp(list, match)) |
| return 1; |
| l = strlen(list) + 1; |
| list += l; |
| len -= l; |
| } |
| return 0; |
| } |
| |
| static void __init fill_in_one_cache(cpuinfo_sparc *c, struct mdesc_node *mp) |
| { |
| const u64 *level = md_get_property(mp, "level", NULL); |
| const u64 *size = md_get_property(mp, "size", NULL); |
| const u64 *line_size = md_get_property(mp, "line-size", NULL); |
| const char *type; |
| int type_len; |
| |
| type = md_get_property(mp, "type", &type_len); |
| |
| switch (*level) { |
| case 1: |
| if (find_in_proplist(type, "instn", type_len)) { |
| c->icache_size = *size; |
| c->icache_line_size = *line_size; |
| } else if (find_in_proplist(type, "data", type_len)) { |
| c->dcache_size = *size; |
| c->dcache_line_size = *line_size; |
| } |
| break; |
| |
| case 2: |
| c->ecache_size = *size; |
| c->ecache_line_size = *line_size; |
| break; |
| |
| default: |
| break; |
| } |
| |
| if (*level == 1) { |
| unsigned int i; |
| |
| for (i = 0; i < mp->num_arcs; i++) { |
| struct mdesc_node *t = mp->arcs[i].arc; |
| |
| if (strcmp(mp->arcs[i].name, "fwd")) |
| continue; |
| |
| if (!strcmp(t->name, "cache")) |
| fill_in_one_cache(c, t); |
| } |
| } |
| } |
| |
| static void __init mark_core_ids(struct mdesc_node *mp, int core_id) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < mp->num_arcs; i++) { |
| struct mdesc_node *t = mp->arcs[i].arc; |
| const u64 *id; |
| |
| if (strcmp(mp->arcs[i].name, "back")) |
| continue; |
| |
| if (!strcmp(t->name, "cpu")) { |
| id = md_get_property(t, "id", NULL); |
| if (*id < NR_CPUS) |
| cpu_data(*id).core_id = core_id; |
| } else { |
| unsigned int j; |
| |
| for (j = 0; j < t->num_arcs; j++) { |
| struct mdesc_node *n = t->arcs[j].arc; |
| |
| if (strcmp(t->arcs[j].name, "back")) |
| continue; |
| |
| if (strcmp(n->name, "cpu")) |
| continue; |
| |
| id = md_get_property(n, "id", NULL); |
| if (*id < NR_CPUS) |
| cpu_data(*id).core_id = core_id; |
| } |
| } |
| } |
| } |
| |
| static void __init set_core_ids(void) |
| { |
| struct mdesc_node *mp; |
| int idx; |
| |
| idx = 1; |
| md_for_each_node_by_name(mp, "cache") { |
| const u64 *level = md_get_property(mp, "level", NULL); |
| const char *type; |
| int len; |
| |
| if (*level != 1) |
| continue; |
| |
| type = md_get_property(mp, "type", &len); |
| if (!find_in_proplist(type, "instn", len)) |
| continue; |
| |
| mark_core_ids(mp, idx); |
| |
| idx++; |
| } |
| } |
| |
| static void __init mark_proc_ids(struct mdesc_node *mp, int proc_id) |
| { |
| int i; |
| |
| for (i = 0; i < mp->num_arcs; i++) { |
| struct mdesc_node *t = mp->arcs[i].arc; |
| const u64 *id; |
| |
| if (strcmp(mp->arcs[i].name, "back")) |
| continue; |
| |
| if (strcmp(t->name, "cpu")) |
| continue; |
| |
| id = md_get_property(t, "id", NULL); |
| if (*id < NR_CPUS) |
| cpu_data(*id).proc_id = proc_id; |
| } |
| } |
| |
| static void __init __set_proc_ids(const char *exec_unit_name) |
| { |
| struct mdesc_node *mp; |
| int idx; |
| |
| idx = 0; |
| md_for_each_node_by_name(mp, exec_unit_name) { |
| const char *type; |
| int len; |
| |
| type = md_get_property(mp, "type", &len); |
| if (!find_in_proplist(type, "int", len) && |
| !find_in_proplist(type, "integer", len)) |
| continue; |
| |
| mark_proc_ids(mp, idx); |
| |
| idx++; |
| } |
| } |
| |
| static void __init set_proc_ids(void) |
| { |
| __set_proc_ids("exec_unit"); |
| __set_proc_ids("exec-unit"); |
| } |
| |
| static void __init get_one_mondo_bits(const u64 *p, unsigned int *mask, unsigned char def) |
| { |
| u64 val; |
| |
| if (!p) |
| goto use_default; |
| val = *p; |
| |
| if (!val || val >= 64) |
| goto use_default; |
| |
| *mask = ((1U << val) * 64U) - 1U; |
| return; |
| |
| use_default: |
| *mask = ((1U << def) * 64U) - 1U; |
| } |
| |
| static void __init get_mondo_data(struct mdesc_node *mp, struct trap_per_cpu *tb) |
| { |
| const u64 *val; |
| |
| val = md_get_property(mp, "q-cpu-mondo-#bits", NULL); |
| get_one_mondo_bits(val, &tb->cpu_mondo_qmask, 7); |
| |
| val = md_get_property(mp, "q-dev-mondo-#bits", NULL); |
| get_one_mondo_bits(val, &tb->dev_mondo_qmask, 7); |
| |
| val = md_get_property(mp, "q-resumable-#bits", NULL); |
| get_one_mondo_bits(val, &tb->resum_qmask, 6); |
| |
| val = md_get_property(mp, "q-nonresumable-#bits", NULL); |
| get_one_mondo_bits(val, &tb->nonresum_qmask, 2); |
| } |
| |
| static void __init mdesc_fill_in_cpu_data(void) |
| { |
| struct mdesc_node *mp; |
| |
| ncpus_probed = 0; |
| md_for_each_node_by_name(mp, "cpu") { |
| const u64 *id = md_get_property(mp, "id", NULL); |
| const u64 *cfreq = md_get_property(mp, "clock-frequency", NULL); |
| struct trap_per_cpu *tb; |
| cpuinfo_sparc *c; |
| unsigned int i; |
| int cpuid; |
| |
| ncpus_probed++; |
| |
| cpuid = *id; |
| |
| #ifdef CONFIG_SMP |
| if (cpuid >= NR_CPUS) |
| continue; |
| #else |
| /* On uniprocessor we only want the values for the |
| * real physical cpu the kernel booted onto, however |
| * cpu_data() only has one entry at index 0. |
| */ |
| if (cpuid != real_hard_smp_processor_id()) |
| continue; |
| cpuid = 0; |
| #endif |
| |
| c = &cpu_data(cpuid); |
| c->clock_tick = *cfreq; |
| |
| tb = &trap_block[cpuid]; |
| get_mondo_data(mp, tb); |
| |
| for (i = 0; i < mp->num_arcs; i++) { |
| struct mdesc_node *t = mp->arcs[i].arc; |
| unsigned int j; |
| |
| if (strcmp(mp->arcs[i].name, "fwd")) |
| continue; |
| |
| if (!strcmp(t->name, "cache")) { |
| fill_in_one_cache(c, t); |
| continue; |
| } |
| |
| for (j = 0; j < t->num_arcs; j++) { |
| struct mdesc_node *n; |
| |
| n = t->arcs[j].arc; |
| if (strcmp(t->arcs[j].name, "fwd")) |
| continue; |
| |
| if (!strcmp(n->name, "cache")) |
| fill_in_one_cache(c, n); |
| } |
| } |
| |
| #ifdef CONFIG_SMP |
| cpu_set(cpuid, cpu_present_map); |
| cpu_set(cpuid, phys_cpu_present_map); |
| #endif |
| |
| c->core_id = 0; |
| c->proc_id = -1; |
| } |
| |
| #ifdef CONFIG_SMP |
| sparc64_multi_core = 1; |
| #endif |
| |
| set_core_ids(); |
| set_proc_ids(); |
| |
| smp_fill_in_sib_core_maps(); |
| } |
| |
| void __init sun4v_mdesc_init(void) |
| { |
| unsigned long len, real_len, status; |
| |
| (void) sun4v_mach_desc(0UL, 0UL, &len); |
| |
| printk("MDESC: Size is %lu bytes.\n", len); |
| |
| main_mdesc = mdesc_early_alloc(len); |
| |
| status = sun4v_mach_desc(__pa(main_mdesc), len, &real_len); |
| if (status != HV_EOK || real_len > len) { |
| prom_printf("sun4v_mach_desc fails, err(%lu), " |
| "len(%lu), real_len(%lu)\n", |
| status, len, real_len); |
| prom_halt(); |
| } |
| |
| len = count_nodes(main_mdesc); |
| printk("MDESC: %lu nodes.\n", len); |
| |
| len = roundup_pow_of_two(len); |
| |
| mdesc_hash = mdesc_early_alloc(len * sizeof(struct mdesc_node *)); |
| mdesc_hash_size = len; |
| |
| printk("MDESC: Hash size %lu entries.\n", len); |
| |
| build_all_nodes(main_mdesc); |
| |
| printk("MDESC: Built graph with %u bytes of memory.\n", |
| mdesc_early_allocated); |
| |
| report_platform_properties(); |
| mdesc_fill_in_cpu_data(); |
| } |