1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 #include <sys/promif.h>
  27 #include <sys/promimpl.h>
  28 
  29 #ifdef  DPRINTF
  30 #define dprintf prom_printf
  31 #endif
  32 
  33 /*
  34  * Check if the prom is 64-bit ready.
  35  */
  36 
  37 /*
  38  * Table listing the minimum prom versions supported by this kernel.
  39  * The model value is expected to match the model in the flashprom node.
  40  */
  41 static struct obp_rev_table {
  42         char *model;
  43         char *version;
  44 } obp_min_revs[] = {
  45         {"SUNW,525-1414", "OBP 3.11.2 1997/12/05 10:25"},  /* pulsar */
  46         {"SUNW,525-1672", "OBP 3.7.107 1998/02/19 17:54"},  /* tazmo */
  47         {"SUNW,525-1431", "OBP 3.2.16 1998/06/08 16:58"},   /* sunfire */
  48         { NULL, NULL}
  49 };
  50 
  51 #define NMINS   60
  52 #define NHOURS  24
  53 #define NDAYS   31
  54 #define NMONTHS 12
  55 
  56 #define YEAR(y)  ((y-1) * (NMONTHS * NDAYS * NHOURS * NMINS))
  57 #define MONTH(m) ((m-1) * (NDAYS * NHOURS * NMINS))
  58 #define DAY(d)   ((d-1) * (NHOURS * NMINS))
  59 #define HOUR(h)  ((h)   * (NMINS))
  60 #define MINUTE(m) (m)
  61 
  62 static int
  63 strtoi(char *str, char **pos)
  64 {
  65         int c;
  66         int val = 0;
  67 
  68         for (c = *str++; c >= '0' && c <= '9'; c = *str++) {
  69                 val *= 10;
  70                 val += c - '0';
  71         }
  72         if (pos)
  73                 *pos = str;
  74         return (val);
  75 }
  76 
  77 /*
  78  * obp_timestamp: based on the OBP flashprom version string of the
  79  * format "OBP x.y.z YYYY/MM/DD HH:MM" calculate a timestamp based
  80  * on the year, month, day, hour and minute by turning that into
  81  * a number of minutes.
  82  */
  83 static int
  84 obp_timestamp(char *v)
  85 {
  86         char *c;
  87         int maj, year, month, day, hour, min;
  88 
  89         if (v[0] != 'O' || v[1] != 'B' || v[2] != 'P')
  90                 return (-1);
  91 
  92         c = v + 3;
  93 
  94         /* Find first non-space character after OBP */
  95         while (*c != '\0' && (*c == ' ' || *c == '\t'))
  96                 c++;
  97         if (prom_strlen(c) < 5)              /* need at least "x.y.z" */
  98                 return (-1);
  99 
 100         maj = strtoi(c, &c);
 101         if (maj < 3)
 102                 return (-1);
 103 
 104 #if 0 /* XXX - not used */
 105         dot = dotdot = 0;
 106         if (*c == '.') {
 107                 dot = strtoi(c + 1, &c);
 108 
 109                 /* optional? dot-dot release */
 110                 if (*c == '.')
 111                         dotdot = strtoi(c + 1, &c);
 112         }
 113 #endif
 114 
 115         /* Find space at the end of version number */
 116         while (*c != '\0' && *c != ' ')
 117                 c++;
 118         if (prom_strlen(c) < 11)     /* need at least " xxxx/xx/xx" */
 119                 return (-1);
 120 
 121         /* Point to first character of date */
 122         c++;
 123 
 124         /* Validate date format */
 125         if (c[4] != '/' || c[7] != '/')
 126                 return (-1);
 127 
 128         year = strtoi(c, NULL);
 129         month = strtoi(c + 5, NULL);
 130         day = strtoi(c + 8, NULL);
 131 
 132         if (year < 1995 || month == 0 || day == 0)
 133                 return (-1);
 134 
 135         /*
 136          * Find space at the end of date number
 137          */
 138         c += 10;
 139         while (*c != '\0' && *c != ' ')
 140                 c++;
 141         if (prom_strlen(c) < 6)              /* need at least " xx:xx" */
 142                 return (-1);
 143 
 144         /* Point to first character of time */
 145         c++;
 146 
 147         if (c[2] != ':')
 148                 return (-1);
 149 
 150         hour = strtoi(c, NULL);
 151         min = strtoi(c + 3, NULL);
 152 
 153         return (YEAR(year) + MONTH(month) +
 154             DAY(day) + HOUR(hour) + MINUTE(min));
 155 }
 156 
 157 /*
 158  * Check the prom against the obp_min_revs table and complain if
 159  * the system has an older prom installed.  The actual major/minor/
 160  * dotdot numbers are not checked, only the date/time stamp.
 161  */
 162 
 163 static struct obp_rev_table *flashprom_ortp;
 164 static pnode_t flashprom_node;
 165 static int flashprom_checked;
 166 static int flashprom_return_code;
 167 
 168 int
 169 check_timestamp(char *model, int tstamp)
 170 {
 171         int min_tstamp;
 172         struct obp_rev_table *ortp;
 173 
 174         for (ortp = obp_min_revs; ortp->model != NULL; ortp++) {
 175                 if (prom_strcmp(model, ortp->model) == 0) {
 176                         min_tstamp = obp_timestamp(ortp->version);
 177                         if (min_tstamp == -1) {
 178 #ifdef  DEBUG
 179                                 prom_printf("prom_version_check: "
 180                                     "invalid OBP version string in table "
 181                                     " (entry %d)", (int)(ortp - obp_min_revs));
 182 #endif
 183                                 continue;
 184                         }
 185                         if (tstamp < min_tstamp) {
 186 #ifdef  DPRINTF
 187                                 dprintf("prom_version_check: "
 188                                     "Down-rev OBP detected.  "
 189                                     "Please update to at least:\n\t%s\n\n",
 190                                     ortp->version);
 191 #endif
 192                                 flashprom_ortp = ortp;
 193                                 return (1);
 194                         }
 195                 }
 196         } /* for each obp_rev_table entry */
 197 
 198         return (0);
 199 }
 200 
 201 static pnode_t
 202 visit(pnode_t node)
 203 {
 204         int tstamp, plen, i;
 205         char vers[512], model[64];
 206         static pnode_t openprom_node;
 207         static char version[] = "version";
 208         static char model_name[] = "model";
 209         static char flashprom[] = "flashprom";
 210 
 211         /*
 212          * if name isn't 'flashprom', continue.
 213          */
 214         if (prom_getproplen(node, OBP_NAME) != sizeof (flashprom))
 215                 return ((pnode_t)0);
 216         (void) prom_getprop(node, OBP_NAME, model);
 217         if (prom_strncmp(model, flashprom, sizeof (flashprom)) != 0)
 218                 return ((pnode_t)0);
 219 
 220         plen = prom_getproplen(node, version);
 221         if (plen <= 0 || plen > sizeof (vers))
 222                 return ((pnode_t)0);
 223         (void) prom_getprop(node, version, vers);
 224         vers[plen] = '\0';
 225 
 226         /* Make sure it's an OBP flashprom */
 227         if (vers[0] != 'O' && vers[1] != 'B' && vers[2] != 'P')
 228                 return ((pnode_t)0);
 229 
 230         plen = prom_getproplen(node, model_name);
 231         if (plen <= 0 || plen > sizeof (model))
 232                 return ((pnode_t)0);
 233         (void) prom_getprop(node, model_name, model);
 234         model[plen] = '\0';
 235 
 236         tstamp = obp_timestamp(vers);
 237         if (tstamp == -1) {
 238                 prom_printf("prom_version_check: node contains "
 239                     "improperly formatted version property,\n"
 240                     "\tnot checking prom version");
 241                 return ((pnode_t)0);
 242         }
 243 
 244         i = check_timestamp(model, tstamp);
 245 
 246         if (i == 0)
 247                 return ((pnode_t)0);
 248 
 249         /*
 250          * We know that "node"'s flashprom image contains downrev firmware,
 251          * however, a multi-board server might be running correct firmware.
 252          * Check for that case by looking at the "/openprom" node,
 253          * which always contains the running version. (We needed the
 254          * "model" value to be able to do this, so we can use it as
 255          * an index value into the table.)
 256          *
 257          * If it turns out we're running 'current' firmware,
 258          * but detect down-rev firmware, use a different return code.
 259          */
 260 
 261         flashprom_return_code = PROM_VER64_UPGRADE;
 262 
 263         openprom_node = prom_finddevice("/openprom");
 264         if (openprom_node == OBP_BADNODE)
 265                 return (node);
 266 
 267         plen = prom_getproplen(node, version);
 268         if (plen <= 0 || plen > sizeof (vers))
 269                 return (node);
 270         (void) prom_getprop(node, version, vers);
 271         vers[plen] = '\0';
 272 
 273         if (vers[0] != 'O' && vers[1] != 'B' && vers[2] != 'P') {
 274                 prom_printf("prom_version_check: "
 275                     "unknown <version> string in </openprom>\n");
 276                 return (node);
 277         }
 278 
 279         tstamp = obp_timestamp(vers);
 280         if (tstamp == -1) {
 281                 prom_printf("prom_version_check: "
 282                     "</openprom> node <version> property: bad tstamp\n");
 283                 return (node);
 284         }
 285 
 286         i = check_timestamp(model, tstamp);
 287         /*
 288          * If that returned zero, then the running version is
 289          * adequate ... so we can 'suggest' instead of 'require'.
 290          */
 291         if (i == 0)
 292                 flashprom_return_code = PROM_VER64_SUGGEST;
 293 
 294         return (node);
 295 }
 296 
 297 /*
 298  * visit each node in the device tree, until we get a non-null answer
 299  */
 300 static pnode_t
 301 walk(pnode_t node)
 302 {
 303         pnode_t id;
 304 
 305         if (visit(node))
 306                 return (node);
 307 
 308         for (node = prom_childnode(node); node; node = prom_nextnode(node))
 309                 if ((id = walk(node)) != (pnode_t)0)
 310                         return (id);
 311 
 312         return ((pnode_t)0);
 313 }
 314 
 315 /*
 316  * Check if the prom is 64-bit ready.
 317  *
 318  * If it's ready (or the test doesn't apply), return PROM_VER64_OK.
 319  * If downrev firmware is running, return PROM_VER64_UPGRADE.
 320  * If downrev firmware is detected (but not running), return PROM_VER64_SUGGEST.
 321  *
 322  * For PROM_VER64_UPGRADE and PROM_VER64_SUGGEST return code values:
 323  * Return the nodeid of the flashprom node in *nodeid.
 324  * and a printable message in *buf, buflen.
 325  */
 326 int
 327 prom_version_check(char *buf, size_t buflen, pnode_t *nodeid)
 328 {
 329         char *p;
 330         pnode_t node = flashprom_node;
 331         size_t i;
 332 
 333         /*
 334          * If we already checked, we already know the answer.
 335          */
 336         if (flashprom_checked == 0) {
 337                 flashprom_node = node = walk(prom_rootnode());
 338                 flashprom_checked = 1;
 339         }
 340 
 341         if (nodeid)
 342                 *nodeid = node;
 343 
 344         if (node == (pnode_t)0) {
 345                 if (buf && buflen)
 346                         *buf = '\0';
 347                 return (PROM_VER64_OK);
 348         }
 349 
 350         /* bzero the callers buffer */
 351         for (i = buflen, p = buf; i != 0; --i, ++p)
 352                 *p = '\0';
 353 
 354         /*
 355          * Do a bounded copy of the output string into the callers buffer
 356          */
 357         if (buflen <= 1)
 358                 return (flashprom_return_code);
 359 
 360         (void) prom_strncpy(buf, flashprom_ortp->version, buflen - 1);
 361         return (flashprom_return_code);
 362 }