/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2000-2001 Qualcomm Incorporated * Copyright (C) 2002-2003 Maxim Krasnyansky * Copyright (C) 2002-2009 Marcel Holtmann * * * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "textfile.h" #include "csr.h" static struct hci_dev_info di; static int all; static void print_dev_hdr(struct hci_dev_info *di); static void print_dev_info(int ctl, struct hci_dev_info *di); static void print_dev_list(int ctl, int flags) { struct hci_dev_list_req *dl; struct hci_dev_req *dr; int i; if (!(dl = malloc(HCI_MAX_DEV * sizeof(struct hci_dev_req) + sizeof(uint16_t)))) { perror("Can't allocate memory"); exit(1); } dl->dev_num = HCI_MAX_DEV; dr = dl->dev_req; if (ioctl(ctl, HCIGETDEVLIST, (void *) dl) < 0) { perror("Can't get device list"); exit(1); } for (i = 0; i< dl->dev_num; i++) { di.dev_id = (dr+i)->dev_id; if (ioctl(ctl, HCIGETDEVINFO, (void *) &di) < 0) continue; if (hci_test_bit(HCI_RAW, &di.flags) && !bacmp(&di.bdaddr, BDADDR_ANY)) { int dd = hci_open_dev(di.dev_id); hci_read_bd_addr(dd, &di.bdaddr, 1000); hci_close_dev(dd); } print_dev_info(ctl, &di); } } static void print_pkt_type(struct hci_dev_info *di) { char *str; str = hci_ptypetostr(di->pkt_type); printf("\tPacket type: %s\n", str); bt_free(str); } static void print_link_policy(struct hci_dev_info *di) { printf("\tLink policy: %s\n", hci_lptostr(di->link_policy)); } static void print_link_mode(struct hci_dev_info *di) { char *str; str = hci_lmtostr(di->link_mode); printf("\tLink mode: %s\n", str); bt_free(str); } static void print_dev_features(struct hci_dev_info *di, int format) { printf("\tFeatures: 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x " "0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n", di->features[0], di->features[1], di->features[2], di->features[3], di->features[4], di->features[5], di->features[6], di->features[7]); if (format) { char *tmp = lmp_featurestostr(di->features, "\t\t", 63); printf("%s\n", tmp); bt_free(tmp); } } static void cmd_rstat(int ctl, int hdev, char *opt) { /* Reset HCI device stat counters */ if (ioctl(ctl, HCIDEVRESTAT, hdev) < 0) { fprintf(stderr, "Can't reset stats counters hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } static void cmd_scan(int ctl, int hdev, char *opt) { struct hci_dev_req dr; dr.dev_id = hdev; dr.dev_opt = SCAN_DISABLED; if (!strcmp(opt, "iscan")) dr.dev_opt = SCAN_INQUIRY; else if (!strcmp(opt, "pscan")) dr.dev_opt = SCAN_PAGE; else if (!strcmp(opt, "piscan")) dr.dev_opt = SCAN_PAGE | SCAN_INQUIRY; if (ioctl(ctl, HCISETSCAN, (unsigned long) &dr) < 0) { fprintf(stderr, "Can't set scan mode on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } static void cmd_iac(int ctl, int hdev, char *opt) { int s = hci_open_dev(hdev); if (s < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (opt) { int l = strtoul(opt, 0, 16); uint8_t lap[3]; if (!strcasecmp(opt, "giac")) { l = 0x9e8b33; } else if (!strcasecmp(opt, "liac")) { l = 0x9e8b00; } else if (l < 0x9e8b00 || l > 0x9e8b3f) { printf("Invalid access code 0x%x\n", l); exit(1); } lap[0] = (l & 0xff); lap[1] = (l >> 8) & 0xff; lap[2] = (l >> 16) & 0xff; if (hci_write_current_iac_lap(s, 1, lap, 1000) < 0) { printf("Failed to set IAC on hci%d: %s\n", hdev, strerror(errno)); exit(1); } } else { uint8_t lap[3 * MAX_IAC_LAP]; int i, j; uint8_t n; if (hci_read_current_iac_lap(s, &n, lap, 1000) < 0) { printf("Failed to read IAC from hci%d: %s\n", hdev, strerror(errno)); exit(1); } print_dev_hdr(&di); printf("\tIAC: "); for (i = 0; i < n; i++) { printf("0x"); for (j = 3; j--; ) printf("%02x", lap[j + 3 * i]); if (i < n - 1) printf(", "); } printf("\n"); } close(s); } static void cmd_auth(int ctl, int hdev, char *opt) { struct hci_dev_req dr; dr.dev_id = hdev; if (!strcmp(opt, "auth")) dr.dev_opt = AUTH_ENABLED; else dr.dev_opt = AUTH_DISABLED; if (ioctl(ctl, HCISETAUTH, (unsigned long) &dr) < 0) { fprintf(stderr, "Can't set auth on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } static void cmd_encrypt(int ctl, int hdev, char *opt) { struct hci_dev_req dr; dr.dev_id = hdev; if (!strcmp(opt, "encrypt")) dr.dev_opt = ENCRYPT_P2P; else dr.dev_opt = ENCRYPT_DISABLED; if (ioctl(ctl, HCISETENCRYPT, (unsigned long) &dr) < 0) { fprintf(stderr, "Can't set encrypt on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } static void cmd_up(int ctl, int hdev, char *opt) { /* Start HCI device */ if (ioctl(ctl, HCIDEVUP, hdev) < 0) { if (errno == EALREADY) return; fprintf(stderr, "Can't init device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } static void cmd_down(int ctl, int hdev, char *opt) { /* Stop HCI device */ if (ioctl(ctl, HCIDEVDOWN, hdev) < 0) { fprintf(stderr, "Can't down device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } static void cmd_reset(int ctl, int hdev, char *opt) { /* Reset HCI device */ #if 0 if (ioctl(ctl, HCIDEVRESET, hdev) < 0 ){ fprintf(stderr, "Reset failed for device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } #endif cmd_down(ctl, hdev, "down"); cmd_up(ctl, hdev, "up"); } static void cmd_ptype(int ctl, int hdev, char *opt) { struct hci_dev_req dr; dr.dev_id = hdev; if (hci_strtoptype(opt, &dr.dev_opt)) { if (ioctl(ctl, HCISETPTYPE, (unsigned long) &dr) < 0) { fprintf(stderr, "Can't set pkttype on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } else { print_dev_hdr(&di); print_pkt_type(&di); } } static void cmd_lp(int ctl, int hdev, char *opt) { struct hci_dev_req dr; dr.dev_id = hdev; if (hci_strtolp(opt, &dr.dev_opt)) { if (ioctl(ctl, HCISETLINKPOL, (unsigned long) &dr) < 0) { fprintf(stderr, "Can't set link policy on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } else { print_dev_hdr(&di); print_link_policy(&di); } } static void cmd_lm(int ctl, int hdev, char *opt) { struct hci_dev_req dr; dr.dev_id = hdev; if (hci_strtolm(opt, &dr.dev_opt)) { if (ioctl(ctl, HCISETLINKMODE, (unsigned long) &dr) < 0) { fprintf(stderr, "Can't set default link mode on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } else { print_dev_hdr(&di); print_link_mode(&di); } } static void cmd_aclmtu(int ctl, int hdev, char *opt) { struct hci_dev_req dr = { dev_id: hdev }; uint16_t mtu, mpkt; if (!opt) return; if (sscanf(opt, "%4hu:%4hu", &mtu, &mpkt) != 2) return; dr.dev_opt = htobl(htobs(mpkt) | (htobs(mtu) << 16)); if (ioctl(ctl, HCISETACLMTU, (unsigned long) &dr) < 0) { fprintf(stderr, "Can't set ACL mtu on hci%d: %s(%d)\n", hdev, strerror(errno), errno); exit(1); } } static void cmd_scomtu(int ctl, int hdev, char *opt) { struct hci_dev_req dr = { dev_id: hdev }; uint16_t mtu, mpkt; if (!opt) return; if (sscanf(opt, "%4hu:%4hu", &mtu, &mpkt) != 2) return; dr.dev_opt = htobl(htobs(mpkt) | (htobs(mtu) << 16)); if (ioctl(ctl, HCISETSCOMTU, (unsigned long) &dr) < 0) { fprintf(stderr, "Can't set SCO mtu on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } static void cmd_features(int ctl, int hdev, char *opt) { uint8_t features[8], max_page = 0; char *tmp; int i, dd; if (!(di.features[7] & LMP_EXT_FEAT)) { print_dev_hdr(&di); print_dev_features(&di, 1); return; } dd = hci_open_dev(hdev); if (dd < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (hci_read_local_ext_features(dd, 0, &max_page, features, 1000) < 0) { fprintf(stderr, "Can't read extended features hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } print_dev_hdr(&di); printf("\tFeatures%s: 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x " "0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n", (max_page > 0) ? " page 0" : "", features[0], features[1], features[2], features[3], features[4], features[5], features[6], features[7]); tmp = lmp_featurestostr(di.features, "\t\t", 63); printf("%s\n", tmp); bt_free(tmp); for (i = 1; i <= max_page; i++) { if (hci_read_local_ext_features(dd, i, NULL, features, 1000) < 0) continue; printf("\tFeatures page %d: 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x " "0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n", i, features[0], features[1], features[2], features[3], features[4], features[5], features[6], features[7]); } hci_close_dev(dd); } static void cmd_name(int ctl, int hdev, char *opt) { int dd; dd = hci_open_dev(hdev); if (dd < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (opt) { if (hci_write_local_name(dd, opt, 2000) < 0) { fprintf(stderr, "Can't change local name on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } else { char name[249]; int i; if (hci_read_local_name(dd, sizeof(name), name, 1000) < 0) { fprintf(stderr, "Can't read local name on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } for (i = 0; i < 248 && name[i]; i++) { if ((unsigned char) name[i] < 32 || name[i] == 127) name[i] = '.'; } name[248] = '\0'; print_dev_hdr(&di); printf("\tName: '%s'\n", name); } hci_close_dev(dd); } /* * see http://www.bluetooth.org/assigned-numbers/baseband.htm --- all * strings are reproduced verbatim */ static char *get_minor_device_name(int major, int minor) { switch (major) { case 0: /* misc */ return ""; case 1: /* computer */ switch(minor) { case 0: return "Uncategorized"; case 1: return "Desktop workstation"; case 2: return "Server"; case 3: return "Laptop"; case 4: return "Handheld"; case 5: return "Palm"; case 6: return "Wearable"; } break; case 2: /* phone */ switch(minor) { case 0: return "Uncategorized"; case 1: return "Cellular"; case 2: return "Cordless"; case 3: return "Smart phone"; case 4: return "Wired modem or voice gateway"; case 5: return "Common ISDN Access"; case 6: return "Sim Card Reader"; } break; case 3: /* lan access */ if (minor == 0) return "Uncategorized"; switch(minor / 8) { case 0: return "Fully available"; case 1: return "1-17% utilized"; case 2: return "17-33% utilized"; case 3: return "33-50% utilized"; case 4: return "50-67% utilized"; case 5: return "67-83% utilized"; case 6: return "83-99% utilized"; case 7: return "No service available"; } break; case 4: /* audio/video */ switch(minor) { case 0: return "Uncategorized"; case 1: return "Device conforms to the Headset profile"; case 2: return "Hands-free"; /* 3 is reserved */ case 4: return "Microphone"; case 5: return "Loudspeaker"; case 6: return "Headphones"; case 7: return "Portable Audio"; case 8: return "Car Audio"; case 9: return "Set-top box"; case 10: return "HiFi Audio Device"; case 11: return "VCR"; case 12: return "Video Camera"; case 13: return "Camcorder"; case 14: return "Video Monitor"; case 15: return "Video Display and Loudspeaker"; case 16: return "Video Conferencing"; /* 17 is reserved */ case 18: return "Gaming/Toy"; } break; case 5: /* peripheral */ { static char cls_str[48]; cls_str[0] = '\0'; switch(minor & 48) { case 16: strncpy(cls_str, "Keyboard", sizeof(cls_str)); break; case 32: strncpy(cls_str, "Pointing device", sizeof(cls_str)); break; case 48: strncpy(cls_str, "Combo keyboard/pointing device", sizeof(cls_str)); break; } if((minor & 15) && (strlen(cls_str) > 0)) strcat(cls_str, "/"); switch(minor & 15) { case 0: break; case 1: strncat(cls_str, "Joystick", sizeof(cls_str) - strlen(cls_str)); break; case 2: strncat(cls_str, "Gamepad", sizeof(cls_str) - strlen(cls_str)); break; case 3: strncat(cls_str, "Remote control", sizeof(cls_str) - strlen(cls_str)); break; case 4: strncat(cls_str, "Sensing device", sizeof(cls_str) - strlen(cls_str)); break; case 5: strncat(cls_str, "Digitizer tablet", sizeof(cls_str) - strlen(cls_str)); break; case 6: strncat(cls_str, "Card reader", sizeof(cls_str) - strlen(cls_str)); break; default: strncat(cls_str, "(reserved)", sizeof(cls_str) - strlen(cls_str)); break; } if(strlen(cls_str) > 0) return cls_str; } case 6: /* imaging */ if (minor & 4) return "Display"; if (minor & 8) return "Camera"; if (minor & 16) return "Scanner"; if (minor & 32) return "Printer"; break; case 7: /* wearable */ switch(minor) { case 1: return "Wrist Watch"; case 2: return "Pager"; case 3: return "Jacket"; case 4: return "Helmet"; case 5: return "Glasses"; } break; case 8: /* toy */ switch(minor) { case 1: return "Robot"; case 2: return "Vehicle"; case 3: return "Doll / Action Figure"; case 4: return "Controller"; case 5: return "Game"; } break; case 63: /* uncategorised */ return ""; } return "Unknown (reserved) minor device class"; } static void cmd_class(int ctl, int hdev, char *opt) { static const char *services[] = { "Positioning", "Networking", "Rendering", "Capturing", "Object Transfer", "Audio", "Telephony", "Information" }; static const char *major_devices[] = { "Miscellaneous", "Computer", "Phone", "LAN Access", "Audio/Video", "Peripheral", "Imaging", "Uncategorized" }; int s = hci_open_dev(hdev); if (s < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (opt) { uint32_t cod = strtoul(opt, NULL, 16); if (hci_write_class_of_dev(s, cod, 2000) < 0) { fprintf(stderr, "Can't write local class of device on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } else { uint8_t cls[3]; if (hci_read_class_of_dev(s, cls, 1000) < 0) { fprintf(stderr, "Can't read class of device on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } print_dev_hdr(&di); printf("\tClass: 0x%02x%02x%02x\n", cls[2], cls[1], cls[0]); printf("\tService Classes: "); if (cls[2]) { unsigned int i; int first = 1; for (i = 0; i < (sizeof(services) / sizeof(*services)); i++) if (cls[2] & (1 << i)) { if (!first) printf(", "); printf("%s", services[i]); first = 0; } } else printf("Unspecified"); printf("\n\tDevice Class: "); if ((cls[1] & 0x1f) >= sizeof(major_devices) / sizeof(*major_devices)) printf("Invalid Device Class!\n"); else printf("%s, %s\n", major_devices[cls[1] & 0x1f], get_minor_device_name(cls[1] & 0x1f, cls[0] >> 2)); } } static void cmd_voice(int ctl, int hdev, char *opt) { static char *icf[] = { "Linear", "u-Law", "A-Law", "Reserved" }; static char *idf[] = { "1's complement", "2's complement", "Sign-Magnitude", "Reserved" }; static char *iss[] = { "8 bit", "16 bit" }; static char *acf[] = { "CVSD", "u-Law", "A-Law", "Reserved" }; int s = hci_open_dev(hdev); if (s < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (opt) { uint16_t vs = htobs(strtoul(opt, NULL, 16)); if (hci_write_voice_setting(s, vs, 2000) < 0) { fprintf(stderr, "Can't write voice setting on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } else { uint16_t vs; uint8_t ic; if (hci_read_voice_setting(s, &vs, 1000) < 0) { fprintf(stderr, "Can't read voice setting on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } vs = htobs(vs); ic = (vs & 0x0300) >> 8; print_dev_hdr(&di); printf("\tVoice setting: 0x%04x%s\n", vs, ((vs & 0x03fc) == 0x0060) ? " (Default Condition)" : ""); printf("\tInput Coding: %s\n", icf[ic]); printf("\tInput Data Format: %s\n", idf[(vs & 0xc0) >> 6]); if (!ic) { printf("\tInput Sample Size: %s\n", iss[(vs & 0x20) >> 5]); printf("\t# of bits padding at MSB: %d\n", (vs & 0x1c) >> 2); } printf("\tAir Coding Format: %s\n", acf[vs & 0x03]); } } static int get_link_key(const bdaddr_t *local, const bdaddr_t *peer, uint8_t *key) { char filename[PATH_MAX + 1], addr[18], tmp[3], *str; int i; ba2str(local, addr); create_name(filename, PATH_MAX, STORAGEDIR, addr, "linkkeys"); ba2str(peer, addr); str = textfile_get(filename, addr); if (!str) return -EIO; memset(tmp, 0, sizeof(tmp)); for (i = 0; i < 16; i++) { memcpy(tmp, str + (i * 2), 2); key[i] = (uint8_t) strtol(tmp, NULL, 16); } free(str); return 0; } static void cmd_putkey(int ctl, int hdev, char *opt) { struct hci_dev_info di; bdaddr_t bdaddr; uint8_t key[16]; int dd; if (!opt) return; dd = hci_open_dev(hdev); if (dd < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (hci_devinfo(hdev, &di) < 0) { fprintf(stderr, "Can't get device info for hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } str2ba(opt, &bdaddr); if (get_link_key(&di.bdaddr, &bdaddr, key) < 0) { fprintf(stderr, "Can't find link key for %s on hci%d\n", opt, hdev); exit(1); } if (hci_write_stored_link_key(dd, &bdaddr, key, 1000) < 0) { fprintf(stderr, "Can't write stored link key on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } hci_close_dev(dd); } static void cmd_delkey(int ctl, int hdev, char *opt) { bdaddr_t bdaddr; uint8_t all; int dd; if (!opt) return; dd = hci_open_dev(hdev); if (dd < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (!strcasecmp(opt, "all")) { bacpy(&bdaddr, BDADDR_ANY); all = 1; } else { str2ba(opt, &bdaddr); all = 0; } if (hci_delete_stored_link_key(dd, &bdaddr, all, 1000) < 0) { fprintf(stderr, "Can't delete stored link key on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } hci_close_dev(dd); } static void cmd_oob_data(int ctl, int hdev, char *opt) { uint8_t hash[16], randomizer[16]; int i, dd; dd = hci_open_dev(hdev); if (dd < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (hci_read_local_oob_data(dd, hash, randomizer, 1000) < 0) { fprintf(stderr, "Can't read local OOB data on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } print_dev_hdr(&di); printf("\tOOB Hash: "); for (i = 0; i < 16; i++) printf(" %02x", hash[i]); printf("\n\tRandomizer:"); for (i = 0; i < 16; i++) printf(" %02x", randomizer[i]); printf("\n"); hci_close_dev(dd); } static void cmd_commands(int ctl, int hdev, char *opt) { uint8_t cmds[64]; char *str; int i, n, dd; dd = hci_open_dev(hdev); if (dd < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (hci_read_local_commands(dd, cmds, 1000) < 0) { fprintf(stderr, "Can't read support commands on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } print_dev_hdr(&di); for (i = 0; i < 64; i++) { if (!cmds[i]) continue; printf("%s Octet %-2d = 0x%02x (Bit", i ? "\t\t ": "\tCommands:", i, cmds[i]); for (n = 0; n < 8; n++) if (cmds[i] & (1 << n)) printf(" %d", n); printf(")\n"); } str = hci_commandstostr(cmds, "\t", 71); printf("%s\n", str); bt_free(str); hci_close_dev(dd); } static void cmd_version(int ctl, int hdev, char *opt) { struct hci_version ver; char *hciver, *lmpver; int dd; dd = hci_open_dev(hdev); if (dd < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (hci_read_local_version(dd, &ver, 1000) < 0) { fprintf(stderr, "Can't read version info hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } hciver = hci_vertostr(ver.hci_ver); lmpver = lmp_vertostr(ver.hci_ver); print_dev_hdr(&di); printf("\tHCI Ver: %s (0x%x) HCI Rev: 0x%x LMP Ver: %s (0x%x) LMP Subver: 0x%x\n" "\tManufacturer: %s (%d)\n", hciver ? hciver : "n/a", ver.hci_ver, ver.hci_rev, lmpver ? lmpver : "n/a", ver.lmp_ver, ver.lmp_subver, bt_compidtostr(ver.manufacturer), ver.manufacturer); if (hciver) bt_free(hciver); if (lmpver) bt_free(lmpver); hci_close_dev(dd); } static void cmd_inq_tpl(int ctl, int hdev, char *opt) { int dd; dd = hci_open_dev(hdev); if (dd < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (opt) { int8_t level = atoi(opt); if (hci_write_inquiry_transmit_power_level(dd, level, 2000) < 0) { fprintf(stderr, "Can't set inquiry transmit power level on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } else { int8_t level; if (hci_read_inquiry_transmit_power_level(dd, &level, 1000) < 0) { fprintf(stderr, "Can't read inquiry transmit power level on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } print_dev_hdr(&di); printf("\tInquiry transmit power level: %d\n", level); } hci_close_dev(dd); } static void cmd_inq_mode(int ctl, int hdev, char *opt) { int dd; dd = hci_open_dev(hdev); if (dd < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (opt) { uint8_t mode = atoi(opt); if (hci_write_inquiry_mode(dd, mode, 2000) < 0) { fprintf(stderr, "Can't set inquiry mode on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } else { uint8_t mode; if (hci_read_inquiry_mode(dd, &mode, 1000) < 0) { fprintf(stderr, "Can't read inquiry mode on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } print_dev_hdr(&di); printf("\tInquiry mode: "); switch (mode) { case 0: printf("Standard Inquiry\n"); break; case 1: printf("Inquiry with RSSI\n"); break; case 2: printf("Inquiry with RSSI or Extended Inquiry\n"); break; default: printf("Unknown (0x%02x)\n", mode); break; } } hci_close_dev(dd); } static void cmd_inq_data(int ctl, int hdev, char *opt) { int i, dd; dd = hci_open_dev(hdev); if (dd < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (opt) { uint8_t fec = 0, data[240]; char tmp[3]; int i, size; memset(data, 0, sizeof(data)); memset(tmp, 0, sizeof(tmp)); size = (strlen(opt) + 1) / 2; if (size > 240) size = 240; for (i = 0; i < size; i++) { memcpy(tmp, opt + (i * 2), 2); data[i] = strtol(tmp, NULL, 16); } if (hci_write_ext_inquiry_response(dd, fec, data, 2000) < 0) { fprintf(stderr, "Can't set extended inquiry response on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } else { uint8_t fec, data[240], len, type, *ptr; char *str; if (hci_read_ext_inquiry_response(dd, &fec, data, 1000) < 0) { fprintf(stderr, "Can't read extended inquiry response on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } print_dev_hdr(&di); printf("\tFEC %s\n\t\t", fec ? "enabled" : "disabled"); for (i = 0; i < 240; i++) printf("%02x%s%s", data[i], (i + 1) % 8 ? "" : " ", (i + 1) % 16 ? " " : (i < 239 ? "\n\t\t" : "\n")); ptr = data; while (*ptr) { len = *ptr++; type = *ptr++; switch (type) { case 0x01: printf("\tFlags:"); for (i = 0; i < len - 1; i++) printf(" 0x%2.2x", *((uint8_t *) (ptr + i))); printf("\n"); break; case 0x02: case 0x03: printf("\t%s service classes:", type == 0x02 ? "Shortened" : "Complete"); for (i = 0; i < (len - 1) / 2; i++) { uint16_t val = btohs(bt_get_unaligned((uint16_t *) (ptr + (i * 2)))); printf(" 0x%4.4x", val); } printf("\n"); break; case 0x08: case 0x09: str = malloc(len); if (str) { snprintf(str, len, "%s", ptr); for (i = 0; i < len - 1; i++) { if ((unsigned char) str[i] < 32 || str[i] == 127) str[i] = '.'; } printf("\t%s local name: \'%s\'\n", type == 0x08 ? "Shortened" : "Complete", str); free(str); } break; case 0x0a: printf("\tTX power level: %d\n", *((uint8_t *) ptr)); break; default: printf("\tUnknown type 0x%02x with %d bytes data\n", type, len - 1); break; } ptr += (len - 1); } printf("\n"); } hci_close_dev(dd); } static void cmd_inq_type(int ctl, int hdev, char *opt) { int dd; dd = hci_open_dev(hdev); if (dd < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (opt) { uint8_t type = atoi(opt); if (hci_write_inquiry_scan_type(dd, type, 2000) < 0) { fprintf(stderr, "Can't set inquiry scan type on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } else { uint8_t type; if (hci_read_inquiry_scan_type(dd, &type, 1000) < 0) { fprintf(stderr, "Can't read inquiry scan type on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } print_dev_hdr(&di); printf("\tInquiry scan type: %s\n", type == 1 ? "Interlaced Inquiry Scan" : "Standard Inquiry Scan"); } hci_close_dev(dd); } static void cmd_inq_parms(int ctl, int hdev, char *opt) { struct hci_request rq; int s; if ((s = hci_open_dev(hdev)) < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } memset(&rq, 0, sizeof(rq)); if (opt) { unsigned int window, interval; write_inq_activity_cp cp; if (sscanf(opt,"%4u:%4u", &window, &interval) != 2) { printf("Invalid argument format\n"); exit(1); } rq.ogf = OGF_HOST_CTL; rq.ocf = OCF_WRITE_INQ_ACTIVITY; rq.cparam = &cp; rq.clen = WRITE_INQ_ACTIVITY_CP_SIZE; cp.window = htobs((uint16_t) window); cp.interval = htobs((uint16_t) interval); if (window < 0x12 || window > 0x1000) printf("Warning: inquiry window out of range!\n"); if (interval < 0x12 || interval > 0x1000) printf("Warning: inquiry interval out of range!\n"); if (hci_send_req(s, &rq, 2000) < 0) { fprintf(stderr, "Can't set inquiry parameters name on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } else { uint16_t window, interval; read_inq_activity_rp rp; rq.ogf = OGF_HOST_CTL; rq.ocf = OCF_READ_INQ_ACTIVITY; rq.rparam = &rp; rq.rlen = READ_INQ_ACTIVITY_RP_SIZE; if (hci_send_req(s, &rq, 1000) < 0) { fprintf(stderr, "Can't read inquiry parameters on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (rp.status) { printf("Read inquiry parameters on hci%d returned status %d\n", hdev, rp.status); exit(1); } print_dev_hdr(&di); window = btohs(rp.window); interval = btohs(rp.interval); printf("\tInquiry interval: %u slots (%.2f ms), window: %u slots (%.2f ms)\n", interval, (float)interval * 0.625, window, (float)window * 0.625); } } static void cmd_page_parms(int ctl, int hdev, char *opt) { struct hci_request rq; int s; if ((s = hci_open_dev(hdev)) < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } memset(&rq, 0, sizeof(rq)); if (opt) { unsigned int window, interval; write_page_activity_cp cp; if (sscanf(opt,"%4u:%4u", &window, &interval) != 2) { printf("Invalid argument format\n"); exit(1); } rq.ogf = OGF_HOST_CTL; rq.ocf = OCF_WRITE_PAGE_ACTIVITY; rq.cparam = &cp; rq.clen = WRITE_PAGE_ACTIVITY_CP_SIZE; cp.window = htobs((uint16_t) window); cp.interval = htobs((uint16_t) interval); if (window < 0x12 || window > 0x1000) printf("Warning: page window out of range!\n"); if (interval < 0x12 || interval > 0x1000) printf("Warning: page interval out of range!\n"); if (hci_send_req(s, &rq, 2000) < 0) { fprintf(stderr, "Can't set page parameters name on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } else { uint16_t window, interval; read_page_activity_rp rp; rq.ogf = OGF_HOST_CTL; rq.ocf = OCF_READ_PAGE_ACTIVITY; rq.rparam = &rp; rq.rlen = READ_PAGE_ACTIVITY_RP_SIZE; if (hci_send_req(s, &rq, 1000) < 0) { fprintf(stderr, "Can't read page parameters on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (rp.status) { printf("Read page parameters on hci%d returned status %d\n", hdev, rp.status); exit(1); } print_dev_hdr(&di); window = btohs(rp.window); interval = btohs(rp.interval); printf("\tPage interval: %u slots (%.2f ms), window: %u slots (%.2f ms)\n", interval, (float)interval * 0.625, window, (float)window * 0.625); } } static void cmd_page_to(int ctl, int hdev, char *opt) { struct hci_request rq; int s; if ((s = hci_open_dev(hdev)) < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } memset(&rq, 0, sizeof(rq)); if (opt) { unsigned int timeout; write_page_timeout_cp cp; if (sscanf(opt,"%5u", &timeout) != 1) { printf("Invalid argument format\n"); exit(1); } rq.ogf = OGF_HOST_CTL; rq.ocf = OCF_WRITE_PAGE_TIMEOUT; rq.cparam = &cp; rq.clen = WRITE_PAGE_TIMEOUT_CP_SIZE; cp.timeout = htobs((uint16_t) timeout); if (timeout < 0x01 || timeout > 0xFFFF) printf("Warning: page timeout out of range!\n"); if (hci_send_req(s, &rq, 2000) < 0) { fprintf(stderr, "Can't set page timeout on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } else { uint16_t timeout; read_page_timeout_rp rp; rq.ogf = OGF_HOST_CTL; rq.ocf = OCF_READ_PAGE_TIMEOUT; rq.rparam = &rp; rq.rlen = READ_PAGE_TIMEOUT_RP_SIZE; if (hci_send_req(s, &rq, 1000) < 0) { fprintf(stderr, "Can't read page timeout on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (rp.status) { printf("Read page timeout on hci%d returned status %d\n", hdev, rp.status); exit(1); } print_dev_hdr(&di); timeout = btohs(rp.timeout); printf("\tPage timeout: %u slots (%.2f ms)\n", timeout, (float)timeout * 0.625); } } static void cmd_afh_mode(int ctl, int hdev, char *opt) { int dd; dd = hci_open_dev(hdev); if (dd < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (opt) { uint8_t mode = atoi(opt); if (hci_write_afh_mode(dd, mode, 2000) < 0) { fprintf(stderr, "Can't set AFH mode on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } else { uint8_t mode; if (hci_read_afh_mode(dd, &mode, 1000) < 0) { fprintf(stderr, "Can't read AFH mode on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } print_dev_hdr(&di); printf("\tAFH mode: %s\n", mode == 1 ? "Enabled" : "Disabled"); } } static void cmd_ssp_mode(int ctl, int hdev, char *opt) { int dd; dd = hci_open_dev(hdev); if (dd < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } if (opt) { uint8_t mode = atoi(opt); if (hci_write_simple_pairing_mode(dd, mode, 2000) < 0) { fprintf(stderr, "Can't set Simple Pairing mode on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } } else { uint8_t mode; if (hci_read_simple_pairing_mode(dd, &mode, 1000) < 0) { fprintf(stderr, "Can't read Simple Pairing mode on hci%d: %s (%d)\n", hdev, strerror(errno), errno); exit(1); } print_dev_hdr(&di); printf("\tSimple Pairing mode: %s\n", mode == 1 ? "Enabled" : "Disabled"); } } static void print_rev_ericsson(int dd) { struct hci_request rq; unsigned char buf[102]; memset(&rq, 0, sizeof(rq)); rq.ogf = OGF_VENDOR_CMD; rq.ocf = 0x000f; rq.cparam = NULL; rq.clen = 0; rq.rparam = &buf; rq.rlen = sizeof(buf); if (hci_send_req(dd, &rq, 1000) < 0) { printf("\nCan't read revision info: %s (%d)\n", strerror(errno), errno); return; } printf("\t%s\n", buf + 1); } static void print_rev_csr(int dd, uint16_t rev) { uint16_t buildid, chipver, chiprev, maxkeylen, mapsco; if (csr_read_varid_uint16(dd, 0, CSR_VARID_BUILDID, &buildid) < 0) { printf("\t%s\n", csr_buildidtostr(rev)); return; } printf("\t%s\n", csr_buildidtostr(buildid)); if (!csr_read_varid_uint16(dd, 1, CSR_VARID_CHIPVER, &chipver)) { if (csr_read_varid_uint16(dd, 2, CSR_VARID_CHIPREV, &chiprev) < 0) chiprev = 0; printf("\tChip version: %s\n", csr_chipvertostr(chipver, chiprev)); } if (!csr_read_varid_uint16(dd, 3, CSR_VARID_MAX_CRYPT_KEY_LENGTH, &maxkeylen)) printf("\tMax key size: %d bit\n", maxkeylen * 8); if (!csr_read_pskey_uint16(dd, 4, CSR_PSKEY_HOSTIO_MAP_SCO_PCM, 0x0000, &mapsco)) printf("\tSCO mapping: %s\n", mapsco ? "PCM" : "HCI"); } static void print_rev_digianswer(int dd) { struct hci_request rq; unsigned char req[] = { 0x07 }; unsigned char buf[102]; memset(&rq, 0, sizeof(rq)); rq.ogf = OGF_VENDOR_CMD; rq.ocf = 0x000e; rq.cparam = req; rq.clen = sizeof(req); rq.rparam = &buf; rq.rlen = sizeof(buf); if (hci_send_req(dd, &rq, 1000) < 0) { printf("\nCan't read revision info: %s (%d)\n", strerror(errno), errno); return; } printf("\t%s\n", buf + 1); } static void print_rev_broadcom(uint16_t hci_rev, uint16_t lmp_subver) { printf("\tFirmware %d.%d / %d\n", hci_rev & 0xff, lmp_subver >> 8, lmp_subver & 0xff); } static void print_rev_avm(uint16_t hci_rev, uint16_t lmp_subver) { if (lmp_subver == 0x01) printf("\tFirmware 03.%d.%d\n", hci_rev >> 8, hci_rev & 0xff); else printf("\tUnknown type\n"); } static void cmd_revision(int ctl, int hdev, char *opt) { struct hci_version ver; int dd; dd = hci_open_dev(hdev); if (dd < 0) { fprintf(stderr, "Can't open device hci%d: %s (%d)\n", hdev, strerror(errno), errno); return; } if (hci_read_local_version(dd, &ver, 1000) < 0) { fprintf(stderr, "Can't read version info for hci%d: %s (%d)\n", hdev, strerror(errno), errno); return; } print_dev_hdr(&di); switch (ver.manufacturer) { case 0: case 37: case 48: print_rev_ericsson(dd); break; case 10: print_rev_csr(dd, ver.hci_rev); break; case 12: print_rev_digianswer(dd); break; case 15: print_rev_broadcom(ver.hci_rev, ver.lmp_subver); break; case 31: print_rev_avm(ver.hci_rev, ver.lmp_subver); break; default: printf("\tUnsupported manufacturer\n"); break; } return; } static void print_dev_hdr(struct hci_dev_info *di) { static int hdr = -1; char addr[18]; if (hdr == di->dev_id) return; hdr = di->dev_id; ba2str(&di->bdaddr, addr); printf("%s:\tType: %s\n", di->name, hci_dtypetostr(di->type) ); printf("\tBD Address: %s ACL MTU: %d:%d SCO MTU: %d:%d\n", addr, di->acl_mtu, di->acl_pkts, di->sco_mtu, di->sco_pkts); } static void print_dev_info(int ctl, struct hci_dev_info *di) { struct hci_dev_stats *st = &di->stat; char *str; print_dev_hdr(di); str = hci_dflagstostr(di->flags); printf("\t%s\n", str); bt_free(str); printf("\tRX bytes:%d acl:%d sco:%d events:%d errors:%d\n", st->byte_rx, st->acl_rx, st->sco_rx, st->evt_rx, st->err_rx); printf("\tTX bytes:%d acl:%d sco:%d commands:%d errors:%d\n", st->byte_tx, st->acl_tx, st->sco_tx, st->cmd_tx, st->err_tx); if (all && !hci_test_bit(HCI_RAW, &di->flags) && bacmp(&di->bdaddr, BDADDR_ANY)) { print_dev_features(di, 0); print_pkt_type(di); print_link_policy(di); print_link_mode(di); if (hci_test_bit(HCI_UP, &di->flags)) { cmd_name(ctl, di->dev_id, NULL); cmd_class(ctl, di->dev_id, NULL); cmd_version(ctl, di->dev_id, NULL); } } printf("\n"); } static struct { char *cmd; void (*func)(int ctl, int hdev, char *opt); char *opt; char *doc; } command[] = { { "up", cmd_up, 0, "Open and initialize HCI device" }, { "down", cmd_down, 0, "Close HCI device" }, { "reset", cmd_reset, 0, "Reset HCI device" }, { "rstat", cmd_rstat, 0, "Reset statistic counters" }, { "auth", cmd_auth, 0, "Enable Authentication" }, { "noauth", cmd_auth, 0, "Disable Authentication" }, { "encrypt", cmd_encrypt, 0, "Enable Encryption" }, { "noencrypt", cmd_encrypt, 0, "Disable Encryption" }, { "piscan", cmd_scan, 0, "Enable Page and Inquiry scan" }, { "noscan", cmd_scan, 0, "Disable scan" }, { "iscan", cmd_scan, 0, "Enable Inquiry scan" }, { "pscan", cmd_scan, 0, "Enable Page scan" }, { "ptype", cmd_ptype, "[type]", "Get/Set default packet type" }, { "lm", cmd_lm, "[mode]", "Get/Set default link mode" }, { "lp", cmd_lp, "[policy]", "Get/Set default link policy" }, { "name", cmd_name, "[name]", "Get/Set local name" }, { "class", cmd_class, "[class]", "Get/Set class of device" }, { "voice", cmd_voice, "[voice]", "Get/Set voice setting" }, { "iac", cmd_iac, "[iac]", "Get/Set inquiry access code" }, { "inqtpl", cmd_inq_tpl, "[level]", "Get/Set inquiry transmit power level" }, { "inqmode", cmd_inq_mode, "[mode]", "Get/Set inquiry mode" }, { "inqdata", cmd_inq_data, "[data]", "Get/Set inquiry data" }, { "inqtype", cmd_inq_type, "[type]", "Get/Set inquiry scan type" }, { "inqparms", cmd_inq_parms, "[win:int]", "Get/Set inquiry scan window and interval" }, { "pageparms", cmd_page_parms, "[win:int]", "Get/Set page scan window and interval" }, { "pageto", cmd_page_to, "[to]", "Get/Set page timeout" }, { "afhmode", cmd_afh_mode, "[mode]", "Get/Set AFH mode" }, { "sspmode", cmd_ssp_mode, "[mode]", "Get/Set Simple Pairing Mode" }, { "aclmtu", cmd_aclmtu, "", "Set ACL MTU and number of packets" }, { "scomtu", cmd_scomtu, "", "Set SCO MTU and number of packets" }, { "putkey", cmd_putkey, "", "Store link key on the device" }, { "delkey", cmd_delkey, "", "Delete link key from the device" }, { "oobdata", cmd_oob_data, 0, "Display local OOB data" }, { "commands", cmd_commands, 0, "Display supported commands" }, { "features", cmd_features, 0, "Display device features" }, { "version", cmd_version, 0, "Display version information" }, { "revision", cmd_revision, 0, "Display revision information" }, { NULL, NULL, 0 } }; static void usage(void) { int i; printf("hciconfig - HCI device configuration utility\n"); printf("Usage:\n" "\thciconfig\n" "\thciconfig [-a] hciX [command]\n"); printf("Commands:\n"); for (i=0; command[i].cmd; i++) printf("\t%-10s %-8s\t%s\n", command[i].cmd, command[i].opt ? command[i].opt : " ", command[i].doc); } static struct option main_options[] = { { "help", 0, 0, 'h' }, { "all", 0, 0, 'a' }, { 0, 0, 0, 0 } }; int main(int argc, char *argv[]) { int opt, ctl, i, cmd=0; while ((opt=getopt_long(argc, argv, "ah", main_options, NULL)) != -1) { switch(opt) { case 'a': all = 1; break; case 'h': default: usage(); exit(0); } } argc -= optind; argv += optind; optind = 0; /* Open HCI socket */ if ((ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)) < 0) { perror("Can't open HCI socket."); exit(1); } if (argc < 1) { print_dev_list(ctl, 0); exit(0); } di.dev_id = atoi(argv[0] + 3); argc--; argv++; if (ioctl(ctl, HCIGETDEVINFO, (void *) &di)) { perror("Can't get device info"); exit(1); } if (hci_test_bit(HCI_RAW, &di.flags) && !bacmp(&di.bdaddr, BDADDR_ANY)) { int dd = hci_open_dev(di.dev_id); hci_read_bd_addr(dd, &di.bdaddr, 1000); hci_close_dev(dd); } while (argc > 0) { for (i = 0; command[i].cmd; i++) { if (strncmp(command[i].cmd, *argv, 5)) continue; if (command[i].opt) { argc--; argv++; } command[i].func(ctl, di.dev_id, *argv); cmd = 1; break; } argc--; argv++; } if (!cmd) print_dev_info(ctl, &di); close(ctl); return 0; }