| /* |
| * Copyright (C) ST-Ericsson SA 2011 |
| * Author: Maxime Coquelin <maxime.coquelin@stericsson.com> for ST-Ericsson. |
| * License terms: GNU General Public License (GPL), version 2 |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/mm.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/sort.h> |
| #include <linux/pasr.h> |
| #include <linux/debugfs.h> |
| |
| #include "helper.h" |
| |
| #define NR_DIES 8 |
| #define NR_INT 8 |
| |
| struct ddr_die { |
| phys_addr_t addr; |
| unsigned long size; |
| }; |
| |
| struct interleaved_area { |
| phys_addr_t addr1; |
| phys_addr_t addr2; |
| unsigned long size; |
| }; |
| |
| struct pasr_info { |
| int nr_dies; |
| struct ddr_die die[NR_DIES]; |
| |
| int nr_int; |
| struct interleaved_area int_area[NR_INT]; |
| }; |
| |
| static struct pasr_info __initdata pasr_info; |
| static struct pasr_map pasr_map; |
| unsigned long section_size; |
| unsigned int section_bit; |
| |
| static void add_ddr_die(phys_addr_t addr, unsigned long size); |
| static void add_interleaved_area(phys_addr_t a1, |
| phys_addr_t a2, unsigned long size); |
| |
| static int __init section_param(char *p) |
| { |
| section_size = memparse(p, &p); |
| section_bit = ffs(section_size) - 1; |
| |
| return 0; |
| } |
| early_param("section", section_param); |
| |
| static int __init ddr_die_param(char *p) |
| { |
| phys_addr_t start; |
| unsigned long size; |
| |
| size = memparse(p, &p); |
| |
| if (*p != '@') |
| goto err; |
| |
| start = memparse(p + 1, &p); |
| |
| add_ddr_die(start, size); |
| |
| return 0; |
| err: |
| return -EINVAL; |
| } |
| early_param("ddr_die", ddr_die_param); |
| |
| static int __init interleaved_param(char *p) |
| { |
| phys_addr_t start1, start2; |
| unsigned long size; |
| |
| size = memparse(p, &p); |
| |
| if (*p != '@') |
| goto err; |
| |
| start1 = memparse(p + 1, &p); |
| |
| if (*p != ':') |
| goto err; |
| |
| start2 = memparse(p + 1, &p); |
| |
| add_interleaved_area(start1, start2, size); |
| |
| return 0; |
| err: |
| return -EINVAL; |
| } |
| early_param("interleaved", interleaved_param); |
| |
| void __init add_ddr_die(phys_addr_t addr, unsigned long size) |
| { |
| BUG_ON(pasr_info.nr_dies >= NR_DIES); |
| |
| pasr_info.die[pasr_info.nr_dies].addr = addr; |
| pasr_info.die[pasr_info.nr_dies++].size = size; |
| } |
| |
| void __init add_interleaved_area(phys_addr_t a1, phys_addr_t a2, |
| unsigned long size) |
| { |
| BUG_ON(pasr_info.nr_int >= NR_INT); |
| |
| pasr_info.int_area[pasr_info.nr_int].addr1 = a1; |
| pasr_info.int_area[pasr_info.nr_int].addr2 = a2; |
| pasr_info.int_area[pasr_info.nr_int++].size = size; |
| } |
| |
| #ifdef DEBUG |
| static void __init pasr_print_info(struct pasr_info *info) |
| { |
| int i; |
| |
| pr_info("PASR information coherent\n"); |
| |
| |
| pr_info("DDR Dies layout:\n"); |
| pr_info("\tid - start address - end address\n"); |
| for (i = 0; i < info->nr_dies; i++) |
| pr_info("\t- %d : %#08x - %#08x\n", |
| i, (unsigned int)info->die[i].addr, |
| (unsigned int)(info->die[i].addr |
| + info->die[i].size - 1)); |
| |
| if (info->nr_int == 0) { |
| pr_info("No interleaved areas declared\n"); |
| return; |
| } |
| |
| pr_info("Interleaving layout:\n"); |
| pr_info("\tid - start @1 - end @2 : start @2 - end @2\n"); |
| for (i = 0; i < info->nr_int; i++) |
| pr_info("\t-%d - %#08x - %#08x : %#08x - %#08x\n" |
| , i |
| , (unsigned int)info->int_area[i].addr1 |
| , (unsigned int)(info->int_area[i].addr1 |
| + info->int_area[i].size - 1) |
| , (unsigned int)info->int_area[i].addr2 |
| , (unsigned int)(info->int_area[i].addr2 |
| + info->int_area[i].size - 1)); |
| } |
| #else |
| #define pasr_print_info(info) do {} while (0) |
| #endif /* DEBUG */ |
| |
| static int __init is_in_physmem(phys_addr_t addr, struct ddr_die *d) |
| { |
| return ((addr >= d->addr) && (addr <= d->addr + d->size - 1)); |
| } |
| |
| static int __init pasr_check_interleave_in_physmem(struct pasr_info *info, |
| struct interleaved_area *i) |
| { |
| struct ddr_die *d; |
| int j; |
| int err = 4; |
| |
| for (j = 0; j < info->nr_dies; j++) { |
| d = &info->die[j]; |
| |
| if (is_in_physmem(i->addr1, d)) |
| err--; |
| if (is_in_physmem(i->addr1 + i->size - 1, d)) |
| err--; |
| if (is_in_physmem(i->addr2, d)) |
| err--; |
| if (is_in_physmem(i->addr2 + i->size - 1, d)) |
| err--; |
| } |
| |
| return err; |
| } |
| |
| static int __init ddrdie_cmp(const void *_a, const void *_b) |
| { |
| const struct ddr_die *a = _a, *b = _b; |
| |
| return a->addr < b->addr ? -1 : a->addr > b->addr ? 1 : 0; |
| } |
| |
| static int __init interleaved_cmp(const void *_a, const void *_b) |
| { |
| const struct interleaved_area *a = _a, *b = _b; |
| |
| return a->addr1 < b->addr1 ? -1 : a->addr1 > b->addr1 ? 1 : 0; |
| } |
| |
| static int __init pasr_info_sanity_check(struct pasr_info *info) |
| { |
| int i; |
| |
| /* Check at least one physical chunk is defined */ |
| if (info->nr_dies == 0) { |
| pr_err("%s: No DDR dies declared in command line\n", __func__); |
| return -EINVAL; |
| } |
| |
| /* Sort DDR dies areas */ |
| sort(&info->die, info->nr_dies, |
| sizeof(info->die[0]), ddrdie_cmp, NULL); |
| |
| /* Physical layout checking */ |
| for (i = 0; i < info->nr_dies; i++) { |
| struct ddr_die *d1, *d2; |
| |
| d1 = &info->die[i]; |
| |
| if (d1->size == 0) { |
| pr_err("%s: DDR die at %#x has 0 size\n", |
| __func__, d1->addr); |
| return -EINVAL; |
| } |
| |
| /* Check die is aligned on section boundaries */ |
| if (((d1->addr & ~(section_size - 1)) != d1->addr) |
| || ((d1->size & ~(section_size - 1)) != d1->size)) { |
| pr_err("%s: DDR die at %#x (size %#lx) \ |
| is not aligned on section boundaries %#lx\n", |
| __func__, d1->addr, d1->size, section_size); |
| return -EINVAL; |
| } |
| |
| if (i == 0) |
| continue; |
| |
| /* Check areas are not overlapping */ |
| d2 = d1; |
| d1 = &info->die[i-1]; |
| if ((d1->addr + d1->size - 1) >= d2->addr) { |
| pr_err("%s: DDR dies at %#x and %#x are overlapping\n", |
| __func__, d1->addr, d2->addr); |
| return -EINVAL; |
| } |
| } |
| |
| /* Interleave layout checking */ |
| if (info->nr_int == 0) |
| goto out; |
| |
| /* Sort interleaved areas */ |
| sort(&info->int_area, info->nr_int, |
| sizeof(info->int_area[0]), interleaved_cmp, NULL); |
| |
| for (i = 0; i < info->nr_int; i++) { |
| struct interleaved_area *i1; |
| |
| i1 = &info->int_area[i]; |
| if (i1->size == 0) { |
| pr_err("%s: Interleaved area %#x/%#x has 0 size\n", |
| __func__, i1->addr1, i1->addr2); |
| return -EINVAL; |
| } |
| |
| /* Check area is aligned on section boundaries */ |
| if (((i1->addr1 & ~(section_size - 1)) != i1->addr1) |
| || ((i1->addr2 & ~(section_size - 1)) != i1->addr2) |
| || ((i1->size & ~(section_size - 1)) != i1->size)) { |
| pr_err("%s: Interleaved area at %#x/%#x (size %#lx) \ |
| is not aligned on section boundaries %#lx\n", |
| __func__, i1->addr1, i1->addr2, i1->size, |
| section_size); |
| return -EINVAL; |
| } |
| |
| /* Check interleaved areas are not overlapping */ |
| if ((i1->addr1 + i1->size - 1) >= i1->addr2) { |
| pr_err("%s: Interleaved areas %#x and \ |
| %#x are overlapping\n", |
| __func__, i1->addr1, i1->addr2); |
| return -EINVAL; |
| } |
| |
| /* Check the interleaved areas are in the physical areas */ |
| if (pasr_check_interleave_in_physmem(info, i1)) { |
| pr_err("%s: Interleaved area %#x/%#x \ |
| not in physical memory\n", |
| __func__, i1->addr1, i1->addr2); |
| return -EINVAL; |
| } |
| } |
| |
| out: |
| return 0; |
| } |
| |
| #ifdef DEBUG |
| static void __init pasr_print_map(struct pasr_map *map) |
| { |
| int i, j; |
| |
| if (!map) |
| goto out; |
| |
| pr_info("PASR map:\n"); |
| |
| for (i = 0; i < map->nr_dies; i++) { |
| struct pasr_die *die = &map->die[i]; |
| |
| pr_info("Die %d:\n", i); |
| for (j = 0; j < die->nr_sections; j++) { |
| struct pasr_section *s = &die->section[j]; |
| pr_info("\tSection %d: @ = %#08x, Pair = %s @%#08x\n" |
| , j, s->start, s->pair ? "Yes" : "No", |
| s->pair ? s->pair->start : 0); |
| } |
| } |
| out: |
| return; |
| } |
| #else |
| #define pasr_print_map(map) do {} while (0) |
| #endif /* DEBUG */ |
| |
| static int __init pasr_build_map(struct pasr_info *info, struct pasr_map *map) |
| { |
| int i, j; |
| struct pasr_die *die; |
| |
| map->nr_dies = info->nr_dies; |
| die = map->die; |
| |
| for (i = 0; i < info->nr_dies; i++) { |
| phys_addr_t addr = info->die[i].addr; |
| struct pasr_section *section = die[i].section; |
| |
| die[i].start = addr; |
| die[i].idx = i; |
| die[i].nr_sections = info->die[i].size >> section_bit; |
| |
| for (j = 0; j < die[i].nr_sections; j++) { |
| section[j].start = addr; |
| addr += section_size; |
| section[j].die = &die[i]; |
| } |
| } |
| |
| for (i = 0; i < info->nr_int; i++) { |
| struct interleaved_area *ia = &info->int_area[i]; |
| struct pasr_section *s1, *s2; |
| unsigned long offset = 0; |
| |
| for (j = 0; j < (ia->size >> section_bit); j++) { |
| s1 = pasr_addr2section(map, ia->addr1 + offset); |
| s2 = pasr_addr2section(map, ia->addr2 + offset); |
| if (!s1 || !s2) |
| return -EINVAL; |
| |
| offset += section_size; |
| |
| s1->pair = s2; |
| s2->pair = s1; |
| } |
| } |
| return 0; |
| } |
| |
| #ifdef CONFIG_DEBUG_FS |
| static struct dentry *rootdir; |
| |
| static int pasr_print_meminfo(struct seq_file *s, void *data) |
| { |
| struct pasr_map *map = &pasr_map; |
| unsigned int i, j; |
| |
| if (!map) |
| return 0; |
| |
| for (i = 0; i < map->nr_dies; i++) { |
| struct pasr_die *die = &map->die[i]; |
| seq_printf(s, "die %d\n", i); |
| for (j = 0; j < die->nr_sections; j++) { |
| struct pasr_section *section = &die->section[j]; |
| u64 percentage; |
| |
| percentage = (u64)section->free_size * 100; |
| do_div(percentage, section_size); |
| seq_printf(s, "section %d %lu %llu\n", j, section->free_size, |
| percentage); |
| } |
| } |
| return 0; |
| } |
| |
| static int meminfo_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, pasr_print_meminfo, inode->i_private); |
| } |
| |
| static const struct file_operations meminfo_fops = { |
| .open = meminfo_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static int __init pasr_init_debug(void) |
| { |
| struct dentry *d; |
| |
| rootdir = debugfs_create_dir("pasr", NULL); |
| if (!rootdir) |
| return -ENOMEM; |
| |
| d = debugfs_create_file("meminfo", S_IRUGO, rootdir, (void *)&pasr_map, |
| &meminfo_fops); |
| if (!d) |
| return -ENOMEM; |
| |
| return 0; |
| } |
| late_initcall(pasr_init_debug); |
| #endif |
| |
| int __init early_pasr_setup(void) |
| { |
| int ret; |
| |
| ret = pasr_info_sanity_check(&pasr_info); |
| if (ret) { |
| pr_err("PASR info sanity check failed (err %d)\n", ret); |
| return ret; |
| } |
| |
| pasr_print_info(&pasr_info); |
| |
| ret = pasr_build_map(&pasr_info, &pasr_map); |
| if (ret) { |
| pr_err("PASR build map failed (err %d)\n", ret); |
| return ret; |
| } |
| |
| pasr_print_map(&pasr_map); |
| |
| ret = pasr_init_core(&pasr_map); |
| |
| pr_debug("PASR: First stage init done.\n"); |
| |
| return ret; |
| } |
| |
| /* |
| * late_pasr_setup() has to be called after Linux allocator is |
| * initialized but before other CPUs are launched. |
| */ |
| int __init late_pasr_setup(void) |
| { |
| int i, j; |
| struct pasr_section *s; |
| |
| for_each_pasr_section(i, j, pasr_map, s) { |
| if (!s->lock) { |
| s->lock = kzalloc(sizeof(spinlock_t), GFP_KERNEL); |
| BUG_ON(!s->lock); |
| spin_lock_init(s->lock); |
| if (s->pair) |
| s->pair->lock = s->lock; |
| } |
| } |
| |
| pr_debug("PASR Second stage init done.\n"); |
| |
| return 0; |
| } |