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