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
strtoi(char * str,char ** pos)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
obp_timestamp(char * v)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
check_timestamp(char * model,int tstamp)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
visit(pnode_t node)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
walk(pnode_t node)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
prom_version_check(char * buf,size_t buflen,pnode_t * nodeid)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 }
363