| 1 | |
| 2 | |
| 3 | * Copyright (C) 2023 Google Corporation
|
| 4 |
|
| 5 |
|
| 6 | #include <linux/devcoredump.h>
|
| 7 |
|
| 8 | #include <linux/unaligned.h>
|
| 9 | #include <net/bluetooth/bluetooth.h>
|
| 10 | #include <net/bluetooth/hci_core.h>
|
| 11 |
|
| 12 | enum hci_devcoredump_pkt_type {
|
| 13 | HCI_DEVCOREDUMP_PKT_INIT,
|
| 14 | HCI_DEVCOREDUMP_PKT_SKB,
|
| 15 | HCI_DEVCOREDUMP_PKT_PATTERN,
|
| 16 | HCI_DEVCOREDUMP_PKT_COMPLETE,
|
| 17 | HCI_DEVCOREDUMP_PKT_ABORT,
|
| 18 | };
|
| 19 |
|
| 20 | struct hci_devcoredump_skb_cb {
|
| 21 | u16 pkt_type;
|
| 22 | };
|
| 23 |
|
| 24 | struct hci_devcoredump_skb_pattern {
|
| 25 | u8 pattern;
|
| 26 | u32 len;
|
| 27 | } __packed;
|
| 28 |
|
| 29 | #define hci_dmp_cb(skb) ((struct hci_devcoredump_skb_cb *)((skb)->cb))
|
| 30 |
|
| 31 | #define DBG_UNEXPECTED_STATE() \
|
| 32 | bt_dev_dbg(hdev, \
|
| 33 | "Unexpected packet (%d) for state %s.", \
|
| 34 | hci_dmp_cb(skb)->pkt_type, \
|
| 35 | hci_devcd_state_name(hdev->dump.state))
|
| 36 |
|
| 37 | static int hci_devcd_update_hdr_state(char *buf, size_t size, int state)
|
| 38 | {
|
| 39 | int len = 0;
|
| 40 |
|
| 41 | if (!buf)
|
| 42 | return 0;
|
| 43 |
|
| 44 | len = scnprintf(buf, size, "Bluetooth devcoredump\nState: %d\n", state);
|
| 45 |
|
| 46 | return len + 1;
|
| 47 | }
|
| 48 |
|
| 49 |
|
| 50 | static int hci_devcd_update_state(struct hci_dev *hdev, int state)
|
| 51 | {
|
| 52 | bt_dev_dbg(hdev, "Updating devcoredump state from %s to %s.",
|
| 53 | hci_devcd_state_name(hdev->dump.state),
|
| 54 | hci_devcd_state_name(state));
|
| 55 |
|
| 56 | hdev->dump.state = state;
|
| 57 |
|
| 58 | return hci_devcd_update_hdr_state(hdev->dump.head,
|
| 59 | hdev->dump.alloc_size, state);
|
| 60 | }
|
| 61 |
|
| 62 | static int hci_devcd_mkheader(struct hci_dev *hdev, struct sk_buff *skb)
|
| 63 | {
|
| 64 | char hdr[80];
|
| 65 | int hdr_len;
|
| 66 |
|
| 67 | hdr_len = hci_devcd_update_hdr_state(hdr, sizeof(hdr),
|
| 68 | HCI_DEVCOREDUMP_IDLE);
|
| 69 | skb_put_data(skb, hdr, hdr_len);
|
| 70 |
|
| 71 | if (hdev->dump.dmp_hdr)
|
| 72 | hdev->dump.dmp_hdr(hdev, skb);
|
| 73 |
|
| 74 | skb_put_data(skb, HCI_DEVCD_HDR_END_MARKER, strlen(HCI_DEVCD_HDR_END_MARKER));
|
| 75 |
|
| 76 | return skb->len;
|
| 77 | }
|
| 78 |
|
| 79 |
|
| 80 | static void hci_devcd_notify(struct hci_dev *hdev, int state)
|
| 81 | {
|
| 82 | if (hdev->dump.notify_change)
|
| 83 | hdev->dump.notify_change(hdev, state);
|
| 84 | }
|
| 85 |
|
| 86 |
|
| 87 | void hci_devcd_reset(struct hci_dev *hdev)
|
| 88 | {
|
| 89 | hdev->dump.head = NULL;
|
| 90 | hdev->dump.tail = NULL;
|
| 91 | hdev->dump.alloc_size = 0;
|
| 92 |
|
| 93 | hci_devcd_update_state(hdev, HCI_DEVCOREDUMP_IDLE);
|
| 94 |
|
| 95 | cancel_delayed_work(&hdev->dump.dump_timeout);
|
| 96 | skb_queue_purge(&hdev->dump.dump_q);
|
| 97 | }
|
| 98 |
|
| 99 |
|
| 100 | static void hci_devcd_free(struct hci_dev *hdev)
|
| 101 | {
|
| 102 | vfree(hdev->dump.head);
|
| 103 |
|
| 104 | hci_devcd_reset(hdev);
|
| 105 | }
|
| 106 |
|
| 107 | void hci_devcd_shutdown(struct hci_dev *hdev)
|
| 108 | {
|
| 109 | unsigned long flags;
|
| 110 |
|
| 111 | spin_lock_irqsave(&hdev->dump.dump_q.lock, flags);
|
| 112 | hdev->dump.supported = false;
|
| 113 | spin_unlock_irqrestore(&hdev->dump.dump_q.lock, flags);
|
| 114 |
|
| 115 | disable_work_sync(&hdev->dump.dump_rx);
|
| 116 | disable_delayed_work_sync(&hdev->dump.dump_timeout);
|
| 117 |
|
| 118 | hci_dev_lock(hdev);
|
| 119 | hci_devcd_free(hdev);
|
| 120 | hci_dev_unlock(hdev);
|
| 121 | }
|
| 122 |
|
| 123 |
|
| 124 | static int hci_devcd_alloc(struct hci_dev *hdev, u32 size)
|
| 125 | {
|
| 126 | hdev->dump.head = vmalloc(size);
|
| 127 | if (!hdev->dump.head)
|
| 128 | return -ENOMEM;
|
| 129 |
|
| 130 | hdev->dump.alloc_size = size;
|
| 131 | hdev->dump.tail = hdev->dump.head;
|
| 132 | hdev->dump.end = hdev->dump.head + size;
|
| 133 |
|
| 134 | hci_devcd_update_state(hdev, HCI_DEVCOREDUMP_IDLE);
|
| 135 |
|
| 136 | return 0;
|
| 137 | }
|
| 138 |
|
| 139 |
|
| 140 | static bool hci_devcd_copy(struct hci_dev *hdev, char *buf, u32 size)
|
| 141 | {
|
| 142 | if (hdev->dump.tail + size > hdev->dump.end)
|
| 143 | return false;
|
| 144 |
|
| 145 | memcpy(hdev->dump.tail, buf, size);
|
| 146 | hdev->dump.tail += size;
|
| 147 |
|
| 148 | return true;
|
| 149 | }
|
| 150 |
|
| 151 |
|
| 152 | static bool hci_devcd_memset(struct hci_dev *hdev, u8 pattern, u32 len)
|
| 153 | {
|
| 154 | if (hdev->dump.tail + len > hdev->dump.end)
|
| 155 | return false;
|
| 156 |
|
| 157 | memset(hdev->dump.tail, pattern, len);
|
| 158 | hdev->dump.tail += len;
|
| 159 |
|
| 160 | return true;
|
| 161 | }
|
| 162 |
|
| 163 |
|
| 164 | static int hci_devcd_prepare(struct hci_dev *hdev, u32 dump_size)
|
| 165 | {
|
| 166 | struct sk_buff *skb;
|
| 167 | int dump_hdr_size;
|
| 168 | int err = 0;
|
| 169 |
|
| 170 | skb = alloc_skb(HCI_DEVCD_HDR_SIZE_MAX, GFP_ATOMIC);
|
| 171 | if (!skb)
|
| 172 | return -ENOMEM;
|
| 173 |
|
| 174 | dump_hdr_size = hci_devcd_mkheader(hdev, skb);
|
| 175 |
|
| 176 | if (hci_devcd_alloc(hdev, dump_hdr_size + dump_size)) {
|
| 177 | err = -ENOMEM;
|
| 178 | goto hdr_free;
|
| 179 | }
|
| 180 |
|
| 181 |
|
| 182 | if (!hci_devcd_copy(hdev, skb->data, skb->len)) {
|
| 183 | bt_dev_err(hdev, "Failed to insert header");
|
| 184 | hci_devcd_free(hdev);
|
| 185 |
|
| 186 | err = -ENOMEM;
|
| 187 | goto hdr_free;
|
| 188 | }
|
| 189 |
|
| 190 | hdr_free:
|
| 191 | kfree_skb(skb);
|
| 192 |
|
| 193 | return err;
|
| 194 | }
|
| 195 |
|
| 196 | static void hci_devcd_handle_pkt_init(struct hci_dev *hdev, struct sk_buff *skb)
|
| 197 | {
|
| 198 | u32 dump_size;
|
| 199 |
|
| 200 | if (hdev->dump.state != HCI_DEVCOREDUMP_IDLE) {
|
| 201 | DBG_UNEXPECTED_STATE();
|
| 202 | return;
|
| 203 | }
|
| 204 |
|
| 205 | if (skb->len != sizeof(dump_size)) {
|
| 206 | bt_dev_dbg(hdev, "Invalid dump init pkt");
|
| 207 | return;
|
| 208 | }
|
| 209 |
|
| 210 | dump_size = get_unaligned_le32(skb_pull_data(skb, 4));
|
| 211 | if (!dump_size) {
|
| 212 | bt_dev_err(hdev, "Zero size dump init pkt");
|
| 213 | return;
|
| 214 | }
|
| 215 |
|
| 216 | if (hci_devcd_prepare(hdev, dump_size)) {
|
| 217 | bt_dev_err(hdev, "Failed to prepare for dump");
|
| 218 | return;
|
| 219 | }
|
| 220 |
|
| 221 | hci_devcd_update_state(hdev, HCI_DEVCOREDUMP_ACTIVE);
|
| 222 | queue_delayed_work(hdev->workqueue, &hdev->dump.dump_timeout,
|
| 223 | hdev->dump.timeout);
|
| 224 | }
|
| 225 |
|
| 226 | static void hci_devcd_handle_pkt_skb(struct hci_dev *hdev, struct sk_buff *skb)
|
| 227 | {
|
| 228 | if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) {
|
| 229 | DBG_UNEXPECTED_STATE();
|
| 230 | return;
|
| 231 | }
|
| 232 |
|
| 233 | if (!hci_devcd_copy(hdev, skb->data, skb->len))
|
| 234 | bt_dev_dbg(hdev, "Failed to insert skb");
|
| 235 | }
|
| 236 |
|
| 237 | static void hci_devcd_handle_pkt_pattern(struct hci_dev *hdev,
|
| 238 | struct sk_buff *skb)
|
| 239 | {
|
| 240 | struct hci_devcoredump_skb_pattern *pattern;
|
| 241 |
|
| 242 | if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) {
|
| 243 | DBG_UNEXPECTED_STATE();
|
| 244 | return;
|
| 245 | }
|
| 246 |
|
| 247 | if (skb->len != sizeof(*pattern)) {
|
| 248 | bt_dev_dbg(hdev, "Invalid pattern skb");
|
| 249 | return;
|
| 250 | }
|
| 251 |
|
| 252 | pattern = skb_pull_data(skb, sizeof(*pattern));
|
| 253 |
|
| 254 | if (!hci_devcd_memset(hdev, pattern->pattern, pattern->len))
|
| 255 | bt_dev_dbg(hdev, "Failed to set pattern");
|
| 256 | }
|
| 257 |
|
| 258 | static void hci_devcd_dump(struct hci_dev *hdev)
|
| 259 | {
|
| 260 | struct sk_buff *skb;
|
| 261 | u32 size;
|
| 262 |
|
| 263 | bt_dev_dbg(hdev, "state %s", hci_devcd_state_name(hdev->dump.state));
|
| 264 |
|
| 265 | size = hdev->dump.tail - hdev->dump.head;
|
| 266 |
|
| 267 |
|
| 268 | skb = bt_skb_alloc(size, GFP_ATOMIC);
|
| 269 | if (skb) {
|
| 270 | skb_put_data(skb, hdev->dump.head, size);
|
| 271 | hci_recv_diag(hdev, skb);
|
| 272 | }
|
| 273 |
|
| 274 |
|
| 275 | dev_coredumpv(&hdev->dev, hdev->dump.head, size, GFP_KERNEL);
|
| 276 | }
|
| 277 |
|
| 278 | static void hci_devcd_handle_pkt_complete(struct hci_dev *hdev,
|
| 279 | struct sk_buff *skb)
|
| 280 | {
|
| 281 | u32 dump_size;
|
| 282 |
|
| 283 | if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) {
|
| 284 | DBG_UNEXPECTED_STATE();
|
| 285 | return;
|
| 286 | }
|
| 287 |
|
| 288 | hci_devcd_update_state(hdev, HCI_DEVCOREDUMP_DONE);
|
| 289 | dump_size = hdev->dump.tail - hdev->dump.head;
|
| 290 |
|
| 291 | bt_dev_dbg(hdev, "complete with size %u (expect %zu)", dump_size,
|
| 292 | hdev->dump.alloc_size);
|
| 293 |
|
| 294 | hci_devcd_dump(hdev);
|
| 295 | }
|
| 296 |
|
| 297 | static void hci_devcd_handle_pkt_abort(struct hci_dev *hdev,
|
| 298 | struct sk_buff *skb)
|
| 299 | {
|
| 300 | u32 dump_size;
|
| 301 |
|
| 302 | if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) {
|
| 303 | DBG_UNEXPECTED_STATE();
|
| 304 | return;
|
| 305 | }
|
| 306 |
|
| 307 | hci_devcd_update_state(hdev, HCI_DEVCOREDUMP_ABORT);
|
| 308 | dump_size = hdev->dump.tail - hdev->dump.head;
|
| 309 |
|
| 310 | bt_dev_dbg(hdev, "aborted with size %u (expect %zu)", dump_size,
|
| 311 | hdev->dump.alloc_size);
|
| 312 |
|
| 313 | hci_devcd_dump(hdev);
|
| 314 | }
|
| 315 |
|
| 316 | |
| 317 | *
|
| 318 | * Devcoredump states:
|
| 319 | *
|
| 320 | * HCI_DEVCOREDUMP_IDLE: The default state.
|
| 321 | *
|
| 322 | * HCI_DEVCOREDUMP_ACTIVE: A devcoredump will be in this state once it has
|
| 323 | * been initialized using hci_devcd_init(). Once active, the driver
|
| 324 | * can append data using hci_devcd_append() or insert a pattern
|
| 325 | * using hci_devcd_append_pattern().
|
| 326 | *
|
| 327 | * HCI_DEVCOREDUMP_DONE: Once the dump collection is complete, the drive
|
| 328 | * can signal the completion using hci_devcd_complete(). A
|
| 329 | * devcoredump is generated indicating the completion event and
|
| 330 | * then the state machine is reset to the default state.
|
| 331 | *
|
| 332 | * HCI_DEVCOREDUMP_ABORT: The driver can cancel ongoing dump collection in
|
| 333 | * case of any error using hci_devcd_abort(). A devcoredump is
|
| 334 | * still generated with the available data indicating the abort
|
| 335 | * event and then the state machine is reset to the default state.
|
| 336 | *
|
| 337 | * HCI_DEVCOREDUMP_TIMEOUT: A timeout timer for HCI_DEVCOREDUMP_TIMEOUT sec
|
| 338 | * is started during devcoredump initialization. Once the timeout
|
| 339 | * occurs, the driver is notified, a devcoredump is generated with
|
| 340 | * the available data indicating the timeout event and then the
|
| 341 | * state machine is reset to the default state.
|
| 342 | *
|
| 343 | * The driver must register using hci_devcd_register() before using the hci
|
| 344 | * devcoredump APIs.
|
| 345 |
|
| 346 | void hci_devcd_rx(struct work_struct *work)
|
| 347 | {
|
| 348 | struct hci_dev *hdev = container_of(work, struct hci_dev, dump.dump_rx);
|
| 349 | struct sk_buff *skb;
|
| 350 | int start_state;
|
| 351 |
|
| 352 | while ((skb = skb_dequeue(&hdev->dump.dump_q))) {
|
| 353 | |
| 354 | * hci_devcd_timeout() will report the available dump data.
|
| 355 |
|
| 356 | if (hdev->dump.state == HCI_DEVCOREDUMP_TIMEOUT) {
|
| 357 | kfree_skb(skb);
|
| 358 | return;
|
| 359 | }
|
| 360 |
|
| 361 | hci_dev_lock(hdev);
|
| 362 | start_state = hdev->dump.state;
|
| 363 |
|
| 364 | switch (hci_dmp_cb(skb)->pkt_type) {
|
| 365 | case HCI_DEVCOREDUMP_PKT_INIT:
|
| 366 | hci_devcd_handle_pkt_init(hdev, skb);
|
| 367 | break;
|
| 368 |
|
| 369 | case HCI_DEVCOREDUMP_PKT_SKB:
|
| 370 | hci_devcd_handle_pkt_skb(hdev, skb);
|
| 371 | break;
|
| 372 |
|
| 373 | case HCI_DEVCOREDUMP_PKT_PATTERN:
|
| 374 | hci_devcd_handle_pkt_pattern(hdev, skb);
|
| 375 | break;
|
| 376 |
|
| 377 | case HCI_DEVCOREDUMP_PKT_COMPLETE:
|
| 378 | hci_devcd_handle_pkt_complete(hdev, skb);
|
| 379 | break;
|
| 380 |
|
| 381 | case HCI_DEVCOREDUMP_PKT_ABORT:
|
| 382 | hci_devcd_handle_pkt_abort(hdev, skb);
|
| 383 | break;
|
| 384 |
|
| 385 | default:
|
| 386 | bt_dev_dbg(hdev, "Unknown packet (%d) for state %s.",
|
| 387 | hci_dmp_cb(skb)->pkt_type,
|
| 388 | hci_devcd_state_name(hdev->dump.state));
|
| 389 | break;
|
| 390 | }
|
| 391 |
|
| 392 | hci_dev_unlock(hdev);
|
| 393 | kfree_skb(skb);
|
| 394 |
|
| 395 | |
| 396 | * the state machine
|
| 397 |
|
| 398 | if (start_state != hdev->dump.state)
|
| 399 | hci_devcd_notify(hdev, hdev->dump.state);
|
| 400 |
|
| 401 |
|
| 402 | hci_dev_lock(hdev);
|
| 403 | if (hdev->dump.state == HCI_DEVCOREDUMP_DONE ||
|
| 404 | hdev->dump.state == HCI_DEVCOREDUMP_ABORT)
|
| 405 | hci_devcd_reset(hdev);
|
| 406 | hci_dev_unlock(hdev);
|
| 407 | }
|
| 408 | }
|
| 409 |
|
| 410 | void hci_devcd_timeout(struct work_struct *work)
|
| 411 | {
|
| 412 | struct hci_dev *hdev = container_of(work, struct hci_dev,
|
| 413 | dump.dump_timeout.work);
|
| 414 | u32 dump_size;
|
| 415 |
|
| 416 | hci_devcd_notify(hdev, HCI_DEVCOREDUMP_TIMEOUT);
|
| 417 |
|
| 418 | hci_dev_lock(hdev);
|
| 419 |
|
| 420 | cancel_work(&hdev->dump.dump_rx);
|
| 421 |
|
| 422 | hci_devcd_update_state(hdev, HCI_DEVCOREDUMP_TIMEOUT);
|
| 423 |
|
| 424 | dump_size = hdev->dump.tail - hdev->dump.head;
|
| 425 | bt_dev_dbg(hdev, "timeout with size %u (expect %zu)", dump_size,
|
| 426 | hdev->dump.alloc_size);
|
| 427 |
|
| 428 | hci_devcd_dump(hdev);
|
| 429 |
|
| 430 | hci_devcd_reset(hdev);
|
| 431 |
|
| 432 | hci_dev_unlock(hdev);
|
| 433 | }
|
| 434 |
|
| 435 | int hci_devcd_register(struct hci_dev *hdev, coredump_t coredump,
|
| 436 | dmp_hdr_t dmp_hdr, notify_change_t notify_change)
|
| 437 | {
|
| 438 | |
| 439 | * bluetooth devcoredump. The coredump() should trigger a coredump
|
| 440 | * event on the controller when the device's coredump sysfs entry is
|
| 441 | * written to. The dmp_hdr() should create a dump header to identify
|
| 442 | * the controller/fw/driver info.
|
| 443 |
|
| 444 | if (!coredump || !dmp_hdr)
|
| 445 | return -EINVAL;
|
| 446 |
|
| 447 | hci_dev_lock(hdev);
|
| 448 | hdev->dump.coredump = coredump;
|
| 449 | hdev->dump.dmp_hdr = dmp_hdr;
|
| 450 | hdev->dump.notify_change = notify_change;
|
| 451 | hdev->dump.supported = true;
|
| 452 | hdev->dump.timeout = DEVCOREDUMP_TIMEOUT;
|
| 453 | hci_dev_unlock(hdev);
|
| 454 |
|
| 455 | return 0;
|
| 456 | }
|
| 457 | EXPORT_SYMBOL(hci_devcd_register);
|
| 458 |
|
| 459 | static inline bool hci_devcd_enabled(struct hci_dev *hdev)
|
| 460 | {
|
| 461 | return READ_ONCE(hdev->dump.supported);
|
| 462 | }
|
| 463 |
|
| 464 | static int hci_devcd_queue(struct hci_dev *hdev, struct sk_buff *skb)
|
| 465 | {
|
| 466 | unsigned long flags;
|
| 467 | int err = 0;
|
| 468 |
|
| 469 | spin_lock_irqsave(&hdev->dump.dump_q.lock, flags);
|
| 470 | if (!hdev->dump.supported)
|
| 471 | err = -EOPNOTSUPP;
|
| 472 | else
|
| 473 | __skb_queue_tail(&hdev->dump.dump_q, skb);
|
| 474 | spin_unlock_irqrestore(&hdev->dump.dump_q.lock, flags);
|
| 475 |
|
| 476 | if (err) {
|
| 477 | kfree_skb(skb);
|
| 478 | return err;
|
| 479 | }
|
| 480 |
|
| 481 | queue_work(hdev->workqueue, &hdev->dump.dump_rx);
|
| 482 |
|
| 483 | return 0;
|
| 484 | }
|
| 485 |
|
| 486 | int hci_devcd_init(struct hci_dev *hdev, u32 dump_size)
|
| 487 | {
|
| 488 | struct sk_buff *skb;
|
| 489 |
|
| 490 | if (!hci_devcd_enabled(hdev))
|
| 491 | return -EOPNOTSUPP;
|
| 492 |
|
| 493 | skb = alloc_skb(sizeof(dump_size), GFP_ATOMIC);
|
| 494 | if (!skb)
|
| 495 | return -ENOMEM;
|
| 496 |
|
| 497 | hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_INIT;
|
| 498 | put_unaligned_le32(dump_size, skb_put(skb, 4));
|
| 499 |
|
| 500 | return hci_devcd_queue(hdev, skb);
|
| 501 | }
|
| 502 | EXPORT_SYMBOL(hci_devcd_init);
|
| 503 |
|
| 504 | int hci_devcd_append(struct hci_dev *hdev, struct sk_buff *skb)
|
| 505 | {
|
| 506 | if (!skb)
|
| 507 | return -ENOMEM;
|
| 508 |
|
| 509 | if (!hci_devcd_enabled(hdev)) {
|
| 510 | kfree_skb(skb);
|
| 511 | return -EOPNOTSUPP;
|
| 512 | }
|
| 513 |
|
| 514 | hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_SKB;
|
| 515 |
|
| 516 | return hci_devcd_queue(hdev, skb);
|
| 517 | }
|
| 518 | EXPORT_SYMBOL(hci_devcd_append);
|
| 519 |
|
| 520 | int hci_devcd_append_pattern(struct hci_dev *hdev, u8 pattern, u32 len)
|
| 521 | {
|
| 522 | struct hci_devcoredump_skb_pattern p;
|
| 523 | struct sk_buff *skb;
|
| 524 |
|
| 525 | if (!hci_devcd_enabled(hdev))
|
| 526 | return -EOPNOTSUPP;
|
| 527 |
|
| 528 | skb = alloc_skb(sizeof(p), GFP_ATOMIC);
|
| 529 | if (!skb)
|
| 530 | return -ENOMEM;
|
| 531 |
|
| 532 | p.pattern = pattern;
|
| 533 | p.len = len;
|
| 534 |
|
| 535 | hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_PATTERN;
|
| 536 | skb_put_data(skb, &p, sizeof(p));
|
| 537 |
|
| 538 | return hci_devcd_queue(hdev, skb);
|
| 539 | }
|
| 540 | EXPORT_SYMBOL(hci_devcd_append_pattern);
|
| 541 |
|
| 542 | int hci_devcd_complete(struct hci_dev *hdev)
|
| 543 | {
|
| 544 | struct sk_buff *skb;
|
| 545 |
|
| 546 | if (!hci_devcd_enabled(hdev))
|
| 547 | return -EOPNOTSUPP;
|
| 548 |
|
| 549 | skb = alloc_skb(0, GFP_ATOMIC);
|
| 550 | if (!skb)
|
| 551 | return -ENOMEM;
|
| 552 |
|
| 553 | hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_COMPLETE;
|
| 554 |
|
| 555 | return hci_devcd_queue(hdev, skb);
|
| 556 | }
|
| 557 | EXPORT_SYMBOL(hci_devcd_complete);
|
| 558 |
|
| 559 | int hci_devcd_abort(struct hci_dev *hdev)
|
| 560 | {
|
| 561 | struct sk_buff *skb;
|
| 562 |
|
| 563 | if (!hci_devcd_enabled(hdev))
|
| 564 | return -EOPNOTSUPP;
|
| 565 |
|
| 566 | skb = alloc_skb(0, GFP_ATOMIC);
|
| 567 | if (!skb)
|
| 568 | return -ENOMEM;
|
| 569 |
|
| 570 | hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_ABORT;
|
| 571 |
|
| 572 | return hci_devcd_queue(hdev, skb);
|
| 573 | }
|
| 574 | EXPORT_SYMBOL(hci_devcd_abort);
|
| 575 |
|
| 576 | const char *hci_devcd_state_name(enum devcoredump_state state)
|
| 577 | {
|
| 578 | const char *state_name = "Unknown";
|
| 579 |
|
| 580 | switch (state) {
|
| 581 | case HCI_DEVCOREDUMP_IDLE:
|
| 582 | state_name = "IDLE";
|
| 583 | break;
|
| 584 | case HCI_DEVCOREDUMP_ACTIVE:
|
| 585 | state_name = "ACTIVE";
|
| 586 | break;
|
| 587 | case HCI_DEVCOREDUMP_DONE:
|
| 588 | state_name = "DONE";
|
| 589 | break;
|
| 590 | case HCI_DEVCOREDUMP_ABORT:
|
| 591 | state_name = "ABORT";
|
| 592 | break;
|
| 593 | case HCI_DEVCOREDUMP_TIMEOUT:
|
| 594 | state_name = "TIMEOUT";
|
| 595 | break;
|
| 596 | default:
|
| 597 | break;
|
| 598 | }
|
| 599 |
|
| 600 | return state_name;
|
| 601 | }
|
| 602 | EXPORT_SYMBOL(hci_devcd_state_name);
|