nexmon – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 /***************************************************************************
2 * *
3 * ########### ########### ########## ########## *
4 * ############ ############ ############ ############ *
5 * ## ## ## ## ## ## ## *
6 * ## ## ## ## ## ## ## *
7 * ########### #### ###### ## ## ## ## ###### *
8 * ########### #### # ## ## ## ## # # *
9 * ## ## ###### ## ## ## ## # # *
10 * ## ## # ## ## ## ## # # *
11 * ############ ##### ###### ## ## ## ##### ###### *
12 * ########### ########### ## ## ## ########## *
13 * *
14 * S E C U R E M O B I L E N E T W O R K I N G *
15 * *
16 * This file is part of NexMon. *
17 * *
18 * Copyright (c) 2016 NexMon Team *
19 * *
20 * NexMon is free software: you can redistribute it and/or modify *
21 * it under the terms of the GNU General Public License as published by *
22 * the Free Software Foundation, either version 3 of the License, or *
23 * (at your option) any later version. *
24 * *
25 * NexMon is distributed in the hope that it will be useful, *
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
28 * GNU General Public License for more details. *
29 * *
30 * You should have received a copy of the GNU General Public License *
31 * along with NexMon. If not, see <http://www.gnu.org/licenses/>. *
32 * *
33 **************************************************************************/
34  
35 #define _XOPEN_SOURCE 700
36  
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <argp-extern.h>
40 #include <string.h>
41 #include <byteswap.h>
42  
43 #include <sys/types.h>
44 #include <sys/socket.h>
45 #include <unistd.h>
46 #include <sys/param.h> // for MIN macro
47  
48 #include <sys/ioctl.h>
49 #include <arpa/inet.h>
50 #ifdef BUILD_ON_RPI
51 #include <types.h> //not sure why it was removed, but it is needed for typedefs like `uint`
52 #include <linux/if.h>
53 #else
54 #include <net/if.h>
55 #endif
56 #include <stdbool.h>
57 #define TYPEDEF_BOOL
58 #include <errno.h>
59  
60 #include <wlcnt.h>
61  
62 #include <nexioctls.h>
63  
64 #include <typedefs.h>
65 #include <bcmwifi_channels.h>
66 #include <b64.h>
67  
68 #if ANDROID
69 #include <sys/system_properties.h>
70 #endif
71  
72 #define HEXDUMP_COLS 16
73  
74 #define IPADDR(a,b,c,d) ((d) << 24 | (c) << 16 | (b) << 8 | (a))
75  
76 struct nexio {
77 struct ifreq *ifr;
78 int sock_rx_ioctl;
79 int sock_rx_frame;
80 int sock_tx;
81 };
82  
83 extern int nex_ioctl(struct nexio *nexio, int cmd, void *buf, int len, bool set);
84 extern struct nexio *nex_init_ioctl(const char *ifname);
85 extern struct nexio *nex_init_udp(unsigned int securitycookie, unsigned int txip);
86 extern struct nexio *nex_init_netlink(void);
87  
88 char *ifname = "wlan0";
89 unsigned char set_monitor = 0;
90 unsigned char set_monitor_value = 0;
91 unsigned char get_monitor = 0;
92 unsigned char set_promisc = 0;
93 unsigned char set_promisc_value = 0;
94 unsigned char get_promisc = 0;
95 unsigned char set_scansuppress = 0;
96 unsigned char set_scansuppress_value = 0;
97 unsigned char get_scansuppress = 0;
98 unsigned char set_securitycookie = 0;
99 unsigned int set_securitycookie_value = 0;
100 unsigned char get_securitycookie = 0;
101 unsigned int use_udp_tunneling = 0; // contains the securtiy cookie
102 unsigned int txip = IPADDR(192,168,222,255);
103 unsigned int custom_cmd = 0;
104 signed char custom_cmd_set = -1;
105 unsigned int custom_cmd_buf_len = 4;
106 void *custom_cmd_buf = NULL;
107 char *custom_cmd_value = NULL;
108 unsigned char get_chanspec = 0;
109 unsigned char set_chanspec = 0;
110 char *set_chanspec_value = NULL;
111 unsigned char custom_cmd_value_int = false;
112 unsigned char custom_cmd_value_base64 = false;
113 unsigned char raw_output = false;
114 unsigned char base64_output = false;
115 unsigned int dump_objmem_addr = 0;
116 unsigned char dump_objmem = false;
117 unsigned char disassociate = false;
118 unsigned char dump_wl_cnt = false;
119 unsigned char revinfo = false;
120  
121 const char *argp_program_version = VERSION;
122 const char *argp_program_bug_address = "<mschulz@seemoo.tu-darmstadt.de>";
123  
124 static char doc[] = "nexutil -- a program to control a nexmon firmware for broadcom chips.";
125  
126 static struct argp_option options[] = {
127 {"interface-name", 'I', "CHAR", 0, "Set interface name (default: wlan0)"},
128 {"monitor", 'm', "INT", OPTION_ARG_OPTIONAL, "Set/Get monitor mode"},
129 {"promisc", 'p', "INT", OPTION_ARG_OPTIONAL, "Set/Get promiscuous mode"},
130 {"scansuppress", 'c', "INT", OPTION_ARG_OPTIONAL, "Set/Get scan suppress setting to avoid scanning"},
131 {"disassociate", 'd', 0, 0, "Disassociate from access point"},
132 {"get-custom-cmd", 'g', "INT", 0, "Get custom command, e.g. 107 for WLC_GET_VAR"},
133 {"set-custom-cmd", 's', "INT", 0, "Set custom command, e.g. 108 for WLC_SET_VAR"},
134 {"custom-cmd-buf-len", 'l', "INT", 0, "Custom command buffer length (default: 4)"},
135 {"custom-cmd-value", 'v', "CHAR/INT", 0, "Initialization value for the buffer used by custom command"},
136 {"custom-cmd-value-int", 'i', 0, 0, "Define that custom-cmd-value should be interpreted as integer"},
137 {"custom-cmd-value-base64", 'b', 0, 0, "Define that custom-cmd-value should be interpreted as base64 string"},
138 {"base64-output", 'R', 0, 0, "Write base64 encoded strings to stdout instead of hex dumping"},
139 {"raw-output", 'r', 0, 0, "Write raw output to stdout instead of hex dumping"},
140 {"dump-wl_cnt", 'w', 0, 0, "Dump WL counters"},
141 {"dump-objmem", 'o', "INT", 0, "Dumps objmem at addr INT"},
142 {"chanspec", 'k', "CHAR/INT", OPTION_ARG_OPTIONAL, "Set chanspec either as integer (e.g., 0x1001, set -i) or as string (e.g., 64/80)."},
143 {"security-cookie", 'x', "INT", OPTION_ARG_OPTIONAL, "Set/Get security cookie"},
144 {"use-udp-tunneling", 'X', "INT", 0, "Use UDP tunneling with security cookie INT"},
145 {"broadcast-ip", 'B', "CHAR", 0, "Broadcast IP to use for UDP tunneling (default: 192.168.222.255)"},
146 {"revinfo", 'V', 0, 0, "Dump revision information of the Wi-Fi chip"},
147 { 0 }
148 };
149  
150 static error_t
151 parse_opt(int key, char *arg, struct argp_state *state)
152 {
153 switch (key) {
154 case 'I':
155 ifname = arg;
156 break;
157  
158 case 'm':
159 if (arg) {
160 set_monitor = true;
161 set_monitor_value = strtol(arg, NULL, 0);
162 } else {
163 get_monitor = true;
164 }
165 break;
166  
167 case 'p':
168 if (arg) {
169 set_promisc = true;
170 set_promisc_value = strtol(arg, NULL, 0);
171 } else {
172 get_promisc = true;
173 }
174 break;
175  
176 case 'c':
177 if (arg) {
178 set_scansuppress = true;
179 set_scansuppress_value = strtol(arg, NULL, 0);
180 } else {
181 get_scansuppress = true;
182 }
183 break;
184  
185 case 'd':
186 disassociate = true;
187 break;
188  
189 case 'g':
190 custom_cmd_set = false;
191 custom_cmd = strtol(arg, NULL, 0);
192 break;
193  
194 case 's':
195 custom_cmd_set = true;
196 custom_cmd = strtol(arg, NULL, 0);
197 break;
198  
199 case 'l':
200 custom_cmd_buf_len = strtol(arg, NULL, 0);
201 break;
202  
203 case 'k':
204 if (arg) {
205 set_chanspec = true;
206 set_chanspec_value = arg;
207 } else {
208 get_chanspec = true;
209 }
210 break;
211  
212 case 'v':
213 custom_cmd_value = arg;
214 break;
215  
216 case 'i':
217 if (!custom_cmd_value_base64)
218 custom_cmd_value_int = true;
219 else
220 printf("ERR: you can only either use base64 or integer encoding.");
221 break;
222  
223 case 'b':
224 if (!custom_cmd_value_int)
225 custom_cmd_value_base64 = true;
226 else
227 printf("ERR: you can only either use base64 or integer encoding.");
228 break;
229  
230 case 'r':
231 if (!base64_output)
232 raw_output = true;
233 else
234 printf("ERR: you can only either use base64 or raw output.");
235 break;
236  
237 case 'R':
238 if (!raw_output)
239 base64_output = true;
240 else
241 printf("ERR: you can only either use base64 or raw output.");
242 break;
243  
244 case 'o':
245 dump_objmem_addr = strtol(arg, NULL, 0);
246 dump_objmem = true;
247 break;
248  
249 case 'w':
250 dump_wl_cnt = true;
251 break;
252  
253 case 'x':
254 if (arg) {
255 set_securitycookie = true;
256 set_securitycookie_value = strtol(arg, NULL, 0);
257 } else {
258 get_securitycookie = true;
259 }
260 break;
261  
262 case 'X':
263 if (set_securitycookie == false) {
264 use_udp_tunneling = strtol(arg, NULL, 0);
265 if (use_udp_tunneling == 0)
266 printf("ERR: You need to use a security cookie different from 0.");
267 } else {
268 printf("ERR: You cannot use -x in combination with -X.");
269 }
270 break;
271  
272 case 'B':
273 if (arg) {
274 txip = inet_addr(arg);
275 }
276 break;
277  
278 case 'V':
279 revinfo = true;
280 break;
281  
282 default:
283 return ARGP_ERR_UNKNOWN;
284 }
285  
286 return 0;
287 }
288  
289 static struct argp argp = { options, parse_opt, 0, doc };
290  
291 /* source: http://grapsus.net/blog/post/Hexadecimal-dump-in-C */
292 void
293 hexdump(void *mem, unsigned int len)
294 {
295 unsigned int i, j;
296  
297 for(i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++) {
298 if (i % HEXDUMP_COLS == 0) {
299 printf("0x%06x: ", i);
300 }
301  
302 if(i < len) {
303 printf("%02x ", 0xFF & ((char*)mem)[i]);
304 } else {
305 printf(" ");
306 }
307  
308 if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
309 for(j = i - (HEXDUMP_COLS - 1); j <= i; j++) {
310 if(j >= len) {
311 putchar(' ');
312 } else if(isprint(((char*)mem)[j])) {
313 putchar(0xFF & ((char*)mem)[j]);
314 } else {
315 putchar('.');
316 }
317 }
318 putchar('\n');
319 }
320 }
321 }
322  
323 /* Produce a human-readable string for boardrev */
324 char *
325 bcm_brev_str(uint32 brev, char *buf)
326 {
327 if (brev < 0x100)
328 snprintf(buf, 8, "%d.%d", (brev & 0xf0) >> 4, brev & 0xf);
329 else
330 snprintf(buf, 8, "%c%03x", ((brev & 0xf000) == 0x1000) ? 'P' : 'A', brev & 0xfff);
331  
332 return (buf);
333 }
334  
335 int
336 main(int argc, char **argv)
337 {
338 struct nexio *nexio;
339 int ret;
340 int buf = 0;
341  
342 argp_parse(&argp, argc, argv, 0, 0, 0);
343  
344 if (use_udp_tunneling != 0)
345 nexio = nex_init_udp(use_udp_tunneling, txip);
346 else
347 #ifdef USE_NETLINK
348 nexio = nex_init_netlink();
349 #else
350 nexio = nex_init_ioctl(ifname);
351 #endif
352  
353 if (set_monitor) {
354 buf = set_monitor_value;
355 ret = nex_ioctl(nexio, WLC_SET_MONITOR, &buf, 4, true);
356 }
357  
358 if (set_promisc) {
359 buf = set_promisc_value;
360 ret = nex_ioctl(nexio, WLC_SET_PROMISC, &buf, 4, true);
361 }
362  
363 if (set_scansuppress) {
364 buf = set_scansuppress_value;
365 if (set_scansuppress_value)
366 ret = nex_ioctl(nexio, WLC_SET_SCANSUPPRESS, &buf, 4, true);
367 else
368 ret = nex_ioctl(nexio, WLC_SET_SCANSUPPRESS, &buf, 1, true);
369 }
370  
371 if (set_securitycookie) {
372 buf = set_securitycookie_value;
373 ret = nex_ioctl(nexio, NEX_SET_SECURITYCOOKIE, &buf, 4, true);
374 }
375  
376 if (get_monitor) {
377 ret = nex_ioctl(nexio, WLC_GET_MONITOR, &buf, 4, false);
378 printf("monitor: %d\n", buf);
379 }
380  
381 if (get_promisc) {
382 ret = nex_ioctl(nexio, WLC_GET_PROMISC, &buf, 4, false);
383 printf("promisc: %d\n", buf);
384 }
385  
386 if (get_scansuppress) {
387 ret = nex_ioctl(nexio, WLC_GET_SCANSUPPRESS, &buf, 4, false);
388 printf("scansuppress: %d\n", buf);
389 }
390  
391 if (get_securitycookie) {
392 ret = nex_ioctl(nexio, NEX_GET_SECURITYCOOKIE, &buf, 4, false);
393 printf("securitycookie: %d\n", buf);
394 }
395  
396 if (get_chanspec) {
397 char charbuf[9] = "chanspec";
398 uint16 chanspec = 0;
399 ret = nex_ioctl(nexio, WLC_GET_VAR, charbuf, 9, false);
400 chanspec = *(uint16 *) charbuf;
401 printf("chanspec: 0x%04x, %s\n", chanspec, wf_chspec_ntoa(chanspec, charbuf));
402 }
403  
404 if (set_chanspec) {
405 char charbuf[13] = "chanspec";
406 uint32 *chanspec = (uint32 *) &charbuf[9];
407  
408 if (custom_cmd_value_int)
409 *chanspec = strtoul(set_chanspec_value, NULL, 0);
410 else
411 *chanspec = wf_chspec_aton(set_chanspec_value);
412  
413 if (*chanspec == 0)
414 printf("invalid chanspec\n");
415 else
416 ret = nex_ioctl(nexio, WLC_SET_VAR, charbuf, 13, true);
417 }
418  
419 if (custom_cmd_set != -1) {
420 custom_cmd_buf = malloc(custom_cmd_buf_len);
421 if (!custom_cmd_buf)
422 return -1;
423  
424 memset(custom_cmd_buf, 0, custom_cmd_buf_len);
425  
426 if (custom_cmd_set == 1 && raw_output) { // set command using raw input
427 //freopen(NULL, "rb", stdin);
428 fread(custom_cmd_buf, 1, custom_cmd_buf_len, stdin);
429 } else {
430 if (custom_cmd_value) {
431 if (custom_cmd_value_int) {
432 *(uint32 *) custom_cmd_buf = strtoul(custom_cmd_value, NULL, 0);
433 } else if (custom_cmd_value_base64) {
434 size_t decoded_len = 0;
435 unsigned char *decoded = b64_decode_ex(custom_cmd_value, strlen(custom_cmd_value), &decoded_len);
436 memcpy(custom_cmd_buf, decoded, MIN(decoded_len, custom_cmd_buf_len));
437 } else {
438 strncpy(custom_cmd_buf, custom_cmd_value, custom_cmd_buf_len);
439 }
440 } else {
441 if (custom_cmd_value_int) {
442 *(uint32 *) custom_cmd_buf = strtoul(custom_cmd_value, NULL, 0);
443 }
444 }
445 }
446  
447 /* NOTICE: Using SDIO to communicate to the firmware, the maximum CDC message length
448 * is limited to CDC_MAX_MSG_SIZE = ETHER_MAX_LEN = 1518, however only 1502 bytes
449 * arrive in the ioctl function, the rest might be used for the ioctl header.
450 */
451 if (custom_cmd_buf_len > 1502 && custom_cmd_set)
452 fprintf(stderr, "WARN: Using SDIO, the ioctl payload length is limited to 1502 bytes.\n");
453 ret = nex_ioctl(nexio, custom_cmd, custom_cmd_buf, custom_cmd_buf_len, custom_cmd_set);
454  
455 if (custom_cmd_set == false) {
456 if (raw_output) {
457 fwrite(custom_cmd_buf, sizeof(char), custom_cmd_buf_len, stdout);
458 fflush(stdout);
459 } else if (base64_output) {
460 char *encoded = b64_encode(custom_cmd_buf, custom_cmd_buf_len);
461 fwrite(encoded, sizeof(char), strlen(encoded), stdout);
462 fflush(stdout);
463 } else {
464 hexdump(custom_cmd_buf, custom_cmd_buf_len);
465 }
466 }
467 }
468  
469 if (dump_objmem) {
470 custom_cmd_buf = malloc(custom_cmd_buf_len);
471 if (!custom_cmd_buf)
472 return -1;
473  
474 memset(custom_cmd_buf, 0, custom_cmd_buf_len);
475  
476 unsigned int *custom_cmd_buf_pos = (unsigned int *) custom_cmd_buf;
477  
478 int i = 0;
479 for (i = 0; i < custom_cmd_buf_len / 0x2000; i++) {
480 *custom_cmd_buf_pos = dump_objmem_addr + i * 0x2000 / 4;
481 printf("%8p %08x\n", custom_cmd_buf_pos, *custom_cmd_buf_pos);
482 ret = nex_ioctl(nexio, 406, custom_cmd_buf_pos, 0x2000, false);
483 custom_cmd_buf_pos += 0x2000 / 4;
484 }
485 if (custom_cmd_buf_len % 0x2000 != 0) {
486 *(unsigned int *) custom_cmd_buf_pos = dump_objmem_addr + i * 0x2000 / 4;
487 ret = nex_ioctl(nexio, 406, custom_cmd_buf_pos, custom_cmd_buf_len % 0x2000, false);
488 }
489  
490 if (raw_output) {
491 fwrite(custom_cmd_buf, sizeof(char), custom_cmd_buf_len, stdout);
492 fflush(stdout);
493 } else {
494 hexdump(custom_cmd_buf, custom_cmd_buf_len);
495 }
496 }
497  
498 if (disassociate) {
499 buf = 1;
500 ret = nex_ioctl(nexio, WLC_DISASSOC, &buf, 4, true);
501 }
502  
503 if (dump_wl_cnt) {
504 wl_cnt_t cnt;
505 wl_cnt_t *_cnt = &cnt;
506 memset(_cnt, 0, sizeof(cnt));
507 ret = nex_ioctl(nexio, NEX_GET_WL_CNT, _cnt, sizeof(cnt), false);
508 unsigned int i;
509 printf("version: %d\n", _cnt->version);
510 printf("length: %d\n", _cnt->length);
511 for (i = 1; i < sizeof(cnt)/4; i++) {
512 printf("%s: %d (%s)\n", wl_cnt_varname[i], ((uint32 *) _cnt)[i], wl_cnt_description[i]);
513 }
514 //hexdump(_cnt, sizeof(_cnt));
515 }
516  
517 if (revinfo) {
518 typedef struct wlc_rev_info {
519 uint vendorid; /* PCI vendor id */
520 uint deviceid; /* device id of chip */
521 uint radiorev; /* radio revision */
522 uint chiprev; /* chip revision */
523 uint corerev; /* core revision */
524 uint boardid; /* board identifier (usu. PCI sub-device id) */
525 uint boardvendor; /* board vendor (usu. PCI sub-vendor id) */
526 uint boardrev; /* board revision */
527 uint driverrev; /* driver version */
528 uint ucoderev; /* microcode version */
529 uint bus; /* bus type */
530 uint chipnum; /* chip number */
531 uint phytype; /* phy type */
532 uint phyrev; /* phy revision */
533 uint anarev; /* anacore rev */
534 uint chippkg; /* chip package info */
535 uint nvramrev; /* nvram revision number */
536 } wlc_rev_info_t;
537  
538 char b[8];
539 char str[17][32] = { 0 };
540 wlc_rev_info_t revinfo;
541  
542 memset(&revinfo, 0, sizeof(revinfo));
543  
544 ret = nex_ioctl(nexio, WLC_GET_REVINFO, &revinfo, sizeof(revinfo), false);
545  
546 #ifdef ANDROID
547 char model_string[PROP_VALUE_MAX + 1];
548 __system_property_get("ro.product.model", model_string);
549 #else
550 char model_string[] = "unknown";
551 #endif
552 char fw_ver[256] = "ver\0";
553 nex_ioctl(nexio, WLC_GET_VAR, fw_ver, sizeof(fw_ver), false);
554 char *fw_ver2 = strstr(fw_ver, "version") + 8;
555 fw_ver2[strlen(fw_ver2) - 1] = 0;
556  
557 snprintf(str[0], sizeof(str[0]), "0x%x", revinfo.vendorid);
558 snprintf(str[1], sizeof(str[0]), "0x%x", revinfo.deviceid);
559 snprintf(str[2], sizeof(str[0]), "0x%x", revinfo.radiorev);
560 snprintf(str[3], sizeof(str[0]), "0x%x", revinfo.chipnum);
561 snprintf(str[4], sizeof(str[0]), "0x%x", revinfo.chiprev);
562 snprintf(str[5], sizeof(str[0]), "0x%x", revinfo.chippkg);
563 snprintf(str[6], sizeof(str[0]), "0x%x", revinfo.corerev);
564 snprintf(str[7], sizeof(str[0]), "0x%x", revinfo.boardid);
565 snprintf(str[8], sizeof(str[0]), "0x%x", revinfo.boardvendor);
566 snprintf(str[9], sizeof(str[0]), "%s", bcm_brev_str(revinfo.boardrev, b));
567 snprintf(str[10], sizeof(str[0]), "0x%x", revinfo.driverrev);
568 snprintf(str[11], sizeof(str[0]), "0x%x", revinfo.ucoderev);
569 snprintf(str[12], sizeof(str[0]), "0x%x", revinfo.bus);
570 snprintf(str[13], sizeof(str[0]), "0x%x", revinfo.phytype);
571 snprintf(str[14], sizeof(str[0]), "0x%x", revinfo.phyrev);
572 snprintf(str[15], sizeof(str[0]), "0x%x", revinfo.anarev);
573 snprintf(str[16], sizeof(str[0]), "0x%x", revinfo.nvramrev);
574  
575 #ifdef ANDROID
576 printf("platform %s\n", model_string);
577 #endif
578 printf("firmware %s\n", fw_ver2);
579 printf("vendorid %s\n", str[0]);
580 printf("deviceid %s\n", str[1]);
581 printf("radiorev %s\n", str[2]);
582 printf("chipnum %s\n", str[3]);
583 printf("chiprev %s\n", str[4]);
584 printf("chippackage %s\n", str[5]);
585 printf("corerev %s\n", str[6]);
586 printf("boardid %s\n", str[7]);
587 printf("boardvendor %s\n", str[8]);
588 printf("boardrev %s\n", str[9]);
589 printf("driverrev %s\n", str[10]);
590 printf("ucoderev %s\n", str[11]);
591 printf("bus %s\n", str[12]);
592 printf("phytype %s\n", str[13]);
593 printf("phyrev %s\n", str[14]);
594 printf("anarev %s\n", str[15]);
595 printf("nvramrev %s\n", str[16]);
596  
597 printf("\n");
598 printf("platform | firmware | vendorid | deviceid | radiorev | chipnum | chiprev | chippackage | corerev | boardid | boardvendor | boardrev | driverrev | ucoderev | bus | phytype | phyrev | anarev | nvramrev\n");
599 printf("-------------------- | -------------------------------- | -------- | -------- | ---------- | ------- | ------- | ----------- | ------- | ------- | ----------- | -------- | --------- | --------- | --- | ------- | ------ | ------ | --------\n");
600 printf("%-20s | %-32s | %8s | %8s | %10s | %7s | %7s | %11s | %7s | %7s | %11s | %8s | %9s | %9s | %3s | %7s | %6s | %6s | %8s\n",
601 model_string, fw_ver2, str[0], str[1], str[2], str[3], str[4], str[5], str[6], str[7], str[8], str[9], str[10], str[11], str[12], str[13], str[14], str[15], str[16]);
602 }
603  
604 return 0;
605 }