/* * Copyright (c) International Business Machines Corp., 2006, 2007 * * 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; either version 2 of the License, or * (at your option) any later version. * * 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. */ /* * Authors: Drake Dowsett, dowsett@de.ibm.com * Contact: Andreas Arnez, arnez@de.ibm.com * * unubi uses the following functions to generate analysis output based on * the header information in a raw-UBI image */ /* * TODO: use OOB data to check for eraseblock validity in NAND images */ #include <stdint.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <limits.h> #include <sys/types.h> #include <sys/stat.h> #include <mtd_swab.h> #include "unubi_analyze.h" #include "crc32.h" #define EC_X_INT 50 /** * intcmp - function needed by qsort to order integers **/ int intcmp(const void *a, const void *b) { int A = *(int *)a; int B = *(int *)b; return A - B; } int longcmp(const void *a, const void *b) { long long A = *(long long *)a; long long B = *(long long *)b; return A - B; } /** * unubi_analyze_group_index - finds the normalized index in an array * item: look for this item in the array * array: array to search through * size: length of the array * array should be sorted for this algorithm to perform properly; * if the item is not found returns -1, otherwise return value is the * index in the array (note this contricts the array size to 2^32-1); **/ int norm_index(uint32_t item, uint32_t *array, size_t length) { size_t i, index; for (index = 0, i = 0; i < length; i++) { if ((i != 0) && (array[i] != array[i - 1])) index++; if (item == array[i]) return index; } return -1; } /** * unubi_analyze_ec_hdr - generate data table and plot script * first: head of simple linked list * path: folder to write into * generates a data file containing the eraseblock index in the image * and the erase counter found in its ec header; * if the crc check fails, the line is commented out in the data file; * also generates a simple gnuplot sript for quickly viewing one * display of the data file; **/ int unubi_analyze_ec_hdr(struct eb_info *first, const char *path) { char filename[PATH_MAX + 1]; size_t count, eraseblocks; uint32_t crc, crc32_table[256]; uint64_t *erase_counts; FILE* fpdata; FILE* fpplot; struct eb_info *cur; if (first == NULL) return -1; /* crc check still needed for `first' linked list */ init_crc32_table(crc32_table); /* prepare output files */ memset(filename, 0, PATH_MAX + 1); snprintf(filename, PATH_MAX, "%s/%s", path, FN_EH_DATA); fpdata = fopen(filename, "w"); if (fpdata == NULL) return -1; memset(filename, 0, PATH_MAX + 1); snprintf(filename, PATH_MAX, "%s/%s", path, FN_EH_PLOT); fpplot = fopen(filename, "w"); if (fpplot == NULL) { fclose(fpdata); return -1; } /* make executable */ chmod(filename, 0755); /* first run: count elements */ count = 0; cur = first; while (cur != NULL) { cur = cur->next; count++; } eraseblocks = count; erase_counts = malloc(eraseblocks * sizeof(*erase_counts)); if (!erase_counts) { perror("out of memory"); exit(EXIT_FAILURE); } memset(erase_counts, 0, eraseblocks * sizeof(*erase_counts)); /* second run: populate array to sort */ count = 0; cur = first; while (cur != NULL) { erase_counts[count] = be64_to_cpu(cur->ec.ec); cur = cur->next; count++; } qsort(erase_counts, eraseblocks, sizeof(*erase_counts), (void *)longcmp); /* third run: generate data file */ count = 0; cur = first; fprintf(fpdata, "# eraseblock_no actual_erase_count " "sorted_erase_count\n"); while (cur != NULL) { crc = clc_crc32(crc32_table, UBI_CRC32_INIT, &cur->ec, UBI_EC_HDR_SIZE_CRC); if ((be32_to_cpu(cur->ec.magic) != UBI_EC_HDR_MAGIC) || (crc != be32_to_cpu(cur->ec.hdr_crc))) fprintf(fpdata, "# "); fprintf(fpdata, "%zu %llu %llu", count, (unsigned long long)be64_to_cpu(cur->ec.ec), (unsigned long long)erase_counts[count]); if (be32_to_cpu(cur->ec.magic) != UBI_EC_HDR_MAGIC) fprintf(fpdata, " ## bad magic: %08x", be32_to_cpu(cur->ec.magic)); if (crc != be32_to_cpu(cur->ec.hdr_crc)) fprintf(fpdata, " ## CRC mismatch: given=%08x, " "calc=%08x", be32_to_cpu(cur->ec.hdr_crc), crc); fprintf(fpdata, "\n"); cur = cur->next; count++; } fclose(fpdata); fprintf(fpplot, "#!/usr/bin/gnuplot -persist\n"); fprintf(fpplot, "set xlabel \"eraseblock\"\n"); /* fourth run: generate plot file xtics */ count = 0; cur = first; fprintf(fpplot, "set xtics ("); while (cur != NULL) { if ((count % EC_X_INT) == 0) { if (count > 0) fprintf(fpplot, ", "); fprintf(fpplot, "%zd", count); } cur = cur->next; count++; } fprintf(fpplot, ")\n"); fprintf(fpplot, "set ylabel \"erase count\"\n"); fprintf(fpplot, "set xrange [-1:%zu]\n", eraseblocks + 1); fprintf(fpplot, "# set yrange [-1:%llu]\n", (unsigned long long)erase_counts[eraseblocks - 1] + 1); fprintf(fpplot, "plot \"%s\" u 1:2 t \"unsorted: %s\" with boxes\n", FN_EH_DATA, FN_EH_DATA); fprintf(fpplot, "# replot \"%s\" u 1:3 t \"sorted: %s\" with lines\n", FN_EH_DATA, FN_EH_DATA); fprintf(fpplot, "pause -1 \"press ENTER\"\n"); fclose(fpplot); return 0; } /** * unubi_analyze_vid_hdr - generate data table and plot script * head: head of complex linked list (eb_chain) * path: folder to write into * generates a data file containing the volume id, logical number, leb version, * and data size from the vid header; * all eraseblocks listed in the eb_chain are valid (checked in unubi); * also generates a simple gnuplot sript for quickly viewing one * display of the data file; **/ int unubi_analyze_vid_hdr(struct eb_info **head, const char *path) { char filename[PATH_MAX + 1]; int rc, y1, y2; size_t count, step, breadth; uint32_t *leb_versions, *data_sizes; FILE* fpdata; FILE* fpplot; struct eb_info *cur; if (head == NULL || *head == NULL) return -1; rc = 0; fpdata = NULL; fpplot = NULL; data_sizes = NULL; leb_versions = NULL; /* prepare output files */ memset(filename, 0, PATH_MAX + 1); snprintf(filename, PATH_MAX, "%s/%s", path, FN_VH_DATA); fpdata = fopen(filename, "w"); if (fpdata == NULL) { rc = -1; goto exit; } memset(filename, 0, PATH_MAX + 1); snprintf(filename, PATH_MAX, "%s/%s", path, FN_VH_PLOT); fpplot = fopen(filename, "w"); if (fpplot == NULL) { rc = -1; goto exit; } /* make executable */ chmod(filename, 0755); /* first run: count elements */ count = 0; cur = *head; while (cur != NULL) { cur = cur->next; count++; } breadth = count; leb_versions = malloc(breadth * sizeof(uint32_t)); if (leb_versions == NULL) { rc = -1; goto exit; } memset(leb_versions, 0, breadth * sizeof(uint32_t)); data_sizes = malloc(breadth * sizeof(uint32_t)); if (data_sizes == NULL) { rc = -1; goto exit; } memset(data_sizes, 0, breadth * sizeof(*data_sizes)); /* second run: populate arrays to sort */ count = 0; cur = *head; while (cur != NULL) { leb_versions[count] = be32_to_cpu(cur->vid.leb_ver); data_sizes[count] = be32_to_cpu(cur->vid.data_size); cur = cur->next; count++; } qsort(leb_versions, breadth, sizeof(*leb_versions), (void *)intcmp); qsort(data_sizes, breadth, sizeof(*data_sizes), (void *)intcmp); /* third run: generate data file */ count = 0; cur = *head; fprintf(fpdata, "# x_axis vol_id lnum y1_axis leb_ver " "y2_axis data_size\n"); while (cur != NULL) { y1 = norm_index(be32_to_cpu(cur->vid.leb_ver), leb_versions, breadth); y2 = norm_index(be32_to_cpu(cur->vid.data_size), data_sizes, breadth); if ((y1 == -1) || (y2 == -1)) { rc = -1; goto exit; } fprintf(fpdata, "%zu %u %u %u %u %u %u\n", count, be32_to_cpu(cur->vid.vol_id), be32_to_cpu(cur->vid.lnum), y1, be32_to_cpu(cur->vid.leb_ver), y2, be32_to_cpu(cur->vid.data_size)); cur = cur->next; count++; } fprintf(fpplot, "#!/usr/bin/gnuplot -persist\n"); fprintf(fpplot, "set xlabel \"volume\"\n"); /* fourth run: generate plot file xtics */ count = 0; step = 0; cur = *head; fprintf(fpplot, "set xtics ("); while (cur != NULL) { if (count > 0) fprintf(fpplot, ", "); if (step != be32_to_cpu(cur->vid.vol_id)) { step = be32_to_cpu(cur->vid.vol_id); fprintf(fpplot, "\"%zd\" %zd 0", step, count); } else fprintf(fpplot, "\"%d\" %zd 1", be32_to_cpu(cur->vid.lnum), count); cur = cur->next; count++; } fprintf(fpplot, ")\n"); fprintf(fpplot, "set nox2tics\n"); /* fifth run: generate plot file ytics */ count = 0; cur = *head; fprintf(fpplot, "set ylabel \"leb version\"\n"); fprintf(fpplot, "set ytics ("); while (cur->next != NULL) { y1 = norm_index(be32_to_cpu(cur->vid.leb_ver), leb_versions, breadth); if (y1 == -1) { rc = -1; goto exit; } if (count > 0) fprintf(fpplot, ", "); fprintf(fpplot, "\"%u\" %u", be32_to_cpu(cur->vid.leb_ver), y1); cur = cur->next; count++; } fprintf(fpplot, ")\n"); /* sixth run: generate plot file y2tics */ count = 0; cur = *head; fprintf(fpplot, "set y2label \"data size\"\n"); fprintf(fpplot, "set y2tics ("); while (cur != NULL) { y2 = norm_index(be32_to_cpu(cur->vid.data_size), data_sizes, breadth); if (y2 == -1) { rc = -1; goto exit; } if (count > 0) fprintf(fpplot, ", "); fprintf(fpplot, "\"%u\" %u", be32_to_cpu(cur->vid.data_size), y2); cur = cur->next; count++; } fprintf(fpplot, ")\n"); y1 = norm_index(leb_versions[breadth - 1], leb_versions, breadth); y2 = norm_index(data_sizes[breadth - 1], data_sizes, breadth); fprintf(fpplot, "set xrange [-1:%zu]\n", count + 1); fprintf(fpplot, "set yrange [-1:%u]\n", y1 + 1); fprintf(fpplot, "set y2range [-1:%u]\n", y2 + 1); fprintf(fpplot, "plot \"%s\" u 1:4 t \"leb version: %s\" " "axes x1y1 with lp\n", FN_VH_DATA, FN_VH_DATA); fprintf(fpplot, "replot \"%s\" u 1:6 t \"data size: %s\" " "axes x1y2 with lp\n", FN_VH_DATA, FN_VH_DATA); fprintf(fpplot, "pause -1 \"press ENTER\"\n"); exit: if (fpdata != NULL) fclose(fpdata); if (fpplot != NULL) fclose(fpplot); if (data_sizes != NULL) free(data_sizes); if (leb_versions != NULL) free(leb_versions); return rc; } /** * unubi_analyze - run all analyses * head: eb_chain head * first: simple linked list of eraseblock headers (use .next) * path: directory (without trailing slash) to output to * returns 0 upon successful completion, or -1 otherwise **/ int unubi_analyze(struct eb_info **head, struct eb_info *first, const char *path) { int ec_rc, vid_rc; if (path == NULL) return -1; ec_rc = unubi_analyze_ec_hdr(first, path); vid_rc = unubi_analyze_vid_hdr(head, path); if (ec_rc < 0 || vid_rc < 0) return -1; return 0; }