/* * fs2dt: creates a flattened device-tree * * Copyright (C) 2004,2005 Milton D Miller II, IBM Corporation * Copyright (C) 2005 R Sharada (sharada@in.ibm.com), IBM Corporation * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation (version 2 of the License). * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "kexec.h" #include "fs2dt.h" #include "libfdt/fdt.h" #define MAXPATH 1024 /* max path name length */ #define NAMESPACE 16384 /* max bytes for property names */ #define INIT_TREE_WORDS 65536 /* Initial num words for prop values */ #define MEMRESERVE 256 /* max number of reserved memory blocks */ #define MEM_RANGE_CHUNK_SZ 2048 /* Initial num dwords for mem ranges */ static char pathname[MAXPATH]; static char propnames[NAMESPACE] = { 0 }; static unsigned *dt_base, *dt; static unsigned int dt_cur_size; static unsigned long long mem_rsrv[2*MEMRESERVE] = { 0ULL, 0ULL }; static int crash_param = 0; static char local_cmdline[COMMAND_LINE_SIZE] = { "" }; extern unsigned char reuse_initrd; /* Used for enabling printing message from purgatory code * Only has implemented for PPC64 */ int my_debug; int dt_no_old_root; /* This provides the behaviour of hte existing ppc64 implementation */ static void pad_structure_block(size_t len) { #ifdef NEED_STRUCTURE_BLOCK_EXTRA_PAD if ((len >= 8) && ((unsigned long)dt & 0x4)) dt++; #endif } /* Before we add something to the dt, reserve N words using this. * If there isn't enough room, it's realloced -- and you don't overflow and * splat bits of your heap. */ static void dt_reserve(unsigned **dt_ptr, unsigned words) { unsigned int sz = INIT_TREE_WORDS; if (sz < words) sz = words; if (((*dt_ptr - dt_base) + words) >= dt_cur_size) { int offset; unsigned int new_size = dt_cur_size + sz; unsigned *new_dt = realloc(dt_base, new_size*4); if (!new_dt) die("unrecoverable error: Can't realloc %d bytes for " "device tree\n", new_size*4); offset = *dt_ptr - dt_base; dt_base = new_dt; dt_cur_size = new_size; *dt_ptr = dt_base + offset; memset(*dt_ptr, 0, (new_size - offset)*4); } } void reserve(unsigned long long where, unsigned long long length) { size_t offset; for (offset = 0; be64_to_cpu(mem_rsrv[offset + 1]); offset += 2) ; if (offset + 4 >= 2 * MEMRESERVE) die("unrecoverable error: exhasuted reservation meta data\n"); mem_rsrv[offset] = cpu_to_be64(where); mem_rsrv[offset + 1] = cpu_to_be64(length); mem_rsrv[offset + 2] = mem_rsrv[offset + 3] = cpu_to_be64(0); } /* look for properties we need to reserve memory space for */ static void checkprop(char *name, unsigned *data, int len) { static unsigned long long base, size, end; if ((data == NULL) && (base || size || end)) die("unrecoverable error: no property data"); else if (!strcmp(name, "linux,rtas-base")) base = be32_to_cpu(*data); else if (!strcmp(name, "opal-base-address")) base = be64_to_cpu(*(unsigned long long *)data); else if (!strcmp(name, "opal-runtime-size")) size = be64_to_cpu(*(unsigned long long *)data); else if (!strcmp(name, "linux,tce-base")) base = be64_to_cpu(*(unsigned long long *) data); else if (!strcmp(name, "rtas-size") || !strcmp(name, "linux,tce-size")) size = be32_to_cpu(*data); else if (reuse_initrd && !strcmp(name, "linux,initrd-start")) { if (len == 8) base = be64_to_cpu(*(unsigned long long *) data); else base = be32_to_cpu(*data); } else if (reuse_initrd && !strcmp(name, "linux,initrd-end")) { if (len == 8) end = be64_to_cpu(*(unsigned long long *) data); else end = be32_to_cpu(*data); } if (size && end) die("unrecoverable error: size and end set at same time\n"); if (base && size) { reserve(base, size); base = size = 0; } if (base && end) { reserve(base, end-base); base = end = 0; } } /* * return the property index for a property name, creating a new one * if needed. */ static unsigned propnum(const char *name) { unsigned offset = 0; while(propnames[offset]) if (strcmp(name, propnames+offset)) offset += strlen(propnames+offset)+1; else return offset; if (NAMESPACE - offset < strlen(name) + 1) die("unrecoverable error: propnames overrun\n"); strcpy(propnames+offset, name); return offset; } /* * Add ranges by comparing 'base' and 'end' addresses with usable * memory ranges. Returns the number of ranges added. Each range added * increments 'idx' by 2. */ static uint64_t add_ranges(uint64_t **ranges, int *ranges_size, int idx, uint64_t base, uint64_t end) { uint64_t loc_base, loc_end, rngs_cnt = 0; size_t range; int add = 0; for (range = 0; range < usablemem_rgns.size; range++) { loc_base = usablemem_rgns.ranges[range].start; loc_end = usablemem_rgns.ranges[range].end; if (loc_base >= base && loc_end <= end) { add = 1; } else if (base < loc_end && end > loc_base) { if (loc_base < base) loc_base = base; if (loc_end > end) loc_end = end; add = 1; } if (add) { if (idx >= ((*ranges_size) - 2)) { (*ranges_size) += MEM_RANGE_CHUNK_SZ; *ranges = realloc(*ranges, (*ranges_size)*8); if (!(*ranges)) die("unrecoverable error: can't realloc" "%d bytes for ranges.\n", (*ranges_size)*8); } (*ranges)[idx++] = cpu_to_be64(loc_base); (*ranges)[idx++] = cpu_to_be64(loc_end - loc_base); rngs_cnt++; } } return rngs_cnt; } #ifdef HAVE_DYNAMIC_MEMORY static void add_dyn_reconf_usable_mem_property__(int fd) { char fname[MAXPATH], *bname; char buf[32]; uint32_t lmbs_in_set = 1; uint64_t *ranges; int ranges_size = MEM_RANGE_CHUNK_SZ; uint64_t base, end, rngs_cnt; size_t i, j; int rlen = 0; int tmp_indx; strcpy(fname, pathname); bname = strrchr(fname, '/'); bname[0] = '\0'; bname = strrchr(fname, '/'); if (strncmp(bname, "/ibm,dynamic-reconfiguration-memory", 36)) return; if (lseek(fd, 4, SEEK_SET) < 0) die("unrecoverable error: error seeking in \"%s\": %s\n", pathname, strerror(errno)); ranges = malloc(ranges_size*8); if (!ranges) die("unrecoverable error: can't alloc %d bytes for ranges.\n", ranges_size*8); rlen = 0; for (i = 0; i < num_of_lmb_sets; i++) { if (read(fd, buf, LMB_ENTRY_SIZE) < 0) die("unrecoverable error: error reading \"%s\": %s\n", pathname, strerror(errno)); /* * If the property is ibm,dynamic-memory-v2, the first 4 bytes * tell the number of sequential LMBs in this entry. Else, if * the property is ibm,dynamic-memory, each entry represents * one LMB. Make sure to add an entry for each LMB as kernel * looks for a counter for every LMB. */ if (is_dyn_mem_v2) lmbs_in_set = be32_to_cpu(((unsigned int *)buf)[0]); base = be64_to_cpu(*((uint64_t *)&buf[DRCONF_ADDR])); for (j = 0; j < lmbs_in_set; j++) { end = base + lmb_size; if (~0ULL - base < end) { die("unrecoverable error: mem property" " overflow\n"); } tmp_indx = rlen++; rngs_cnt = add_ranges(&ranges, &ranges_size, rlen, base, end); if (rngs_cnt == 0) { /* We still need to add a counter for every LMB * because the kernel parsing code is dumb. We * just have a zero in this case, with no * following base/len. */ ranges[tmp_indx] = 0; /* rlen is already just tmp_indx+1 as we didn't * write anything. Check array size here, as we * will probably go on writing zeros for a while */ if (rlen >= (ranges_size-1)) { ranges_size += MEM_RANGE_CHUNK_SZ; ranges = realloc(ranges, ranges_size*8); if (!ranges) die("unrecoverable error: can't" " realloc %d bytes for" " ranges.\n", ranges_size*8); } } else { /* Store the count of (base, size) duple */ ranges[tmp_indx] = cpu_to_be64(rngs_cnt); rlen += rngs_cnt * 2; } base = end; } } rlen = rlen * sizeof(uint64_t); /* * Add linux,drconf-usable-memory property. */ dt_reserve(&dt, 4+((rlen + 3)/4)); *dt++ = cpu_to_be32(FDT_PROP); *dt++ = cpu_to_be32(rlen); *dt++ = cpu_to_be32(propnum("linux,drconf-usable-memory")); pad_structure_block(rlen); memcpy(dt, ranges, rlen); free(ranges); dt += (rlen + 3)/4; } static void add_dyn_reconf_usable_mem_property(struct dirent *dp, int fd) { if ((!strcmp(dp->d_name, "ibm,dynamic-memory-v2") || !strcmp(dp->d_name, "ibm,dynamic-memory")) && usablemem_rgns.size) add_dyn_reconf_usable_mem_property__(fd); } #else static void add_dyn_reconf_usable_mem_property(struct dirent *dp, int fd) {} #endif static void add_usable_mem_property(int fd, size_t len) { char fname[MAXPATH], *bname; uint64_t buf[2]; uint64_t *ranges; int ranges_size = MEM_RANGE_CHUNK_SZ; uint64_t base, end, rngs_cnt; int rlen = 0; strcpy(fname, pathname); bname = strrchr(fname,'/'); bname[0] = '\0'; bname = strrchr(fname,'/'); if (strncmp(bname, "/memory@", 8) && strcmp(bname, "/memory")) return; if (len < sizeof(buf)) die("unrecoverable error: not enough data for mem property\n"); if (lseek(fd, 0, SEEK_SET) < 0) die("unrecoverable error: error seeking in \"%s\": %s\n", pathname, strerror(errno)); if (read(fd, buf, sizeof(buf)) != sizeof(buf)) die("unrecoverable error: error reading \"%s\": %s\n", pathname, strerror(errno)); base = be64_to_cpu(buf[0]); end = be64_to_cpu(buf[1]); if (~0ULL - base < end) die("unrecoverable error: mem property overflow\n"); end += base; ranges = malloc(ranges_size * sizeof(*ranges)); if (!ranges) die("unrecoverable error: can't alloc %zu bytes for ranges.\n", ranges_size * sizeof(*ranges)); rngs_cnt = add_ranges(&ranges, &ranges_size, rlen, base, end); rlen += rngs_cnt * 2; if (!rlen) { /* * User did not pass any ranges for thsi region. Hence, write * (0,0) duple in linux,usable-memory property such that * this region will be ignored. */ ranges[rlen++] = 0; ranges[rlen++] = 0; } rlen = rlen * sizeof(*ranges); /* * No add linux,usable-memory property. */ dt_reserve(&dt, 4+((rlen + 3)/4)); *dt++ = cpu_to_be32(FDT_PROP); *dt++ = cpu_to_be32(rlen); *dt++ = cpu_to_be32(propnum("linux,usable-memory")); pad_structure_block(rlen); memcpy(dt, ranges, rlen); free(ranges); dt += (rlen + 3)/4; } /* put all properties (files) in the property structure */ static void putprops(char *fn, struct dirent **nlist, int numlist) { struct dirent *dp; int i = 0, fd; off_t len; off_t slen; struct stat statbuf; for (i = 0; i < numlist; i++) { dp = nlist[i]; strcpy(fn, dp->d_name); if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; /* Empirically, this seems to need to be ecluded. * Observed on ARM with 3.6-rc2 kernel */ if (!strcmp(dp->d_name, "name")) continue; if (lstat(pathname, &statbuf)) die("unrecoverable error: could not stat \"%s\": %s\n", pathname, strerror(errno)); if (!crash_param && !strcmp(fn,"linux,crashkernel-base")) continue; if (!crash_param && !strcmp(fn,"linux,crashkernel-size")) continue; /* * This property will be created for each node during kexec * boot. So, ignore it. */ if (!strcmp(dp->d_name, "linux,pci-domain") || !strcmp(dp->d_name, "linux,htab-base") || !strcmp(dp->d_name, "linux,htab-size") || !strcmp(dp->d_name, "linux,kernel-end")) continue; /* This property will be created/modified later in putnode() * So ignore it, unless we are reusing the initrd. */ if ((!strcmp(dp->d_name, "linux,initrd-start") || !strcmp(dp->d_name, "linux,initrd-end")) && !reuse_initrd) continue; /* This property will be created later in putnode() So * ignore it now. */ if (!strcmp(dp->d_name, "bootargs")) continue; if (! S_ISREG(statbuf.st_mode)) continue; len = statbuf.st_size; dt_reserve(&dt, 4+((len + 3)/4)); *dt++ = cpu_to_be32(FDT_PROP); *dt++ = cpu_to_be32(len); *dt++ = cpu_to_be32(propnum(fn)); pad_structure_block(len); if (len) { char *buf; buf = slurp_file_len(pathname, len, &slen); if (slen != len) die("unrecoverable error: short read from\"%s\"\n", pathname); memcpy(dt, buf, slen); free(buf); } checkprop(fn, dt, len); dt += (len + 3)/4; fd = open(pathname, O_RDONLY); if (fd == -1) die("unrecoverable error: could not open \"%s\": %s\n", pathname, strerror(errno)); if (!strcmp(dp->d_name, "reg") && usablemem_rgns.size) add_usable_mem_property(fd, len); add_dyn_reconf_usable_mem_property(dp, fd); close(fd); } fn[0] = '\0'; checkprop(pathname, NULL, 0); } /* * Compare function used to sort the device-tree directories * This function will be passed to scandir. */ static int comparefunc(const struct dirent **dentry1, const struct dirent **dentry2) { char *str1 = (*(struct dirent **)dentry1)->d_name; char *str2 = (*(struct dirent **)dentry2)->d_name; char *sep1 = strchr(str1, '@'); char *sep2 = strchr(str2, '@'); /* * strcmp scans from left to right and fails to idetify for some * strings such as memory@10000000 and memory@f000000. * Therefore, we get the wrong sorted order like memory@10000000 and * memory@f000000. */ if (sep1 && sep2) { int baselen1 = sep1 - str1; int baselen2 = sep2 - str2; int len1 = strlen(str1); int len2 = strlen(str2); /* * Check the base name matches, and the properties are * different lengths. */ if ((baselen1 == baselen2) && (len1 != len2) && !strncmp(str1, str2, baselen2)) return (len1 > len2) - (len1 < len2); } return strcmp(str1, str2); } /* grab root= from the old command line */ static void dt_copy_old_root_param(void) { FILE *fp; char filename[MAXPATH]; char *last_cmdline = NULL; char *p, *old_param; size_t len = 0; strcpy(filename, pathname); strcat(filename, "bootargs"); fp = fopen(filename, "r"); if (!fp) return; if (getline(&last_cmdline, &len, fp) == -1) die("unable to read %s\n", filename); p = strstr(last_cmdline, "root="); if (p) { old_param = strtok(p, " "); len = strlen(local_cmdline); if (len != 0) strcat(local_cmdline, " "); strcat(local_cmdline, old_param); } if (last_cmdline) free(last_cmdline); fclose(fp); } /* * put a node (directory) in the property structure. first properties * then children. */ static void putnode(void) { char *dn; struct dirent *dp; char *basename; struct dirent **namelist; int numlist, i; struct stat statbuf; int plen, ret; numlist = scandir(pathname, &namelist, 0, comparefunc); if (numlist < 0) die("unrecoverable error: could not scan \"%s\": %s\n", pathname, strerror(errno)); if (numlist == 0) die("unrecoverable error: no directory entries in \"%s\"", pathname); basename = strrchr(pathname,'/') + 1; plen = *basename ? strlen(basename) : 0; /* Reserve space for string packed to words; e.g. string length 10 * occupies 3 words, length 12 occupies 4 (for terminating \0s). * So round up & include the \0: */ dt_reserve(&dt, 1+((plen + 4)/4)); *dt++ = cpu_to_be32(FDT_BEGIN_NODE); strcpy((void *)dt, *basename ? basename : ""); dt += ((plen + 4)/4); if (*basename) strcat(pathname, "/"); dn = pathname + strlen(pathname); putprops(dn, namelist, numlist); /* Add initrd entries to the second kernel */ if (initrd_base && initrd_size && !strcmp(basename,"chosen/")) { int len = 8; uint64_t bevalue; dt_reserve(&dt, 12); /* both props, of 6 words ea. */ *dt++ = cpu_to_be32(FDT_PROP); *dt++ = cpu_to_be32(len); *dt++ = cpu_to_be32(propnum("linux,initrd-start")); pad_structure_block(len); bevalue = cpu_to_be64(initrd_base); memcpy(dt, &bevalue, len); dt += (len + 3)/4; len = 8; *dt++ = cpu_to_be32(FDT_PROP); *dt++ = cpu_to_be32(len); *dt++ = cpu_to_be32(propnum("linux,initrd-end")); bevalue = cpu_to_be64(initrd_base + initrd_size); pad_structure_block(len); memcpy(dt, &bevalue, len); dt += (len + 3)/4; reserve(initrd_base, initrd_size); } /* * Add cmdline to the second kernel. Use the old root= cmdline if there * is no root= in the new command line and there's no --dt-no-old-root * option being used. */ if (!strcmp(basename,"chosen/")) { size_t result; size_t cmd_len = 0; char *param = NULL; char filename[MAXPATH]; char *buff; int fd; cmd_len = strlen(local_cmdline); if (cmd_len != 0) { param = strstr(local_cmdline, "crashkernel="); if (param) crash_param = 1; /* does the new cmdline have a root= ? ... */ param = strstr(local_cmdline, "root="); } if (!param && !dt_no_old_root) dt_copy_old_root_param(); strcat(local_cmdline, " "); cmd_len = strlen(local_cmdline); cmd_len = cmd_len + 1; /* add new bootargs */ dt_reserve(&dt, 4+((cmd_len+3)/4)); *dt++ = cpu_to_be32(FDT_PROP); *dt++ = cpu_to_be32(cmd_len); *dt++ = cpu_to_be32(propnum("bootargs")); pad_structure_block(cmd_len); memcpy(dt, local_cmdline,cmd_len); dt += (cmd_len + 3)/4; fprintf(stderr, "Modified cmdline:%s\n", local_cmdline); /* * Determine the platform type/stdout type, so that purgatory * code can print 'I'm in purgatory' message. Currently only * pseries/hvcterminal is supported. */ ret = snprintf(filename, MAXPATH, "%sstdout-path", pathname); if (ret < 0 || ret >= MAXPATH) { fprintf(stderr, "snprintf failed: %s\n", strerror(errno)); goto no_debug; } fd = open(filename, O_RDONLY); if (fd == -1) { ret = snprintf(filename, MAXPATH, "%slinux,stdout-path", pathname); if (ret < 0 || ret >= MAXPATH) { fprintf(stderr, "snprintf failed: %s\n", strerror(errno)); goto no_debug; } fd = open(filename, O_RDONLY); if (fd == -1) { printf("Unable to find %s[linux,]stdout-path, printing from purgatory is disabled\n", pathname); goto no_debug; } } if (fstat(fd, &statbuf)) { printf("Unable to stat %s, printing from purgatory is disabled\n", filename); close(fd); goto no_debug; } buff = malloc(statbuf.st_size); if (!buff) { printf("Can not allocate memory for buff\n"); close(fd); goto no_debug; } result = read(fd, buff, statbuf.st_size); close(fd); if (result <= 0) { printf("Unable to read %s, printing from purgatory is disabled\n", filename); goto no_debug; } snprintf(filename, MAXPATH, "/proc/device-tree/%s/compatible", buff); fd = open(filename, O_RDONLY); if (fd == -1) { printf("Unable to find %s printing from purgatory is disabled\n", filename); goto no_debug; } if (fstat(fd, &statbuf)) { printf("Unable to stat %s printing from purgatory is disabled\n", filename); close(fd); goto no_debug; } buff = realloc(buff, statbuf.st_size); if (!buff) { printf("Can not allocate memory for buff\n"); close(fd); goto no_debug; } result = read(fd, buff, statbuf.st_size); if (result && (!strcmp(buff, "hvterm1") || !strcmp(buff, "hvterm-protocol"))) my_debug = 1; close(fd); free(buff); } no_debug: for (i=0; i < numlist; i++) { dp = namelist[i]; strcpy(dn, dp->d_name); free(namelist[i]); if (!strcmp(dn, ".") || !strcmp(dn, "..")) continue; if (lstat(pathname, &statbuf)) die("unrecoverable error: could not stat \"%s\": %s\n", pathname, strerror(errno)); if (S_ISDIR(statbuf.st_mode)) putnode(); } dt_reserve(&dt, 1); *dt++ = cpu_to_be32(FDT_END_NODE); dn[-1] = '\0'; free(namelist); } struct bootblock bb[1]; static void add_boot_block(char **bufp, off_t *sizep) { unsigned long len; unsigned long tlen, toff; char *buf; len = _ALIGN(sizeof(bb[0]), 8); bb->off_mem_rsvmap = cpu_to_be32(len); for (len = 1; be64_to_cpu(mem_rsrv[len]); len += 2) ; len++; #ifdef NEED_RESERVE_DTB len+= 3; /* Leave space for totalsize reservation */ #endif len *= sizeof(mem_rsrv[0]); bb->off_dt_struct = cpu_to_be32(be32_to_cpu(bb->off_mem_rsvmap) + len); len = dt - dt_base; len *= sizeof(unsigned); #if (BOOT_BLOCK_VERSION >= 17) bb->dt_struct_size = cpu_to_be32(len); #endif bb->off_dt_strings = cpu_to_be32(be32_to_cpu(bb->off_dt_struct) + len); len = propnum(""); bb->dt_strings_size = cpu_to_be32(len); len = _ALIGN(len, 4); bb->totalsize = cpu_to_be32(be32_to_cpu(bb->off_dt_strings) + len); bb->magic = cpu_to_be32(FDT_MAGIC); bb->version = cpu_to_be32(BOOT_BLOCK_VERSION); bb->last_comp_version = cpu_to_be32(BOOT_BLOCK_LAST_COMP_VERSION); #ifdef NEED_RESERVE_DTB reserve(0, be32_to_cpu(bb->totalsize)); /* patched later in kexec_load */ #endif buf = malloc(be32_to_cpu(bb->totalsize)); *bufp = buf; tlen = be32_to_cpu(bb->off_mem_rsvmap); memcpy(buf, bb, tlen); toff = be32_to_cpu(bb->off_mem_rsvmap); tlen = be32_to_cpu(bb->off_dt_struct) - be32_to_cpu(bb->off_mem_rsvmap); memcpy(buf + toff, mem_rsrv, tlen); toff += be32_to_cpu(bb->off_dt_struct) - be32_to_cpu(bb->off_mem_rsvmap); tlen = be32_to_cpu(bb->off_dt_strings) - be32_to_cpu(bb->off_dt_struct); memcpy(buf + toff, dt_base, tlen); toff += be32_to_cpu(bb->off_dt_strings) - be32_to_cpu(bb->off_dt_struct); tlen = be32_to_cpu(bb->totalsize) - be32_to_cpu(bb->off_dt_strings); memcpy(buf + toff, propnames, tlen); *sizep = toff + be32_to_cpu(bb->totalsize) - be32_to_cpu(bb->off_dt_strings); } void create_flatten_tree(char **bufp, off_t *sizep, const char *cmdline) { strcpy(pathname, "/proc/device-tree/"); dt_cur_size = INIT_TREE_WORDS; dt_base = malloc(dt_cur_size*4); if (!dt_base) { die("Can't malloc %d bytes for dt struct!\n", dt_cur_size*4); } memset(dt_base, 0, dt_cur_size*4); dt = dt_base; if (cmdline) strcpy(local_cmdline, cmdline); putnode(); dt_reserve(&dt, 1); *dt++ = cpu_to_be32(FDT_END); add_boot_block(bufp, sizep); free(dt_base); }