19695ef87SAbhishek Pandit-Subedi // SPDX-License-Identifier: GPL-2.0-only
29695ef87SAbhishek Pandit-Subedi /*
39695ef87SAbhishek Pandit-Subedi * Copyright (C) 2023 Google Corporation
49695ef87SAbhishek Pandit-Subedi */
59695ef87SAbhishek Pandit-Subedi
69695ef87SAbhishek Pandit-Subedi #include <linux/devcoredump.h>
79695ef87SAbhishek Pandit-Subedi
8*0ab905c3SZijun Hu #include <asm/unaligned.h>
99695ef87SAbhishek Pandit-Subedi #include <net/bluetooth/bluetooth.h>
109695ef87SAbhishek Pandit-Subedi #include <net/bluetooth/hci_core.h>
119695ef87SAbhishek Pandit-Subedi
129695ef87SAbhishek Pandit-Subedi enum hci_devcoredump_pkt_type {
139695ef87SAbhishek Pandit-Subedi HCI_DEVCOREDUMP_PKT_INIT,
149695ef87SAbhishek Pandit-Subedi HCI_DEVCOREDUMP_PKT_SKB,
159695ef87SAbhishek Pandit-Subedi HCI_DEVCOREDUMP_PKT_PATTERN,
169695ef87SAbhishek Pandit-Subedi HCI_DEVCOREDUMP_PKT_COMPLETE,
179695ef87SAbhishek Pandit-Subedi HCI_DEVCOREDUMP_PKT_ABORT,
189695ef87SAbhishek Pandit-Subedi };
199695ef87SAbhishek Pandit-Subedi
209695ef87SAbhishek Pandit-Subedi struct hci_devcoredump_skb_cb {
219695ef87SAbhishek Pandit-Subedi u16 pkt_type;
229695ef87SAbhishek Pandit-Subedi };
239695ef87SAbhishek Pandit-Subedi
249695ef87SAbhishek Pandit-Subedi struct hci_devcoredump_skb_pattern {
259695ef87SAbhishek Pandit-Subedi u8 pattern;
269695ef87SAbhishek Pandit-Subedi u32 len;
279695ef87SAbhishek Pandit-Subedi } __packed;
289695ef87SAbhishek Pandit-Subedi
299695ef87SAbhishek Pandit-Subedi #define hci_dmp_cb(skb) ((struct hci_devcoredump_skb_cb *)((skb)->cb))
309695ef87SAbhishek Pandit-Subedi
319695ef87SAbhishek Pandit-Subedi #define DBG_UNEXPECTED_STATE() \
329695ef87SAbhishek Pandit-Subedi bt_dev_dbg(hdev, \
339695ef87SAbhishek Pandit-Subedi "Unexpected packet (%d) for state (%d). ", \
349695ef87SAbhishek Pandit-Subedi hci_dmp_cb(skb)->pkt_type, hdev->dump.state)
359695ef87SAbhishek Pandit-Subedi
369695ef87SAbhishek Pandit-Subedi #define MAX_DEVCOREDUMP_HDR_SIZE 512 /* bytes */
379695ef87SAbhishek Pandit-Subedi
hci_devcd_update_hdr_state(char * buf,size_t size,int state)389695ef87SAbhishek Pandit-Subedi static int hci_devcd_update_hdr_state(char *buf, size_t size, int state)
399695ef87SAbhishek Pandit-Subedi {
409695ef87SAbhishek Pandit-Subedi int len = 0;
419695ef87SAbhishek Pandit-Subedi
429695ef87SAbhishek Pandit-Subedi if (!buf)
439695ef87SAbhishek Pandit-Subedi return 0;
449695ef87SAbhishek Pandit-Subedi
459695ef87SAbhishek Pandit-Subedi len = scnprintf(buf, size, "Bluetooth devcoredump\nState: %d\n", state);
469695ef87SAbhishek Pandit-Subedi
479695ef87SAbhishek Pandit-Subedi return len + 1; /* scnprintf adds \0 at the end upon state rewrite */
489695ef87SAbhishek Pandit-Subedi }
499695ef87SAbhishek Pandit-Subedi
509695ef87SAbhishek Pandit-Subedi /* Call with hci_dev_lock only. */
hci_devcd_update_state(struct hci_dev * hdev,int state)519695ef87SAbhishek Pandit-Subedi static int hci_devcd_update_state(struct hci_dev *hdev, int state)
529695ef87SAbhishek Pandit-Subedi {
539695ef87SAbhishek Pandit-Subedi bt_dev_dbg(hdev, "Updating devcoredump state from %d to %d.",
549695ef87SAbhishek Pandit-Subedi hdev->dump.state, state);
559695ef87SAbhishek Pandit-Subedi
569695ef87SAbhishek Pandit-Subedi hdev->dump.state = state;
579695ef87SAbhishek Pandit-Subedi
589695ef87SAbhishek Pandit-Subedi return hci_devcd_update_hdr_state(hdev->dump.head,
599695ef87SAbhishek Pandit-Subedi hdev->dump.alloc_size, state);
609695ef87SAbhishek Pandit-Subedi }
619695ef87SAbhishek Pandit-Subedi
hci_devcd_mkheader(struct hci_dev * hdev,struct sk_buff * skb)629695ef87SAbhishek Pandit-Subedi static int hci_devcd_mkheader(struct hci_dev *hdev, struct sk_buff *skb)
639695ef87SAbhishek Pandit-Subedi {
649695ef87SAbhishek Pandit-Subedi char dump_start[] = "--- Start dump ---\n";
659695ef87SAbhishek Pandit-Subedi char hdr[80];
669695ef87SAbhishek Pandit-Subedi int hdr_len;
679695ef87SAbhishek Pandit-Subedi
689695ef87SAbhishek Pandit-Subedi hdr_len = hci_devcd_update_hdr_state(hdr, sizeof(hdr),
699695ef87SAbhishek Pandit-Subedi HCI_DEVCOREDUMP_IDLE);
709695ef87SAbhishek Pandit-Subedi skb_put_data(skb, hdr, hdr_len);
719695ef87SAbhishek Pandit-Subedi
729695ef87SAbhishek Pandit-Subedi if (hdev->dump.dmp_hdr)
739695ef87SAbhishek Pandit-Subedi hdev->dump.dmp_hdr(hdev, skb);
749695ef87SAbhishek Pandit-Subedi
759695ef87SAbhishek Pandit-Subedi skb_put_data(skb, dump_start, strlen(dump_start));
769695ef87SAbhishek Pandit-Subedi
779695ef87SAbhishek Pandit-Subedi return skb->len;
789695ef87SAbhishek Pandit-Subedi }
799695ef87SAbhishek Pandit-Subedi
809695ef87SAbhishek Pandit-Subedi /* Do not call with hci_dev_lock since this calls driver code. */
hci_devcd_notify(struct hci_dev * hdev,int state)819695ef87SAbhishek Pandit-Subedi static void hci_devcd_notify(struct hci_dev *hdev, int state)
829695ef87SAbhishek Pandit-Subedi {
839695ef87SAbhishek Pandit-Subedi if (hdev->dump.notify_change)
849695ef87SAbhishek Pandit-Subedi hdev->dump.notify_change(hdev, state);
859695ef87SAbhishek Pandit-Subedi }
869695ef87SAbhishek Pandit-Subedi
879695ef87SAbhishek Pandit-Subedi /* Call with hci_dev_lock only. */
hci_devcd_reset(struct hci_dev * hdev)889695ef87SAbhishek Pandit-Subedi void hci_devcd_reset(struct hci_dev *hdev)
899695ef87SAbhishek Pandit-Subedi {
909695ef87SAbhishek Pandit-Subedi hdev->dump.head = NULL;
919695ef87SAbhishek Pandit-Subedi hdev->dump.tail = NULL;
929695ef87SAbhishek Pandit-Subedi hdev->dump.alloc_size = 0;
939695ef87SAbhishek Pandit-Subedi
949695ef87SAbhishek Pandit-Subedi hci_devcd_update_state(hdev, HCI_DEVCOREDUMP_IDLE);
959695ef87SAbhishek Pandit-Subedi
969695ef87SAbhishek Pandit-Subedi cancel_delayed_work(&hdev->dump.dump_timeout);
979695ef87SAbhishek Pandit-Subedi skb_queue_purge(&hdev->dump.dump_q);
989695ef87SAbhishek Pandit-Subedi }
999695ef87SAbhishek Pandit-Subedi
1009695ef87SAbhishek Pandit-Subedi /* Call with hci_dev_lock only. */
hci_devcd_free(struct hci_dev * hdev)1019695ef87SAbhishek Pandit-Subedi static void hci_devcd_free(struct hci_dev *hdev)
1029695ef87SAbhishek Pandit-Subedi {
1039695ef87SAbhishek Pandit-Subedi vfree(hdev->dump.head);
1049695ef87SAbhishek Pandit-Subedi
1059695ef87SAbhishek Pandit-Subedi hci_devcd_reset(hdev);
1069695ef87SAbhishek Pandit-Subedi }
1079695ef87SAbhishek Pandit-Subedi
1089695ef87SAbhishek Pandit-Subedi /* Call with hci_dev_lock only. */
hci_devcd_alloc(struct hci_dev * hdev,u32 size)1099695ef87SAbhishek Pandit-Subedi static int hci_devcd_alloc(struct hci_dev *hdev, u32 size)
1109695ef87SAbhishek Pandit-Subedi {
1119695ef87SAbhishek Pandit-Subedi hdev->dump.head = vmalloc(size);
1129695ef87SAbhishek Pandit-Subedi if (!hdev->dump.head)
1139695ef87SAbhishek Pandit-Subedi return -ENOMEM;
1149695ef87SAbhishek Pandit-Subedi
1159695ef87SAbhishek Pandit-Subedi hdev->dump.alloc_size = size;
1169695ef87SAbhishek Pandit-Subedi hdev->dump.tail = hdev->dump.head;
1179695ef87SAbhishek Pandit-Subedi hdev->dump.end = hdev->dump.head + size;
1189695ef87SAbhishek Pandit-Subedi
1199695ef87SAbhishek Pandit-Subedi hci_devcd_update_state(hdev, HCI_DEVCOREDUMP_IDLE);
1209695ef87SAbhishek Pandit-Subedi
1219695ef87SAbhishek Pandit-Subedi return 0;
1229695ef87SAbhishek Pandit-Subedi }
1239695ef87SAbhishek Pandit-Subedi
1249695ef87SAbhishek Pandit-Subedi /* Call with hci_dev_lock only. */
hci_devcd_copy(struct hci_dev * hdev,char * buf,u32 size)1259695ef87SAbhishek Pandit-Subedi static bool hci_devcd_copy(struct hci_dev *hdev, char *buf, u32 size)
1269695ef87SAbhishek Pandit-Subedi {
1279695ef87SAbhishek Pandit-Subedi if (hdev->dump.tail + size > hdev->dump.end)
1289695ef87SAbhishek Pandit-Subedi return false;
1299695ef87SAbhishek Pandit-Subedi
1309695ef87SAbhishek Pandit-Subedi memcpy(hdev->dump.tail, buf, size);
1319695ef87SAbhishek Pandit-Subedi hdev->dump.tail += size;
1329695ef87SAbhishek Pandit-Subedi
1339695ef87SAbhishek Pandit-Subedi return true;
1349695ef87SAbhishek Pandit-Subedi }
1359695ef87SAbhishek Pandit-Subedi
1369695ef87SAbhishek Pandit-Subedi /* Call with hci_dev_lock only. */
hci_devcd_memset(struct hci_dev * hdev,u8 pattern,u32 len)1379695ef87SAbhishek Pandit-Subedi static bool hci_devcd_memset(struct hci_dev *hdev, u8 pattern, u32 len)
1389695ef87SAbhishek Pandit-Subedi {
1399695ef87SAbhishek Pandit-Subedi if (hdev->dump.tail + len > hdev->dump.end)
1409695ef87SAbhishek Pandit-Subedi return false;
1419695ef87SAbhishek Pandit-Subedi
1429695ef87SAbhishek Pandit-Subedi memset(hdev->dump.tail, pattern, len);
1439695ef87SAbhishek Pandit-Subedi hdev->dump.tail += len;
1449695ef87SAbhishek Pandit-Subedi
1459695ef87SAbhishek Pandit-Subedi return true;
1469695ef87SAbhishek Pandit-Subedi }
1479695ef87SAbhishek Pandit-Subedi
1489695ef87SAbhishek Pandit-Subedi /* Call with hci_dev_lock only. */
hci_devcd_prepare(struct hci_dev * hdev,u32 dump_size)1499695ef87SAbhishek Pandit-Subedi static int hci_devcd_prepare(struct hci_dev *hdev, u32 dump_size)
1509695ef87SAbhishek Pandit-Subedi {
1519695ef87SAbhishek Pandit-Subedi struct sk_buff *skb;
1529695ef87SAbhishek Pandit-Subedi int dump_hdr_size;
1539695ef87SAbhishek Pandit-Subedi int err = 0;
1549695ef87SAbhishek Pandit-Subedi
1559695ef87SAbhishek Pandit-Subedi skb = alloc_skb(MAX_DEVCOREDUMP_HDR_SIZE, GFP_ATOMIC);
1569695ef87SAbhishek Pandit-Subedi if (!skb)
1579695ef87SAbhishek Pandit-Subedi return -ENOMEM;
1589695ef87SAbhishek Pandit-Subedi
1599695ef87SAbhishek Pandit-Subedi dump_hdr_size = hci_devcd_mkheader(hdev, skb);
1609695ef87SAbhishek Pandit-Subedi
1619695ef87SAbhishek Pandit-Subedi if (hci_devcd_alloc(hdev, dump_hdr_size + dump_size)) {
1629695ef87SAbhishek Pandit-Subedi err = -ENOMEM;
1639695ef87SAbhishek Pandit-Subedi goto hdr_free;
1649695ef87SAbhishek Pandit-Subedi }
1659695ef87SAbhishek Pandit-Subedi
1669695ef87SAbhishek Pandit-Subedi /* Insert the device header */
1679695ef87SAbhishek Pandit-Subedi if (!hci_devcd_copy(hdev, skb->data, skb->len)) {
1689695ef87SAbhishek Pandit-Subedi bt_dev_err(hdev, "Failed to insert header");
1699695ef87SAbhishek Pandit-Subedi hci_devcd_free(hdev);
1709695ef87SAbhishek Pandit-Subedi
1719695ef87SAbhishek Pandit-Subedi err = -ENOMEM;
1729695ef87SAbhishek Pandit-Subedi goto hdr_free;
1739695ef87SAbhishek Pandit-Subedi }
1749695ef87SAbhishek Pandit-Subedi
1759695ef87SAbhishek Pandit-Subedi hdr_free:
1769695ef87SAbhishek Pandit-Subedi kfree_skb(skb);
1779695ef87SAbhishek Pandit-Subedi
1789695ef87SAbhishek Pandit-Subedi return err;
1799695ef87SAbhishek Pandit-Subedi }
1809695ef87SAbhishek Pandit-Subedi
hci_devcd_handle_pkt_init(struct hci_dev * hdev,struct sk_buff * skb)1819695ef87SAbhishek Pandit-Subedi static void hci_devcd_handle_pkt_init(struct hci_dev *hdev, struct sk_buff *skb)
1829695ef87SAbhishek Pandit-Subedi {
183*0ab905c3SZijun Hu u32 dump_size;
1849695ef87SAbhishek Pandit-Subedi
1859695ef87SAbhishek Pandit-Subedi if (hdev->dump.state != HCI_DEVCOREDUMP_IDLE) {
1869695ef87SAbhishek Pandit-Subedi DBG_UNEXPECTED_STATE();
1879695ef87SAbhishek Pandit-Subedi return;
1889695ef87SAbhishek Pandit-Subedi }
1899695ef87SAbhishek Pandit-Subedi
190*0ab905c3SZijun Hu if (skb->len != sizeof(dump_size)) {
1919695ef87SAbhishek Pandit-Subedi bt_dev_dbg(hdev, "Invalid dump init pkt");
1929695ef87SAbhishek Pandit-Subedi return;
1939695ef87SAbhishek Pandit-Subedi }
1949695ef87SAbhishek Pandit-Subedi
195*0ab905c3SZijun Hu dump_size = get_unaligned_le32(skb_pull_data(skb, 4));
196*0ab905c3SZijun Hu if (!dump_size) {
1979695ef87SAbhishek Pandit-Subedi bt_dev_err(hdev, "Zero size dump init pkt");
1989695ef87SAbhishek Pandit-Subedi return;
1999695ef87SAbhishek Pandit-Subedi }
2009695ef87SAbhishek Pandit-Subedi
201*0ab905c3SZijun Hu if (hci_devcd_prepare(hdev, dump_size)) {
2029695ef87SAbhishek Pandit-Subedi bt_dev_err(hdev, "Failed to prepare for dump");
2039695ef87SAbhishek Pandit-Subedi return;
2049695ef87SAbhishek Pandit-Subedi }
2059695ef87SAbhishek Pandit-Subedi
2069695ef87SAbhishek Pandit-Subedi hci_devcd_update_state(hdev, HCI_DEVCOREDUMP_ACTIVE);
2079695ef87SAbhishek Pandit-Subedi queue_delayed_work(hdev->workqueue, &hdev->dump.dump_timeout,
2089695ef87SAbhishek Pandit-Subedi hdev->dump.timeout);
2099695ef87SAbhishek Pandit-Subedi }
2109695ef87SAbhishek Pandit-Subedi
hci_devcd_handle_pkt_skb(struct hci_dev * hdev,struct sk_buff * skb)2119695ef87SAbhishek Pandit-Subedi static void hci_devcd_handle_pkt_skb(struct hci_dev *hdev, struct sk_buff *skb)
2129695ef87SAbhishek Pandit-Subedi {
2139695ef87SAbhishek Pandit-Subedi if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) {
2149695ef87SAbhishek Pandit-Subedi DBG_UNEXPECTED_STATE();
2159695ef87SAbhishek Pandit-Subedi return;
2169695ef87SAbhishek Pandit-Subedi }
2179695ef87SAbhishek Pandit-Subedi
2189695ef87SAbhishek Pandit-Subedi if (!hci_devcd_copy(hdev, skb->data, skb->len))
2199695ef87SAbhishek Pandit-Subedi bt_dev_dbg(hdev, "Failed to insert skb");
2209695ef87SAbhishek Pandit-Subedi }
2219695ef87SAbhishek Pandit-Subedi
hci_devcd_handle_pkt_pattern(struct hci_dev * hdev,struct sk_buff * skb)2229695ef87SAbhishek Pandit-Subedi static void hci_devcd_handle_pkt_pattern(struct hci_dev *hdev,
2239695ef87SAbhishek Pandit-Subedi struct sk_buff *skb)
2249695ef87SAbhishek Pandit-Subedi {
2259695ef87SAbhishek Pandit-Subedi struct hci_devcoredump_skb_pattern *pattern;
2269695ef87SAbhishek Pandit-Subedi
2279695ef87SAbhishek Pandit-Subedi if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) {
2289695ef87SAbhishek Pandit-Subedi DBG_UNEXPECTED_STATE();
2299695ef87SAbhishek Pandit-Subedi return;
2309695ef87SAbhishek Pandit-Subedi }
2319695ef87SAbhishek Pandit-Subedi
2329695ef87SAbhishek Pandit-Subedi if (skb->len != sizeof(*pattern)) {
2339695ef87SAbhishek Pandit-Subedi bt_dev_dbg(hdev, "Invalid pattern skb");
2349695ef87SAbhishek Pandit-Subedi return;
2359695ef87SAbhishek Pandit-Subedi }
2369695ef87SAbhishek Pandit-Subedi
2379695ef87SAbhishek Pandit-Subedi pattern = skb_pull_data(skb, sizeof(*pattern));
2389695ef87SAbhishek Pandit-Subedi
2399695ef87SAbhishek Pandit-Subedi if (!hci_devcd_memset(hdev, pattern->pattern, pattern->len))
2409695ef87SAbhishek Pandit-Subedi bt_dev_dbg(hdev, "Failed to set pattern");
2419695ef87SAbhishek Pandit-Subedi }
2429695ef87SAbhishek Pandit-Subedi
hci_devcd_handle_pkt_complete(struct hci_dev * hdev,struct sk_buff * skb)2439695ef87SAbhishek Pandit-Subedi static void hci_devcd_handle_pkt_complete(struct hci_dev *hdev,
2449695ef87SAbhishek Pandit-Subedi struct sk_buff *skb)
2459695ef87SAbhishek Pandit-Subedi {
2469695ef87SAbhishek Pandit-Subedi u32 dump_size;
2479695ef87SAbhishek Pandit-Subedi
2489695ef87SAbhishek Pandit-Subedi if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) {
2499695ef87SAbhishek Pandit-Subedi DBG_UNEXPECTED_STATE();
2509695ef87SAbhishek Pandit-Subedi return;
2519695ef87SAbhishek Pandit-Subedi }
2529695ef87SAbhishek Pandit-Subedi
2539695ef87SAbhishek Pandit-Subedi hci_devcd_update_state(hdev, HCI_DEVCOREDUMP_DONE);
2549695ef87SAbhishek Pandit-Subedi dump_size = hdev->dump.tail - hdev->dump.head;
2559695ef87SAbhishek Pandit-Subedi
2569695ef87SAbhishek Pandit-Subedi bt_dev_dbg(hdev, "complete with size %u (expect %zu)", dump_size,
2579695ef87SAbhishek Pandit-Subedi hdev->dump.alloc_size);
2589695ef87SAbhishek Pandit-Subedi
2599695ef87SAbhishek Pandit-Subedi dev_coredumpv(&hdev->dev, hdev->dump.head, dump_size, GFP_KERNEL);
2609695ef87SAbhishek Pandit-Subedi }
2619695ef87SAbhishek Pandit-Subedi
hci_devcd_handle_pkt_abort(struct hci_dev * hdev,struct sk_buff * skb)2629695ef87SAbhishek Pandit-Subedi static void hci_devcd_handle_pkt_abort(struct hci_dev *hdev,
2639695ef87SAbhishek Pandit-Subedi struct sk_buff *skb)
2649695ef87SAbhishek Pandit-Subedi {
2659695ef87SAbhishek Pandit-Subedi u32 dump_size;
2669695ef87SAbhishek Pandit-Subedi
2679695ef87SAbhishek Pandit-Subedi if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) {
2689695ef87SAbhishek Pandit-Subedi DBG_UNEXPECTED_STATE();
2699695ef87SAbhishek Pandit-Subedi return;
2709695ef87SAbhishek Pandit-Subedi }
2719695ef87SAbhishek Pandit-Subedi
2729695ef87SAbhishek Pandit-Subedi hci_devcd_update_state(hdev, HCI_DEVCOREDUMP_ABORT);
2739695ef87SAbhishek Pandit-Subedi dump_size = hdev->dump.tail - hdev->dump.head;
2749695ef87SAbhishek Pandit-Subedi
2759695ef87SAbhishek Pandit-Subedi bt_dev_dbg(hdev, "aborted with size %u (expect %zu)", dump_size,
2769695ef87SAbhishek Pandit-Subedi hdev->dump.alloc_size);
2779695ef87SAbhishek Pandit-Subedi
2789695ef87SAbhishek Pandit-Subedi /* Emit a devcoredump with the available data */
2799695ef87SAbhishek Pandit-Subedi dev_coredumpv(&hdev->dev, hdev->dump.head, dump_size, GFP_KERNEL);
2809695ef87SAbhishek Pandit-Subedi }
2819695ef87SAbhishek Pandit-Subedi
2829695ef87SAbhishek Pandit-Subedi /* Bluetooth devcoredump state machine.
2839695ef87SAbhishek Pandit-Subedi *
2849695ef87SAbhishek Pandit-Subedi * Devcoredump states:
2859695ef87SAbhishek Pandit-Subedi *
2869695ef87SAbhishek Pandit-Subedi * HCI_DEVCOREDUMP_IDLE: The default state.
2879695ef87SAbhishek Pandit-Subedi *
2889695ef87SAbhishek Pandit-Subedi * HCI_DEVCOREDUMP_ACTIVE: A devcoredump will be in this state once it has
2899695ef87SAbhishek Pandit-Subedi * been initialized using hci_devcd_init(). Once active, the driver
2909695ef87SAbhishek Pandit-Subedi * can append data using hci_devcd_append() or insert a pattern
2919695ef87SAbhishek Pandit-Subedi * using hci_devcd_append_pattern().
2929695ef87SAbhishek Pandit-Subedi *
2939695ef87SAbhishek Pandit-Subedi * HCI_DEVCOREDUMP_DONE: Once the dump collection is complete, the drive
2949695ef87SAbhishek Pandit-Subedi * can signal the completion using hci_devcd_complete(). A
2959695ef87SAbhishek Pandit-Subedi * devcoredump is generated indicating the completion event and
2969695ef87SAbhishek Pandit-Subedi * then the state machine is reset to the default state.
2979695ef87SAbhishek Pandit-Subedi *
2989695ef87SAbhishek Pandit-Subedi * HCI_DEVCOREDUMP_ABORT: The driver can cancel ongoing dump collection in
2999695ef87SAbhishek Pandit-Subedi * case of any error using hci_devcd_abort(). A devcoredump is
3009695ef87SAbhishek Pandit-Subedi * still generated with the available data indicating the abort
3019695ef87SAbhishek Pandit-Subedi * event and then the state machine is reset to the default state.
3029695ef87SAbhishek Pandit-Subedi *
3039695ef87SAbhishek Pandit-Subedi * HCI_DEVCOREDUMP_TIMEOUT: A timeout timer for HCI_DEVCOREDUMP_TIMEOUT sec
3049695ef87SAbhishek Pandit-Subedi * is started during devcoredump initialization. Once the timeout
3059695ef87SAbhishek Pandit-Subedi * occurs, the driver is notified, a devcoredump is generated with
3069695ef87SAbhishek Pandit-Subedi * the available data indicating the timeout event and then the
3079695ef87SAbhishek Pandit-Subedi * state machine is reset to the default state.
3089695ef87SAbhishek Pandit-Subedi *
3099695ef87SAbhishek Pandit-Subedi * The driver must register using hci_devcd_register() before using the hci
3109695ef87SAbhishek Pandit-Subedi * devcoredump APIs.
3119695ef87SAbhishek Pandit-Subedi */
hci_devcd_rx(struct work_struct * work)3129695ef87SAbhishek Pandit-Subedi void hci_devcd_rx(struct work_struct *work)
3139695ef87SAbhishek Pandit-Subedi {
3149695ef87SAbhishek Pandit-Subedi struct hci_dev *hdev = container_of(work, struct hci_dev, dump.dump_rx);
3159695ef87SAbhishek Pandit-Subedi struct sk_buff *skb;
3169695ef87SAbhishek Pandit-Subedi int start_state;
3179695ef87SAbhishek Pandit-Subedi
3189695ef87SAbhishek Pandit-Subedi while ((skb = skb_dequeue(&hdev->dump.dump_q))) {
3199695ef87SAbhishek Pandit-Subedi /* Return if timeout occurs. The timeout handler function
3209695ef87SAbhishek Pandit-Subedi * hci_devcd_timeout() will report the available dump data.
3219695ef87SAbhishek Pandit-Subedi */
3229695ef87SAbhishek Pandit-Subedi if (hdev->dump.state == HCI_DEVCOREDUMP_TIMEOUT) {
3239695ef87SAbhishek Pandit-Subedi kfree_skb(skb);
3249695ef87SAbhishek Pandit-Subedi return;
3259695ef87SAbhishek Pandit-Subedi }
3269695ef87SAbhishek Pandit-Subedi
3279695ef87SAbhishek Pandit-Subedi hci_dev_lock(hdev);
3289695ef87SAbhishek Pandit-Subedi start_state = hdev->dump.state;
3299695ef87SAbhishek Pandit-Subedi
3309695ef87SAbhishek Pandit-Subedi switch (hci_dmp_cb(skb)->pkt_type) {
3319695ef87SAbhishek Pandit-Subedi case HCI_DEVCOREDUMP_PKT_INIT:
3329695ef87SAbhishek Pandit-Subedi hci_devcd_handle_pkt_init(hdev, skb);
3339695ef87SAbhishek Pandit-Subedi break;
3349695ef87SAbhishek Pandit-Subedi
3359695ef87SAbhishek Pandit-Subedi case HCI_DEVCOREDUMP_PKT_SKB:
3369695ef87SAbhishek Pandit-Subedi hci_devcd_handle_pkt_skb(hdev, skb);
3379695ef87SAbhishek Pandit-Subedi break;
3389695ef87SAbhishek Pandit-Subedi
3399695ef87SAbhishek Pandit-Subedi case HCI_DEVCOREDUMP_PKT_PATTERN:
3409695ef87SAbhishek Pandit-Subedi hci_devcd_handle_pkt_pattern(hdev, skb);
3419695ef87SAbhishek Pandit-Subedi break;
3429695ef87SAbhishek Pandit-Subedi
3439695ef87SAbhishek Pandit-Subedi case HCI_DEVCOREDUMP_PKT_COMPLETE:
3449695ef87SAbhishek Pandit-Subedi hci_devcd_handle_pkt_complete(hdev, skb);
3459695ef87SAbhishek Pandit-Subedi break;
3469695ef87SAbhishek Pandit-Subedi
3479695ef87SAbhishek Pandit-Subedi case HCI_DEVCOREDUMP_PKT_ABORT:
3489695ef87SAbhishek Pandit-Subedi hci_devcd_handle_pkt_abort(hdev, skb);
3499695ef87SAbhishek Pandit-Subedi break;
3509695ef87SAbhishek Pandit-Subedi
3519695ef87SAbhishek Pandit-Subedi default:
3529695ef87SAbhishek Pandit-Subedi bt_dev_dbg(hdev, "Unknown packet (%d) for state (%d). ",
3539695ef87SAbhishek Pandit-Subedi hci_dmp_cb(skb)->pkt_type, hdev->dump.state);
3549695ef87SAbhishek Pandit-Subedi break;
3559695ef87SAbhishek Pandit-Subedi }
3569695ef87SAbhishek Pandit-Subedi
3579695ef87SAbhishek Pandit-Subedi hci_dev_unlock(hdev);
3589695ef87SAbhishek Pandit-Subedi kfree_skb(skb);
3599695ef87SAbhishek Pandit-Subedi
3609695ef87SAbhishek Pandit-Subedi /* Notify the driver about any state changes before resetting
3619695ef87SAbhishek Pandit-Subedi * the state machine
3629695ef87SAbhishek Pandit-Subedi */
3639695ef87SAbhishek Pandit-Subedi if (start_state != hdev->dump.state)
3649695ef87SAbhishek Pandit-Subedi hci_devcd_notify(hdev, hdev->dump.state);
3659695ef87SAbhishek Pandit-Subedi
3669695ef87SAbhishek Pandit-Subedi /* Reset the state machine if the devcoredump is complete */
3679695ef87SAbhishek Pandit-Subedi hci_dev_lock(hdev);
3689695ef87SAbhishek Pandit-Subedi if (hdev->dump.state == HCI_DEVCOREDUMP_DONE ||
3699695ef87SAbhishek Pandit-Subedi hdev->dump.state == HCI_DEVCOREDUMP_ABORT)
3709695ef87SAbhishek Pandit-Subedi hci_devcd_reset(hdev);
3719695ef87SAbhishek Pandit-Subedi hci_dev_unlock(hdev);
3729695ef87SAbhishek Pandit-Subedi }
3739695ef87SAbhishek Pandit-Subedi }
3749695ef87SAbhishek Pandit-Subedi EXPORT_SYMBOL(hci_devcd_rx);
3759695ef87SAbhishek Pandit-Subedi
hci_devcd_timeout(struct work_struct * work)3769695ef87SAbhishek Pandit-Subedi void hci_devcd_timeout(struct work_struct *work)
3779695ef87SAbhishek Pandit-Subedi {
3789695ef87SAbhishek Pandit-Subedi struct hci_dev *hdev = container_of(work, struct hci_dev,
3799695ef87SAbhishek Pandit-Subedi dump.dump_timeout.work);
3809695ef87SAbhishek Pandit-Subedi u32 dump_size;
3819695ef87SAbhishek Pandit-Subedi
3829695ef87SAbhishek Pandit-Subedi hci_devcd_notify(hdev, HCI_DEVCOREDUMP_TIMEOUT);
3839695ef87SAbhishek Pandit-Subedi
3849695ef87SAbhishek Pandit-Subedi hci_dev_lock(hdev);
3859695ef87SAbhishek Pandit-Subedi
3869695ef87SAbhishek Pandit-Subedi cancel_work(&hdev->dump.dump_rx);
3879695ef87SAbhishek Pandit-Subedi
3889695ef87SAbhishek Pandit-Subedi hci_devcd_update_state(hdev, HCI_DEVCOREDUMP_TIMEOUT);
3899695ef87SAbhishek Pandit-Subedi
3909695ef87SAbhishek Pandit-Subedi dump_size = hdev->dump.tail - hdev->dump.head;
3919695ef87SAbhishek Pandit-Subedi bt_dev_dbg(hdev, "timeout with size %u (expect %zu)", dump_size,
3929695ef87SAbhishek Pandit-Subedi hdev->dump.alloc_size);
3939695ef87SAbhishek Pandit-Subedi
3949695ef87SAbhishek Pandit-Subedi /* Emit a devcoredump with the available data */
3959695ef87SAbhishek Pandit-Subedi dev_coredumpv(&hdev->dev, hdev->dump.head, dump_size, GFP_KERNEL);
3969695ef87SAbhishek Pandit-Subedi
3979695ef87SAbhishek Pandit-Subedi hci_devcd_reset(hdev);
3989695ef87SAbhishek Pandit-Subedi
3999695ef87SAbhishek Pandit-Subedi hci_dev_unlock(hdev);
4009695ef87SAbhishek Pandit-Subedi }
4019695ef87SAbhishek Pandit-Subedi EXPORT_SYMBOL(hci_devcd_timeout);
4029695ef87SAbhishek Pandit-Subedi
hci_devcd_register(struct hci_dev * hdev,coredump_t coredump,dmp_hdr_t dmp_hdr,notify_change_t notify_change)4039695ef87SAbhishek Pandit-Subedi int hci_devcd_register(struct hci_dev *hdev, coredump_t coredump,
4049695ef87SAbhishek Pandit-Subedi dmp_hdr_t dmp_hdr, notify_change_t notify_change)
4059695ef87SAbhishek Pandit-Subedi {
4069695ef87SAbhishek Pandit-Subedi /* Driver must implement coredump() and dmp_hdr() functions for
4079695ef87SAbhishek Pandit-Subedi * bluetooth devcoredump. The coredump() should trigger a coredump
4089695ef87SAbhishek Pandit-Subedi * event on the controller when the device's coredump sysfs entry is
4099695ef87SAbhishek Pandit-Subedi * written to. The dmp_hdr() should create a dump header to identify
4109695ef87SAbhishek Pandit-Subedi * the controller/fw/driver info.
4119695ef87SAbhishek Pandit-Subedi */
4129695ef87SAbhishek Pandit-Subedi if (!coredump || !dmp_hdr)
4139695ef87SAbhishek Pandit-Subedi return -EINVAL;
4149695ef87SAbhishek Pandit-Subedi
4159695ef87SAbhishek Pandit-Subedi hci_dev_lock(hdev);
4169695ef87SAbhishek Pandit-Subedi hdev->dump.coredump = coredump;
4179695ef87SAbhishek Pandit-Subedi hdev->dump.dmp_hdr = dmp_hdr;
4189695ef87SAbhishek Pandit-Subedi hdev->dump.notify_change = notify_change;
4199695ef87SAbhishek Pandit-Subedi hdev->dump.supported = true;
4209695ef87SAbhishek Pandit-Subedi hdev->dump.timeout = DEVCOREDUMP_TIMEOUT;
4219695ef87SAbhishek Pandit-Subedi hci_dev_unlock(hdev);
4229695ef87SAbhishek Pandit-Subedi
4239695ef87SAbhishek Pandit-Subedi return 0;
4249695ef87SAbhishek Pandit-Subedi }
4259695ef87SAbhishek Pandit-Subedi EXPORT_SYMBOL(hci_devcd_register);
4269695ef87SAbhishek Pandit-Subedi
hci_devcd_enabled(struct hci_dev * hdev)4279695ef87SAbhishek Pandit-Subedi static inline bool hci_devcd_enabled(struct hci_dev *hdev)
4289695ef87SAbhishek Pandit-Subedi {
4299695ef87SAbhishek Pandit-Subedi return hdev->dump.supported;
4309695ef87SAbhishek Pandit-Subedi }
4319695ef87SAbhishek Pandit-Subedi
hci_devcd_init(struct hci_dev * hdev,u32 dump_size)4329695ef87SAbhishek Pandit-Subedi int hci_devcd_init(struct hci_dev *hdev, u32 dump_size)
4339695ef87SAbhishek Pandit-Subedi {
4349695ef87SAbhishek Pandit-Subedi struct sk_buff *skb;
4359695ef87SAbhishek Pandit-Subedi
4369695ef87SAbhishek Pandit-Subedi if (!hci_devcd_enabled(hdev))
4379695ef87SAbhishek Pandit-Subedi return -EOPNOTSUPP;
4389695ef87SAbhishek Pandit-Subedi
4399695ef87SAbhishek Pandit-Subedi skb = alloc_skb(sizeof(dump_size), GFP_ATOMIC);
4409695ef87SAbhishek Pandit-Subedi if (!skb)
4419695ef87SAbhishek Pandit-Subedi return -ENOMEM;
4429695ef87SAbhishek Pandit-Subedi
4439695ef87SAbhishek Pandit-Subedi hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_INIT;
444*0ab905c3SZijun Hu put_unaligned_le32(dump_size, skb_put(skb, 4));
4459695ef87SAbhishek Pandit-Subedi
4469695ef87SAbhishek Pandit-Subedi skb_queue_tail(&hdev->dump.dump_q, skb);
4479695ef87SAbhishek Pandit-Subedi queue_work(hdev->workqueue, &hdev->dump.dump_rx);
4489695ef87SAbhishek Pandit-Subedi
4499695ef87SAbhishek Pandit-Subedi return 0;
4509695ef87SAbhishek Pandit-Subedi }
4519695ef87SAbhishek Pandit-Subedi EXPORT_SYMBOL(hci_devcd_init);
4529695ef87SAbhishek Pandit-Subedi
hci_devcd_append(struct hci_dev * hdev,struct sk_buff * skb)4539695ef87SAbhishek Pandit-Subedi int hci_devcd_append(struct hci_dev *hdev, struct sk_buff *skb)
4549695ef87SAbhishek Pandit-Subedi {
4559695ef87SAbhishek Pandit-Subedi if (!skb)
4569695ef87SAbhishek Pandit-Subedi return -ENOMEM;
4579695ef87SAbhishek Pandit-Subedi
4589695ef87SAbhishek Pandit-Subedi if (!hci_devcd_enabled(hdev)) {
4599695ef87SAbhishek Pandit-Subedi kfree_skb(skb);
4609695ef87SAbhishek Pandit-Subedi return -EOPNOTSUPP;
4619695ef87SAbhishek Pandit-Subedi }
4629695ef87SAbhishek Pandit-Subedi
4639695ef87SAbhishek Pandit-Subedi hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_SKB;
4649695ef87SAbhishek Pandit-Subedi
4659695ef87SAbhishek Pandit-Subedi skb_queue_tail(&hdev->dump.dump_q, skb);
4669695ef87SAbhishek Pandit-Subedi queue_work(hdev->workqueue, &hdev->dump.dump_rx);
4679695ef87SAbhishek Pandit-Subedi
4689695ef87SAbhishek Pandit-Subedi return 0;
4699695ef87SAbhishek Pandit-Subedi }
4709695ef87SAbhishek Pandit-Subedi EXPORT_SYMBOL(hci_devcd_append);
4719695ef87SAbhishek Pandit-Subedi
hci_devcd_append_pattern(struct hci_dev * hdev,u8 pattern,u32 len)4729695ef87SAbhishek Pandit-Subedi int hci_devcd_append_pattern(struct hci_dev *hdev, u8 pattern, u32 len)
4739695ef87SAbhishek Pandit-Subedi {
4749695ef87SAbhishek Pandit-Subedi struct hci_devcoredump_skb_pattern p;
4759695ef87SAbhishek Pandit-Subedi struct sk_buff *skb;
4769695ef87SAbhishek Pandit-Subedi
4779695ef87SAbhishek Pandit-Subedi if (!hci_devcd_enabled(hdev))
4789695ef87SAbhishek Pandit-Subedi return -EOPNOTSUPP;
4799695ef87SAbhishek Pandit-Subedi
4809695ef87SAbhishek Pandit-Subedi skb = alloc_skb(sizeof(p), GFP_ATOMIC);
4819695ef87SAbhishek Pandit-Subedi if (!skb)
4829695ef87SAbhishek Pandit-Subedi return -ENOMEM;
4839695ef87SAbhishek Pandit-Subedi
4849695ef87SAbhishek Pandit-Subedi p.pattern = pattern;
4859695ef87SAbhishek Pandit-Subedi p.len = len;
4869695ef87SAbhishek Pandit-Subedi
4879695ef87SAbhishek Pandit-Subedi hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_PATTERN;
4889695ef87SAbhishek Pandit-Subedi skb_put_data(skb, &p, sizeof(p));
4899695ef87SAbhishek Pandit-Subedi
4909695ef87SAbhishek Pandit-Subedi skb_queue_tail(&hdev->dump.dump_q, skb);
4919695ef87SAbhishek Pandit-Subedi queue_work(hdev->workqueue, &hdev->dump.dump_rx);
4929695ef87SAbhishek Pandit-Subedi
4939695ef87SAbhishek Pandit-Subedi return 0;
4949695ef87SAbhishek Pandit-Subedi }
4959695ef87SAbhishek Pandit-Subedi EXPORT_SYMBOL(hci_devcd_append_pattern);
4969695ef87SAbhishek Pandit-Subedi
hci_devcd_complete(struct hci_dev * hdev)4979695ef87SAbhishek Pandit-Subedi int hci_devcd_complete(struct hci_dev *hdev)
4989695ef87SAbhishek Pandit-Subedi {
4999695ef87SAbhishek Pandit-Subedi struct sk_buff *skb;
5009695ef87SAbhishek Pandit-Subedi
5019695ef87SAbhishek Pandit-Subedi if (!hci_devcd_enabled(hdev))
5029695ef87SAbhishek Pandit-Subedi return -EOPNOTSUPP;
5039695ef87SAbhishek Pandit-Subedi
5049695ef87SAbhishek Pandit-Subedi skb = alloc_skb(0, GFP_ATOMIC);
5059695ef87SAbhishek Pandit-Subedi if (!skb)
5069695ef87SAbhishek Pandit-Subedi return -ENOMEM;
5079695ef87SAbhishek Pandit-Subedi
5089695ef87SAbhishek Pandit-Subedi hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_COMPLETE;
5099695ef87SAbhishek Pandit-Subedi
5109695ef87SAbhishek Pandit-Subedi skb_queue_tail(&hdev->dump.dump_q, skb);
5119695ef87SAbhishek Pandit-Subedi queue_work(hdev->workqueue, &hdev->dump.dump_rx);
5129695ef87SAbhishek Pandit-Subedi
5139695ef87SAbhishek Pandit-Subedi return 0;
5149695ef87SAbhishek Pandit-Subedi }
5159695ef87SAbhishek Pandit-Subedi EXPORT_SYMBOL(hci_devcd_complete);
5169695ef87SAbhishek Pandit-Subedi
hci_devcd_abort(struct hci_dev * hdev)5179695ef87SAbhishek Pandit-Subedi int hci_devcd_abort(struct hci_dev *hdev)
5189695ef87SAbhishek Pandit-Subedi {
5199695ef87SAbhishek Pandit-Subedi struct sk_buff *skb;
5209695ef87SAbhishek Pandit-Subedi
5219695ef87SAbhishek Pandit-Subedi if (!hci_devcd_enabled(hdev))
5229695ef87SAbhishek Pandit-Subedi return -EOPNOTSUPP;
5239695ef87SAbhishek Pandit-Subedi
5249695ef87SAbhishek Pandit-Subedi skb = alloc_skb(0, GFP_ATOMIC);
5259695ef87SAbhishek Pandit-Subedi if (!skb)
5269695ef87SAbhishek Pandit-Subedi return -ENOMEM;
5279695ef87SAbhishek Pandit-Subedi
5289695ef87SAbhishek Pandit-Subedi hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_ABORT;
5299695ef87SAbhishek Pandit-Subedi
5309695ef87SAbhishek Pandit-Subedi skb_queue_tail(&hdev->dump.dump_q, skb);
5319695ef87SAbhishek Pandit-Subedi queue_work(hdev->workqueue, &hdev->dump.dump_rx);
5329695ef87SAbhishek Pandit-Subedi
5339695ef87SAbhishek Pandit-Subedi return 0;
5349695ef87SAbhishek Pandit-Subedi }
5359695ef87SAbhishek Pandit-Subedi EXPORT_SYMBOL(hci_devcd_abort);
536