| /* |
| * 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; |
| } |