Message ID | 20220623123549.v2.1.Iaf638bb9f885f5880ab1b4e7ae2f73dd53a54661@changeid |
---|---|
State | Superseded |
Headers | show |
Series | [v2,1/2] Bluetooth: Add support for devcoredump | expand |
This is automated email and please do not reply to this email! Dear submitter, Thank you for submitting the patches to the linux bluetooth mailing list. This is a CI test results with your patch series: PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=653346 ---Test result--- Test Summary: CheckPatch FAIL 3.12 seconds GitLint PASS 2.26 seconds SubjectPrefix PASS 1.98 seconds BuildKernel PASS 32.24 seconds BuildKernel32 PASS 28.83 seconds Incremental Build with patchesPASS 46.96 seconds TestRunner: Setup PASS 479.27 seconds TestRunner: l2cap-tester PASS 17.47 seconds TestRunner: bnep-tester PASS 6.18 seconds TestRunner: mgmt-tester PASS 102.90 seconds TestRunner: rfcomm-tester PASS 9.68 seconds TestRunner: sco-tester PASS 9.51 seconds TestRunner: smp-tester PASS 9.47 seconds TestRunner: userchan-tester PASS 6.41 seconds Details ############################## Test: CheckPatch - FAIL - 3.12 seconds Run checkpatch.pl script with rule in .checkpatch.conf [v2,1/2] Bluetooth: Add support for devcoredump\Traceback (most recent call last): File "scripts/spdxcheck.py", line 6, in <module> from ply import lex, yacc ModuleNotFoundError: No module named 'ply' WARNING:FILE_PATH_CHANGES: added, moved or deleted file(s), does MAINTAINERS need updating? #130: new file mode 100644 WARNING:SPDX_LICENSE_TAG: Improper SPDX comment style for 'include/net/bluetooth/coredump.h', please use '/*' instead #135: FILE: include/net/bluetooth/coredump.h:1: +// SPDX-License-Identifier: GPL-2.0-only WARNING:SPDX_LICENSE_TAG: Missing or malformed SPDX-License-Identifier tag in line 1 #135: FILE: include/net/bluetooth/coredump.h:1: +// SPDX-License-Identifier: GPL-2.0-only WARNING:SPLIT_STRING: quoted string split across lines #585: FILE: net/bluetooth/coredump.c:300: + "Devcoredump complete with size %u " + "(expect %u)", WARNING:SPLIT_STRING: quoted string split across lines #604: FILE: net/bluetooth/coredump.c:319: + "Devcoredump aborted with size %u " + "(expect %u)", WARNING:OOM_MESSAGE: Possible unnecessary 'out of memory' message #687: FILE: net/bluetooth/coredump.c:402: + if (!skb) { + bt_dev_err(hdev, "Failed to allocate devcoredump init"); WARNING:OOM_MESSAGE: Possible unnecessary 'out of memory' message #730: FILE: net/bluetooth/coredump.c:445: + if (!skb) { + bt_dev_err(hdev, "Failed to allocate devcoredump pattern"); WARNING:OOM_MESSAGE: Possible unnecessary 'out of memory' message #756: FILE: net/bluetooth/coredump.c:471: + if (!skb) { + bt_dev_err(hdev, "Failed to allocate devcoredump complete"); WARNING:OOM_MESSAGE: Possible unnecessary 'out of memory' message #778: FILE: net/bluetooth/coredump.c:493: + if (!skb) { + bt_dev_err(hdev, "Failed to allocate devcoredump abort"); total: 0 errors, 9 warnings, 0 checks, 669 lines checked NOTE: For some of the reported defects, checkpatch may be able to mechanically convert to the typical style using --fix or --fix-inplace. /github/workspace/src/12893145.patch has style problems, please review. NOTE: Ignored message types: UNKNOWN_COMMIT_ID NOTE: If any of the errors are false positives, please report them to the maintainer, see CHECKPATCH in MAINTAINERS. --- Regards, Linux Bluetooth
Dear Manish, dear Abhishek, Thank you for the patch. Am 23.06.22 um 21:37 schrieb Manish Mandlik: > From: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> > > Add devcoredump APIs to hci core so that drivers only have to provide > the dump skbs instead of managing the synchronization and timeouts. > > The devcoredump APIs should be used in the following manner: > - hci_devcoredump_init is called to allocate the dump. > - hci_devcoredump_append is called to append any skbs with dump data > OR hci_devcoredump_append_pattern is called to insert a pattern. > - hci_devcoredump_complete is called when all dump packets have been > sent OR hci_devcoredump_abort is called to indicate an error and > cancel an ongoing dump collection. > > The high level APIs just prepare some skbs with the appropriate data and > queue it for the dump to process. Packets part of the crashdump can be > intercepted in the driver in interrupt context and forwarded directly to > the devcoredump APIs. > > Internally, there are 5 states for the dump: idle, active, complete, > abort and timeout. A devcoredump will only be in active state after it > has been initialized. Once active, it accepts data to be appended, > patterns to be inserted (i.e. memset) and a completion event or an abort > event to generate a devcoredump. The timeout is initialized at the same > time the dump is initialized (defaulting to 10s) and will be cleared > either when the timeout occurs or the dump is complete or aborted. Do you have a usage example, how this can be used fro userspace? > Signed-off-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> > Signed-off-by: Manish Mandlik <mmandlik@google.com> > Reviewed-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> > --- > > Changes in v2: > - Move hci devcoredump implementation to new files > - Move dump queue and dump work to hci_devcoredump struct > - Add CONFIG_DEV_COREDUMP conditional compile > > include/net/bluetooth/coredump.h | 109 +++++++ > include/net/bluetooth/hci_core.h | 5 + > net/bluetooth/Makefile | 2 + > net/bluetooth/coredump.c | 504 +++++++++++++++++++++++++++++++ > net/bluetooth/hci_core.c | 9 + > net/bluetooth/hci_sync.c | 2 + > 6 files changed, 631 insertions(+) > create mode 100644 include/net/bluetooth/coredump.h > create mode 100644 net/bluetooth/coredump.c > > diff --git a/include/net/bluetooth/coredump.h b/include/net/bluetooth/coredump.h > new file mode 100644 > index 000000000000..73601c409c6e > --- /dev/null > +++ b/include/net/bluetooth/coredump.h > @@ -0,0 +1,109 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (C) 2022 Google Corporation > + */ > + > +#ifndef __COREDUMP_H > +#define __COREDUMP_H > + > +#define DEVCOREDUMP_TIMEOUT msecs_to_jiffies(10000) /* 10 sec */ > + > +typedef int (*dmp_hdr_t)(struct hci_dev *hdev, char *buf, size_t size); > +typedef void (*notify_change_t)(struct hci_dev *hdev, int state); > + > +/* struct hci_devcoredump - Devcoredump state > + * > + * @supported: Indicates if FW dump collection is supported by driver > + * @state: Current state of dump collection > + * @alloc_size: Total size of the dump > + * @head: Start of the dump > + * @tail: Pointer to current end of dump > + * @end: head + alloc_size for easy comparisons > + * > + * @dump_q: Dump queue for state machine to process > + * @dump_rx: Devcoredump state machine work > + * @dump_timeout: Devcoredump timeout work > + * > + * @dmp_hdr: Create a dump header to identify controller/fw/driver info > + * @notify_change: Notify driver when devcoredump state has changed > + */ > +struct hci_devcoredump { > + bool supported; > + > + enum devcoredump_state { > + HCI_DEVCOREDUMP_IDLE, > + HCI_DEVCOREDUMP_ACTIVE, > + HCI_DEVCOREDUMP_DONE, > + HCI_DEVCOREDUMP_ABORT, > + HCI_DEVCOREDUMP_TIMEOUT > + } state; > + > + u32 alloc_size; As it’s not packed, why specify the length? Just use size_t or `unsigned int`? […] Kind regards, Paul
Hi Manish, On Thu, Jun 23, 2022 at 12:38 PM Manish Mandlik <mmandlik@google.com> wrote: > > From: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> > > Add devcoredump APIs to hci core so that drivers only have to provide > the dump skbs instead of managing the synchronization and timeouts. > > The devcoredump APIs should be used in the following manner: > - hci_devcoredump_init is called to allocate the dump. > - hci_devcoredump_append is called to append any skbs with dump data > OR hci_devcoredump_append_pattern is called to insert a pattern. > - hci_devcoredump_complete is called when all dump packets have been > sent OR hci_devcoredump_abort is called to indicate an error and > cancel an ongoing dump collection. > > The high level APIs just prepare some skbs with the appropriate data and > queue it for the dump to process. Packets part of the crashdump can be > intercepted in the driver in interrupt context and forwarded directly to > the devcoredump APIs. > > Internally, there are 5 states for the dump: idle, active, complete, > abort and timeout. A devcoredump will only be in active state after it > has been initialized. Once active, it accepts data to be appended, > patterns to be inserted (i.e. memset) and a completion event or an abort > event to generate a devcoredump. The timeout is initialized at the same > time the dump is initialized (defaulting to 10s) and will be cleared > either when the timeout occurs or the dump is complete or aborted. > > Signed-off-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> > Signed-off-by: Manish Mandlik <mmandlik@google.com> > Reviewed-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> > --- > > Changes in v2: > - Move hci devcoredump implementation to new files > - Move dump queue and dump work to hci_devcoredump struct > - Add CONFIG_DEV_COREDUMP conditional compile Looks like you didn't add an experimental UUID for enabling it, it should be per index and we mark as supported when the driver supports it then userspace can mark it to be used via main.conf so we can properly experiment with it before marking it as stable. > > include/net/bluetooth/coredump.h | 109 +++++++ > include/net/bluetooth/hci_core.h | 5 + > net/bluetooth/Makefile | 2 + > net/bluetooth/coredump.c | 504 +++++++++++++++++++++++++++++++ > net/bluetooth/hci_core.c | 9 + > net/bluetooth/hci_sync.c | 2 + > 6 files changed, 631 insertions(+) > create mode 100644 include/net/bluetooth/coredump.h > create mode 100644 net/bluetooth/coredump.c > > diff --git a/include/net/bluetooth/coredump.h b/include/net/bluetooth/coredump.h > new file mode 100644 > index 000000000000..73601c409c6e > --- /dev/null > +++ b/include/net/bluetooth/coredump.h > @@ -0,0 +1,109 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (C) 2022 Google Corporation > + */ > + > +#ifndef __COREDUMP_H > +#define __COREDUMP_H > + > +#define DEVCOREDUMP_TIMEOUT msecs_to_jiffies(10000) /* 10 sec */ > + > +typedef int (*dmp_hdr_t)(struct hci_dev *hdev, char *buf, size_t size); > +typedef void (*notify_change_t)(struct hci_dev *hdev, int state); > + > +/* struct hci_devcoredump - Devcoredump state > + * > + * @supported: Indicates if FW dump collection is supported by driver > + * @state: Current state of dump collection > + * @alloc_size: Total size of the dump > + * @head: Start of the dump > + * @tail: Pointer to current end of dump > + * @end: head + alloc_size for easy comparisons > + * > + * @dump_q: Dump queue for state machine to process > + * @dump_rx: Devcoredump state machine work > + * @dump_timeout: Devcoredump timeout work > + * > + * @dmp_hdr: Create a dump header to identify controller/fw/driver info > + * @notify_change: Notify driver when devcoredump state has changed > + */ > +struct hci_devcoredump { > + bool supported; > + > + enum devcoredump_state { > + HCI_DEVCOREDUMP_IDLE, > + HCI_DEVCOREDUMP_ACTIVE, > + HCI_DEVCOREDUMP_DONE, > + HCI_DEVCOREDUMP_ABORT, > + HCI_DEVCOREDUMP_TIMEOUT > + } state; > + > + u32 alloc_size; > + char *head; > + char *tail; > + char *end; > + > + struct sk_buff_head dump_q; > + struct work_struct dump_rx; > + struct delayed_work dump_timeout; > + > + dmp_hdr_t dmp_hdr; > + notify_change_t notify_change; > +}; > + > +#ifdef CONFIG_DEV_COREDUMP > + > +void hci_devcoredump_reset(struct hci_dev *hdev); > +void hci_devcoredump_rx(struct work_struct *work); > +void hci_devcoredump_timeout(struct work_struct *work); > +int hci_devcoredump_register(struct hci_dev *hdev, dmp_hdr_t dmp_hdr, > + notify_change_t notify_change); > +int hci_devcoredump_init(struct hci_dev *hdev, u32 dmp_size); > +int hci_devcoredump_append(struct hci_dev *hdev, struct sk_buff *skb); > +int hci_devcoredump_append_pattern(struct hci_dev *hdev, u8 pattern, u32 len); > +int hci_devcoredump_complete(struct hci_dev *hdev); > +int hci_devcoredump_abort(struct hci_dev *hdev); > + > +#else > + > +static inline void hci_devcoredump_reset(struct hci_dev *hdev) {} > +static inline void hci_devcoredump_rx(struct work_struct *work) {} > +static inline void hci_devcoredump_timeout(struct work_struct *work) {} > + > +static inline int hci_devcoredump_register(struct hci_dev *hdev, > + dmp_hdr_t dmp_hdr, > + notify_change_t notify_change) > +{ > + return -EOPNOTSUPP; > +} > + > +static inline int hci_devcoredump_init(struct hci_dev *hdev, u32 dmp_size) > +{ > + return -EOPNOTSUPP; > +} > + > +static inline int hci_devcoredump_append(struct hci_dev *hdev, > + struct sk_buff *skb) > +{ > + return -EOPNOTSUPP; > +} > + > +static inline int hci_devcoredump_append_pattern(struct hci_dev *hdev, > + u8 pattern, u32 len) > +{ > + return -EOPNOTSUPP; > +} > + > +static inline int hci_devcoredump_complete(struct hci_dev *hdev) > +{ > + return -EOPNOTSUPP; > +} > + > +static inline int hci_devcoredump_abort(struct hci_dev *hdev) > +{ > + return -EOPNOTSUPP; > +} > + > +#endif /* CONFIG_DEV_COREDUMP */ > + > +#endif /* __COREDUMP_H */ > diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h > index 15237ee5f761..9ccc034c8fde 100644 > --- a/include/net/bluetooth/hci_core.h > +++ b/include/net/bluetooth/hci_core.h > @@ -32,6 +32,7 @@ > #include <net/bluetooth/hci.h> > #include <net/bluetooth/hci_sync.h> > #include <net/bluetooth/hci_sock.h> > +#include <net/bluetooth/coredump.h> > > /* HCI priority */ > #define HCI_PRIO_MAX 7 > @@ -582,6 +583,10 @@ struct hci_dev { > const char *fw_info; > struct dentry *debugfs; > > +#ifdef CONFIG_DEV_COREDUMP > + struct hci_devcoredump dump; > +#endif > + > struct device dev; > > struct rfkill *rfkill; > diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile > index a52bba8500e1..4e894e452869 100644 > --- a/net/bluetooth/Makefile > +++ b/net/bluetooth/Makefile > @@ -17,6 +17,8 @@ bluetooth-y := af_bluetooth.o hci_core.o hci_conn.o hci_event.o mgmt.o \ > ecdh_helper.o hci_request.o mgmt_util.o mgmt_config.o hci_codec.o \ > eir.o hci_sync.o > > +bluetooth-$(CONFIG_DEV_COREDUMP) += coredump.o > + > bluetooth-$(CONFIG_BT_BREDR) += sco.o > bluetooth-$(CONFIG_BT_HS) += a2mp.o amp.o > bluetooth-$(CONFIG_BT_LEDS) += leds.o > diff --git a/net/bluetooth/coredump.c b/net/bluetooth/coredump.c > new file mode 100644 > index 000000000000..43c355cd7ad3 > --- /dev/null > +++ b/net/bluetooth/coredump.c > @@ -0,0 +1,504 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (C) 2022 Google Corporation > + */ > + > +#include <linux/devcoredump.h> > + > +#include <net/bluetooth/bluetooth.h> > +#include <net/bluetooth/hci_core.h> > + > +enum hci_devcoredump_pkt_type { > + HCI_DEVCOREDUMP_PKT_INIT, > + HCI_DEVCOREDUMP_PKT_SKB, > + HCI_DEVCOREDUMP_PKT_PATTERN, > + HCI_DEVCOREDUMP_PKT_COMPLETE, > + HCI_DEVCOREDUMP_PKT_ABORT, > +}; > + > +struct hci_devcoredump_skb_cb { > + u16 pkt_type; > +}; > + > +struct hci_devcoredump_skb_pattern { > + u8 pattern; > + u32 len; > +} __packed; > + > +#define hci_dmp_cb(skb) ((struct hci_devcoredump_skb_cb *)((skb)->cb)) > + > +#define MAX_DEVCOREDUMP_HDR_SIZE 512 /* bytes */ > + > +static int hci_devcoredump_update_hdr_state(char *buf, size_t size, int state) > +{ > + if (!buf) > + return 0; > + > + return snprintf(buf, size, "Bluetooth devcoredump\nState: %d\n", state); > +} > + > +/* Call with hci_dev_lock only. */ > +static int hci_devcoredump_update_state(struct hci_dev *hdev, int state) > +{ > + hdev->dump.state = state; > + > + return hci_devcoredump_update_hdr_state(hdev->dump.head, > + hdev->dump.alloc_size, state); > +} > + > +static int hci_devcoredump_mkheader(struct hci_dev *hdev, char *buf, > + size_t buf_size) > +{ > + char *ptr = buf; > + size_t rem = buf_size; > + size_t read = 0; > + > + read = hci_devcoredump_update_hdr_state(ptr, rem, HCI_DEVCOREDUMP_IDLE); > + read += 1; /* update_hdr_state adds \0 at the end upon state rewrite */ > + rem -= read; > + ptr += read; > + > + if (hdev->dump.dmp_hdr) { > + /* dmp_hdr() should return number of bytes written */ > + read = hdev->dump.dmp_hdr(hdev, ptr, rem); > + rem -= read; > + ptr += read; > + } > + > + read = snprintf(ptr, rem, "--- Start dump ---\n"); > + rem -= read; > + ptr += read; > + > + return buf_size - rem; > +} > + > +/* Do not call with hci_dev_lock since this calls driver code. */ > +static void hci_devcoredump_notify(struct hci_dev *hdev, int state) > +{ > + if (hdev->dump.notify_change) > + hdev->dump.notify_change(hdev, state); > +} > + > +/* Call with hci_dev_lock only. */ > +void hci_devcoredump_reset(struct hci_dev *hdev) > +{ > + hdev->dump.head = NULL; > + hdev->dump.tail = NULL; > + hdev->dump.alloc_size = 0; > + > + hci_devcoredump_update_state(hdev, HCI_DEVCOREDUMP_IDLE); > + > + cancel_delayed_work(&hdev->dump.dump_timeout); > + skb_queue_purge(&hdev->dump.dump_q); > +} > + > +/* Call with hci_dev_lock only. */ > +static void hci_devcoredump_free(struct hci_dev *hdev) > +{ > + if (hdev->dump.head) > + vfree(hdev->dump.head); > + > + hci_devcoredump_reset(hdev); > +} > + > +/* Call with hci_dev_lock only. */ > +static int hci_devcoredump_alloc(struct hci_dev *hdev, u32 size) > +{ > + hdev->dump.head = vmalloc(size); > + if (!hdev->dump.head) > + return -ENOMEM; > + > + hdev->dump.alloc_size = size; > + hdev->dump.tail = hdev->dump.head; > + hdev->dump.end = hdev->dump.head + size; > + > + hci_devcoredump_update_state(hdev, HCI_DEVCOREDUMP_IDLE); > + > + return 0; > +} > + > +/* Call with hci_dev_lock only. */ > +static bool hci_devcoredump_copy(struct hci_dev *hdev, char *buf, u32 size) > +{ > + if (hdev->dump.tail + size > hdev->dump.end) > + return false; > + > + memcpy(hdev->dump.tail, buf, size); > + hdev->dump.tail += size; > + > + return true; > +} > + > +/* Call with hci_dev_lock only. */ > +static bool hci_devcoredump_memset(struct hci_dev *hdev, u8 pattern, u32 len) > +{ > + if (hdev->dump.tail + len > hdev->dump.end) > + return false; > + > + memset(hdev->dump.tail, pattern, len); > + hdev->dump.tail += len; > + > + return true; > +} > + > +/* Call with hci_dev_lock only. */ > +static int hci_devcoredump_prepare(struct hci_dev *hdev, u32 dump_size) > +{ > + char *dump_hdr; > + int dump_hdr_size; > + u32 size; > + int err = 0; > + > + dump_hdr = vmalloc(MAX_DEVCOREDUMP_HDR_SIZE); > + if (!dump_hdr) { > + err = -ENOMEM; > + goto hdr_free; > + } > + > + dump_hdr_size = hci_devcoredump_mkheader(hdev, dump_hdr, > + MAX_DEVCOREDUMP_HDR_SIZE); > + size = dump_hdr_size + dump_size; > + > + if (hci_devcoredump_alloc(hdev, size)) { > + err = -ENOMEM; > + goto hdr_free; > + } > + > + /* Insert the device header */ > + if (!hci_devcoredump_copy(hdev, dump_hdr, dump_hdr_size)) { > + bt_dev_err(hdev, "Failed to insert header"); > + hci_devcoredump_free(hdev); > + > + err = -ENOMEM; > + goto hdr_free; > + } > + > +hdr_free: > + if (dump_hdr) > + vfree(dump_hdr); > + > + return err; > +} > + > +/* Bluetooth devcoredump state machine. > + * > + * Devcoredump states: > + * > + * HCI_DEVCOREDUMP_IDLE: The default state. > + * > + * HCI_DEVCOREDUMP_ACTIVE: A devcoredump will be in this state once it has > + * been initialized using hci_devcoredump_init(). Once active, the > + * driver can append data using hci_devcoredump_append() or insert > + * a pattern using hci_devcoredump_append_pattern(). > + * > + * HCI_DEVCOREDUMP_DONE: Once the dump collection is complete, the drive > + * can signal the completion using hci_devcoredump_complete(). A > + * devcoredump is generated indicating the completion event and > + * then the state machine is reset to the default state. > + * > + * HCI_DEVCOREDUMP_ABORT: The driver can cancel ongoing dump collection in > + * case of any error using hci_devcoredump_abort(). A devcoredump > + * is still generated with the available data indicating the abort > + * event and then the state machine is reset to the default state. > + * > + * HCI_DEVCOREDUMP_TIMEOUT: A timeout timer for HCI_DEVCOREDUMP_TIMEOUT sec > + * is started during devcoredump initialization. Once the timeout > + * occurs, the driver is notified, a devcoredump is generated with > + * the available data indicating the timeout event and then the > + * state machine is reset to the default state. > + * > + * The driver must register using hci_devcoredump_register() before using the > + * hci devcoredump APIs. > + */ > +void hci_devcoredump_rx(struct work_struct *work) > +{ > + struct hci_dev *hdev = container_of(work, struct hci_dev, dump.dump_rx); > + struct sk_buff *skb; > + struct hci_devcoredump_skb_pattern *pattern; > + u32 dump_size; > + int start_state; > + > +#define DBG_UNEXPECTED_STATE() \ > + bt_dev_dbg(hdev, \ > + "Unexpected packet (%d) for state (%d). ", \ > + hci_dmp_cb(skb)->pkt_type, hdev->dump.state) > + > + while ((skb = skb_dequeue(&hdev->dump.dump_q))) { > + hci_dev_lock(hdev); > + start_state = hdev->dump.state; > + > + switch (hci_dmp_cb(skb)->pkt_type) { > + case HCI_DEVCOREDUMP_PKT_INIT: > + if (hdev->dump.state != HCI_DEVCOREDUMP_IDLE) { > + DBG_UNEXPECTED_STATE(); > + goto loop_continue; > + } > + > + if (skb->len != sizeof(dump_size)) { > + bt_dev_dbg(hdev, "Invalid dump init pkt"); > + goto loop_continue; > + } > + > + dump_size = *((u32 *)skb->data); > + if (!dump_size) { > + bt_dev_err(hdev, "Zero size dump init pkt"); > + goto loop_continue; > + } > + > + if (hci_devcoredump_prepare(hdev, dump_size)) { > + bt_dev_err(hdev, "Failed to prepare for dump"); > + goto loop_continue; > + } > + > + hci_devcoredump_update_state(hdev, > + HCI_DEVCOREDUMP_ACTIVE); > + queue_delayed_work(hdev->workqueue, > + &hdev->dump.dump_timeout, > + DEVCOREDUMP_TIMEOUT); > + break; > + > + case HCI_DEVCOREDUMP_PKT_SKB: > + if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) { > + DBG_UNEXPECTED_STATE(); > + goto loop_continue; > + } > + > + if (!hci_devcoredump_copy(hdev, skb->data, skb->len)) > + bt_dev_dbg(hdev, "Failed to insert skb"); > + break; > + > + case HCI_DEVCOREDUMP_PKT_PATTERN: > + if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) { > + DBG_UNEXPECTED_STATE(); > + goto loop_continue; > + } > + > + if (skb->len != sizeof(*pattern)) { > + bt_dev_dbg(hdev, "Invalid pattern skb"); > + goto loop_continue; > + } > + > + pattern = (void *)skb->data; > + > + if (!hci_devcoredump_memset(hdev, pattern->pattern, > + pattern->len)) > + bt_dev_dbg(hdev, "Failed to set pattern"); > + break; > + > + case HCI_DEVCOREDUMP_PKT_COMPLETE: > + if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) { > + DBG_UNEXPECTED_STATE(); > + goto loop_continue; > + } > + > + hci_devcoredump_update_state(hdev, > + HCI_DEVCOREDUMP_DONE); > + dump_size = hdev->dump.tail - hdev->dump.head; > + > + bt_dev_info(hdev, > + "Devcoredump complete with size %u " > + "(expect %u)", > + dump_size, hdev->dump.alloc_size); > + > + dev_coredumpv(&hdev->dev, hdev->dump.head, dump_size, > + GFP_KERNEL); > + break; > + > + case HCI_DEVCOREDUMP_PKT_ABORT: > + if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) { > + DBG_UNEXPECTED_STATE(); > + goto loop_continue; > + } > + > + hci_devcoredump_update_state(hdev, > + HCI_DEVCOREDUMP_ABORT); > + dump_size = hdev->dump.tail - hdev->dump.head; > + > + bt_dev_info(hdev, > + "Devcoredump aborted with size %u " > + "(expect %u)", > + dump_size, hdev->dump.alloc_size); > + > + /* Emit a devcoredump with the available data */ > + dev_coredumpv(&hdev->dev, hdev->dump.head, dump_size, > + GFP_KERNEL); > + break; > + > + default: > + bt_dev_dbg(hdev, > + "Unknown packet (%d) for state (%d). ", > + hci_dmp_cb(skb)->pkt_type, hdev->dump.state); > + break; > + } > + > +loop_continue: > + kfree_skb(skb); > + hci_dev_unlock(hdev); > + > + if (start_state != hdev->dump.state) > + hci_devcoredump_notify(hdev, hdev->dump.state); > + > + hci_dev_lock(hdev); > + if (hdev->dump.state == HCI_DEVCOREDUMP_DONE || > + hdev->dump.state == HCI_DEVCOREDUMP_ABORT) > + hci_devcoredump_reset(hdev); > + hci_dev_unlock(hdev); > + } > +} > +EXPORT_SYMBOL(hci_devcoredump_rx); > + > +void hci_devcoredump_timeout(struct work_struct *work) > +{ > + struct hci_dev *hdev = container_of(work, struct hci_dev, > + dump.dump_timeout.work); > + u32 dump_size; > + > + hci_devcoredump_notify(hdev, HCI_DEVCOREDUMP_TIMEOUT); > + > + hci_dev_lock(hdev); > + > + cancel_work_sync(&hdev->dump.dump_rx); > + > + hci_devcoredump_update_state(hdev, HCI_DEVCOREDUMP_TIMEOUT); > + dump_size = hdev->dump.tail - hdev->dump.head; > + bt_dev_info(hdev, "Devcoredump timeout with size %u (expect %u)", > + dump_size, hdev->dump.alloc_size); > + > + /* Emit a devcoredump with the available data */ > + dev_coredumpv(&hdev->dev, hdev->dump.head, dump_size, GFP_KERNEL); > + > + hci_devcoredump_reset(hdev); > + > + hci_dev_unlock(hdev); > +} > +EXPORT_SYMBOL(hci_devcoredump_timeout); > + > +int hci_devcoredump_register(struct hci_dev *hdev, dmp_hdr_t dmp_hdr, > + notify_change_t notify_change) > +{ > + /* driver must implement dmp_hdr() function for bluetooth devcoredump */ > + if (!dmp_hdr) > + return -EINVAL; > + > + hci_dev_lock(hdev); > + hdev->dump.dmp_hdr = dmp_hdr; > + hdev->dump.notify_change = notify_change; > + hdev->dump.supported = true; > + hci_dev_unlock(hdev); > + > + return 0; > +} > +EXPORT_SYMBOL(hci_devcoredump_register); > + > +int hci_devcoredump_init(struct hci_dev *hdev, u32 dmp_size) > +{ > + struct sk_buff *skb = NULL; > + > + if (!hdev->dump.supported) > + return -EOPNOTSUPP; > + > + skb = alloc_skb(sizeof(dmp_size), GFP_ATOMIC); > + if (!skb) { > + bt_dev_err(hdev, "Failed to allocate devcoredump init"); > + return -ENOMEM; > + } > + > + hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_INIT; > + skb_put_data(skb, &dmp_size, sizeof(dmp_size)); > + > + skb_queue_tail(&hdev->dump.dump_q, skb); > + queue_work(hdev->workqueue, &hdev->dump.dump_rx); > + > + return 0; > +} > +EXPORT_SYMBOL(hci_devcoredump_init); > + > +int hci_devcoredump_append(struct hci_dev *hdev, struct sk_buff *skb) > +{ > + if (!skb) > + return -ENOMEM; > + > + if (!hdev->dump.supported) { > + kfree_skb(skb); > + return -EOPNOTSUPP; > + } > + > + hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_SKB; > + > + skb_queue_tail(&hdev->dump.dump_q, skb); > + queue_work(hdev->workqueue, &hdev->dump.dump_rx); > + > + return 0; > +} > +EXPORT_SYMBOL(hci_devcoredump_append); > + > +int hci_devcoredump_append_pattern(struct hci_dev *hdev, u8 pattern, u32 len) > +{ > + struct hci_devcoredump_skb_pattern p; > + struct sk_buff *skb = NULL; > + > + if (!hdev->dump.supported) > + return -EOPNOTSUPP; > + > + skb = alloc_skb(sizeof(p), GFP_ATOMIC); > + if (!skb) { > + bt_dev_err(hdev, "Failed to allocate devcoredump pattern"); > + return -ENOMEM; > + } > + > + p.pattern = pattern; > + p.len = len; > + > + hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_PATTERN; > + skb_put_data(skb, &p, sizeof(p)); > + > + skb_queue_tail(&hdev->dump.dump_q, skb); > + queue_work(hdev->workqueue, &hdev->dump.dump_rx); > + > + return 0; > +} > +EXPORT_SYMBOL(hci_devcoredump_append_pattern); > + > +int hci_devcoredump_complete(struct hci_dev *hdev) > +{ > + struct sk_buff *skb = NULL; > + > + if (!hdev->dump.supported) > + return -EOPNOTSUPP; > + > + skb = alloc_skb(0, GFP_ATOMIC); > + if (!skb) { > + bt_dev_err(hdev, "Failed to allocate devcoredump complete"); > + return -ENOMEM; > + } > + > + hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_COMPLETE; > + > + skb_queue_tail(&hdev->dump.dump_q, skb); > + queue_work(hdev->workqueue, &hdev->dump.dump_rx); > + > + return 0; > +} > +EXPORT_SYMBOL(hci_devcoredump_complete); > + > +int hci_devcoredump_abort(struct hci_dev *hdev) > +{ > + struct sk_buff *skb = NULL; > + > + if (!hdev->dump.supported) > + return -EOPNOTSUPP; > + > + skb = alloc_skb(0, GFP_ATOMIC); > + if (!skb) { > + bt_dev_err(hdev, "Failed to allocate devcoredump abort"); > + return -ENOMEM; > + } > + > + hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_ABORT; > + > + skb_queue_tail(&hdev->dump.dump_q, skb); > + queue_work(hdev->workqueue, &hdev->dump.dump_rx); > + > + return 0; > +} > +EXPORT_SYMBOL(hci_devcoredump_abort); > diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c > index 05c13f639b94..743c5bdb8daa 100644 > --- a/net/bluetooth/hci_core.c > +++ b/net/bluetooth/hci_core.c > @@ -2516,14 +2516,23 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv) > INIT_WORK(&hdev->tx_work, hci_tx_work); > INIT_WORK(&hdev->power_on, hci_power_on); > INIT_WORK(&hdev->error_reset, hci_error_reset); > +#ifdef CONFIG_DEV_COREDUMP > + INIT_WORK(&hdev->dump.dump_rx, hci_devcoredump_rx); > +#endif > > hci_cmd_sync_init(hdev); > > INIT_DELAYED_WORK(&hdev->power_off, hci_power_off); > +#ifdef CONFIG_DEV_COREDUMP > + INIT_DELAYED_WORK(&hdev->dump.dump_timeout, hci_devcoredump_timeout); > +#endif > > skb_queue_head_init(&hdev->rx_q); > skb_queue_head_init(&hdev->cmd_q); > skb_queue_head_init(&hdev->raw_q); > +#ifdef CONFIG_DEV_COREDUMP > + skb_queue_head_init(&hdev->dump.dump_q); > +#endif > > init_waitqueue_head(&hdev->req_wait_q); > > diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c > index e5602e209b63..a3d049b55b70 100644 > --- a/net/bluetooth/hci_sync.c > +++ b/net/bluetooth/hci_sync.c > @@ -3924,6 +3924,8 @@ int hci_dev_open_sync(struct hci_dev *hdev) > goto done; > } > > + hci_devcoredump_reset(hdev); > + > set_bit(HCI_RUNNING, &hdev->flags); > hci_sock_dev_event(hdev, HCI_DEV_OPEN); > > -- > 2.37.0.rc0.104.g0611611a94-goog >
Hi Manish, On Thu, Jul 7, 2022 at 3:29 PM Manish Mandlik <mmandlik@google.com> wrote: > > Hi Luiz, > > > On Wed, Jun 29, 2022 at 5:07 PM Luiz Augusto von Dentz <luiz.dentz@gmail.com> wrote: >> >> Hi Manish, >> >> On Thu, Jun 23, 2022 at 12:38 PM Manish Mandlik <mmandlik@google.com> wrote: >> > >> > From: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> >> > >> > Add devcoredump APIs to hci core so that drivers only have to provide >> > the dump skbs instead of managing the synchronization and timeouts. >> > >> > The devcoredump APIs should be used in the following manner: >> > - hci_devcoredump_init is called to allocate the dump. >> > - hci_devcoredump_append is called to append any skbs with dump data >> > OR hci_devcoredump_append_pattern is called to insert a pattern. >> > - hci_devcoredump_complete is called when all dump packets have been >> > sent OR hci_devcoredump_abort is called to indicate an error and >> > cancel an ongoing dump collection. >> > >> > The high level APIs just prepare some skbs with the appropriate data and >> > queue it for the dump to process. Packets part of the crashdump can be >> > intercepted in the driver in interrupt context and forwarded directly to >> > the devcoredump APIs. >> > >> > Internally, there are 5 states for the dump: idle, active, complete, >> > abort and timeout. A devcoredump will only be in active state after it >> > has been initialized. Once active, it accepts data to be appended, >> > patterns to be inserted (i.e. memset) and a completion event or an abort >> > event to generate a devcoredump. The timeout is initialized at the same >> > time the dump is initialized (defaulting to 10s) and will be cleared >> > either when the timeout occurs or the dump is complete or aborted. >> > >> > Signed-off-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> >> > Signed-off-by: Manish Mandlik <mmandlik@google.com> >> > Reviewed-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> >> > --- >> > >> > Changes in v2: >> > - Move hci devcoredump implementation to new files >> > - Move dump queue and dump work to hci_devcoredump struct >> > - Add CONFIG_DEV_COREDUMP conditional compile >> >> Looks like you didn't add an experimental UUID for enabling it, it >> should be per index and we mark as supported when the driver supports >> it then userspace can mark it to be used via main.conf so we can >> properly experiment with it before marking it as stable. > > We want to keep bluetooth devcoredump implementation to kernel only and not have any dependency on the userspace bluez. But I agree that we should not have it enabled by default without experimenting. So, I have added another attribute to enable/disable hci devcoredump and the default state is set to disabled. This can be enabled via bluetooth sysfs entry `enable_coredump`. I have sent a v3 series with this new patch to add a sysfs entry. Please take a look. I was anyway working on a mechanism to enable/disable devcoredump and I feel this could be the right way. Please let me know your thoughts. Thanks! Not sure I understand about not depending on userspace BlueZ, I mean we might need a userspace interface to enable it or are you suggesting to not use the MGMT interface for enabling it? If there is a is a generic method for enabling devcoredumps then yes that is probably preferable otherwise if is bluetooth specific that is what MGMT interface is for. > >> >> > >> > include/net/bluetooth/coredump.h | 109 +++++++ >> > include/net/bluetooth/hci_core.h | 5 + >> > net/bluetooth/Makefile | 2 + >> > net/bluetooth/coredump.c | 504 +++++++++++++++++++++++++++++++ >> > net/bluetooth/hci_core.c | 9 + >> > net/bluetooth/hci_sync.c | 2 + >> > 6 files changed, 631 insertions(+) >> > create mode 100644 include/net/bluetooth/coredump.h >> > create mode 100644 net/bluetooth/coredump.c >> > >> > diff --git a/include/net/bluetooth/coredump.h b/include/net/bluetooth/coredump.h >> > new file mode 100644 >> > index 000000000000..73601c409c6e >> > --- /dev/null >> > +++ b/include/net/bluetooth/coredump.h >> > @@ -0,0 +1,109 @@ >> > +// SPDX-License-Identifier: GPL-2.0-only >> > +/* >> > + * Copyright (C) 2022 Google Corporation >> > + */ >> > + >> > +#ifndef __COREDUMP_H >> > +#define __COREDUMP_H >> > + >> > +#define DEVCOREDUMP_TIMEOUT msecs_to_jiffies(10000) /* 10 sec */ >> > + >> > +typedef int (*dmp_hdr_t)(struct hci_dev *hdev, char *buf, size_t size); >> > +typedef void (*notify_change_t)(struct hci_dev *hdev, int state); >> > + >> > +/* struct hci_devcoredump - Devcoredump state >> > + * >> > + * @supported: Indicates if FW dump collection is supported by driver >> > + * @state: Current state of dump collection >> > + * @alloc_size: Total size of the dump >> > + * @head: Start of the dump >> > + * @tail: Pointer to current end of dump >> > + * @end: head + alloc_size for easy comparisons >> > + * >> > + * @dump_q: Dump queue for state machine to process >> > + * @dump_rx: Devcoredump state machine work >> > + * @dump_timeout: Devcoredump timeout work >> > + * >> > + * @dmp_hdr: Create a dump header to identify controller/fw/driver info >> > + * @notify_change: Notify driver when devcoredump state has changed >> > + */ >> > +struct hci_devcoredump { >> > + bool supported; >> > + >> > + enum devcoredump_state { >> > + HCI_DEVCOREDUMP_IDLE, >> > + HCI_DEVCOREDUMP_ACTIVE, >> > + HCI_DEVCOREDUMP_DONE, >> > + HCI_DEVCOREDUMP_ABORT, >> > + HCI_DEVCOREDUMP_TIMEOUT >> > + } state; >> > + >> > + u32 alloc_size; >> > + char *head; >> > + char *tail; >> > + char *end; >> > + >> > + struct sk_buff_head dump_q; >> > + struct work_struct dump_rx; >> > + struct delayed_work dump_timeout; >> > + >> > + dmp_hdr_t dmp_hdr; >> > + notify_change_t notify_change; >> > +}; >> > + >> > +#ifdef CONFIG_DEV_COREDUMP >> > + >> > +void hci_devcoredump_reset(struct hci_dev *hdev); >> > +void hci_devcoredump_rx(struct work_struct *work); >> > +void hci_devcoredump_timeout(struct work_struct *work); >> > +int hci_devcoredump_register(struct hci_dev *hdev, dmp_hdr_t dmp_hdr, >> > + notify_change_t notify_change); >> > +int hci_devcoredump_init(struct hci_dev *hdev, u32 dmp_size); >> > +int hci_devcoredump_append(struct hci_dev *hdev, struct sk_buff *skb); >> > +int hci_devcoredump_append_pattern(struct hci_dev *hdev, u8 pattern, u32 len); >> > +int hci_devcoredump_complete(struct hci_dev *hdev); >> > +int hci_devcoredump_abort(struct hci_dev *hdev); >> > + >> > +#else >> > + >> > +static inline void hci_devcoredump_reset(struct hci_dev *hdev) {} >> > +static inline void hci_devcoredump_rx(struct work_struct *work) {} >> > +static inline void hci_devcoredump_timeout(struct work_struct *work) {} >> > + >> > +static inline int hci_devcoredump_register(struct hci_dev *hdev, >> > + dmp_hdr_t dmp_hdr, >> > + notify_change_t notify_change) >> > +{ >> > + return -EOPNOTSUPP; >> > +} >> > + >> > +static inline int hci_devcoredump_init(struct hci_dev *hdev, u32 dmp_size) >> > +{ >> > + return -EOPNOTSUPP; >> > +} >> > + >> > +static inline int hci_devcoredump_append(struct hci_dev *hdev, >> > + struct sk_buff *skb) >> > +{ >> > + return -EOPNOTSUPP; >> > +} >> > + >> > +static inline int hci_devcoredump_append_pattern(struct hci_dev *hdev, >> > + u8 pattern, u32 len) >> > +{ >> > + return -EOPNOTSUPP; >> > +} >> > + >> > +static inline int hci_devcoredump_complete(struct hci_dev *hdev) >> > +{ >> > + return -EOPNOTSUPP; >> > +} >> > + >> > +static inline int hci_devcoredump_abort(struct hci_dev *hdev) >> > +{ >> > + return -EOPNOTSUPP; >> > +} >> > + >> > +#endif /* CONFIG_DEV_COREDUMP */ >> > + >> > +#endif /* __COREDUMP_H */ >> > diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h >> > index 15237ee5f761..9ccc034c8fde 100644 >> > --- a/include/net/bluetooth/hci_core.h >> > +++ b/include/net/bluetooth/hci_core.h >> > @@ -32,6 +32,7 @@ >> > #include <net/bluetooth/hci.h> >> > #include <net/bluetooth/hci_sync.h> >> > #include <net/bluetooth/hci_sock.h> >> > +#include <net/bluetooth/coredump.h> >> > >> > /* HCI priority */ >> > #define HCI_PRIO_MAX 7 >> > @@ -582,6 +583,10 @@ struct hci_dev { >> > const char *fw_info; >> > struct dentry *debugfs; >> > >> > +#ifdef CONFIG_DEV_COREDUMP >> > + struct hci_devcoredump dump; >> > +#endif >> > + >> > struct device dev; >> > >> > struct rfkill *rfkill; >> > diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile >> > index a52bba8500e1..4e894e452869 100644 >> > --- a/net/bluetooth/Makefile >> > +++ b/net/bluetooth/Makefile >> > @@ -17,6 +17,8 @@ bluetooth-y := af_bluetooth.o hci_core.o hci_conn.o hci_event.o mgmt.o \ >> > ecdh_helper.o hci_request.o mgmt_util.o mgmt_config.o hci_codec.o \ >> > eir.o hci_sync.o >> > >> > +bluetooth-$(CONFIG_DEV_COREDUMP) += coredump.o >> > + >> > bluetooth-$(CONFIG_BT_BREDR) += sco.o >> > bluetooth-$(CONFIG_BT_HS) += a2mp.o amp.o >> > bluetooth-$(CONFIG_BT_LEDS) += leds.o >> > diff --git a/net/bluetooth/coredump.c b/net/bluetooth/coredump.c >> > new file mode 100644 >> > index 000000000000..43c355cd7ad3 >> > --- /dev/null >> > +++ b/net/bluetooth/coredump.c >> > @@ -0,0 +1,504 @@ >> > +// SPDX-License-Identifier: GPL-2.0-only >> > +/* >> > + * Copyright (C) 2022 Google Corporation >> > + */ >> > + >> > +#include <linux/devcoredump.h> >> > + >> > +#include <net/bluetooth/bluetooth.h> >> > +#include <net/bluetooth/hci_core.h> >> > + >> > +enum hci_devcoredump_pkt_type { >> > + HCI_DEVCOREDUMP_PKT_INIT, >> > + HCI_DEVCOREDUMP_PKT_SKB, >> > + HCI_DEVCOREDUMP_PKT_PATTERN, >> > + HCI_DEVCOREDUMP_PKT_COMPLETE, >> > + HCI_DEVCOREDUMP_PKT_ABORT, >> > +}; >> > + >> > +struct hci_devcoredump_skb_cb { >> > + u16 pkt_type; >> > +}; >> > + >> > +struct hci_devcoredump_skb_pattern { >> > + u8 pattern; >> > + u32 len; >> > +} __packed; >> > + >> > +#define hci_dmp_cb(skb) ((struct hci_devcoredump_skb_cb *)((skb)->cb)) >> > + >> > +#define MAX_DEVCOREDUMP_HDR_SIZE 512 /* bytes */ >> > + >> > +static int hci_devcoredump_update_hdr_state(char *buf, size_t size, int state) >> > +{ >> > + if (!buf) >> > + return 0; >> > + >> > + return snprintf(buf, size, "Bluetooth devcoredump\nState: %d\n", state); >> > +} >> > + >> > +/* Call with hci_dev_lock only. */ >> > +static int hci_devcoredump_update_state(struct hci_dev *hdev, int state) >> > +{ >> > + hdev->dump.state = state; >> > + >> > + return hci_devcoredump_update_hdr_state(hdev->dump.head, >> > + hdev->dump.alloc_size, state); >> > +} >> > + >> > +static int hci_devcoredump_mkheader(struct hci_dev *hdev, char *buf, >> > + size_t buf_size) >> > +{ >> > + char *ptr = buf; >> > + size_t rem = buf_size; >> > + size_t read = 0; >> > + >> > + read = hci_devcoredump_update_hdr_state(ptr, rem, HCI_DEVCOREDUMP_IDLE); >> > + read += 1; /* update_hdr_state adds \0 at the end upon state rewrite */ >> > + rem -= read; >> > + ptr += read; >> > + >> > + if (hdev->dump.dmp_hdr) { >> > + /* dmp_hdr() should return number of bytes written */ >> > + read = hdev->dump.dmp_hdr(hdev, ptr, rem); >> > + rem -= read; >> > + ptr += read; >> > + } >> > + >> > + read = snprintf(ptr, rem, "--- Start dump ---\n"); >> > + rem -= read; >> > + ptr += read; >> > + >> > + return buf_size - rem; >> > +} >> > + >> > +/* Do not call with hci_dev_lock since this calls driver code. */ >> > +static void hci_devcoredump_notify(struct hci_dev *hdev, int state) >> > +{ >> > + if (hdev->dump.notify_change) >> > + hdev->dump.notify_change(hdev, state); >> > +} >> > + >> > +/* Call with hci_dev_lock only. */ >> > +void hci_devcoredump_reset(struct hci_dev *hdev) >> > +{ >> > + hdev->dump.head = NULL; >> > + hdev->dump.tail = NULL; >> > + hdev->dump.alloc_size = 0; >> > + >> > + hci_devcoredump_update_state(hdev, HCI_DEVCOREDUMP_IDLE); >> > + >> > + cancel_delayed_work(&hdev->dump.dump_timeout); >> > + skb_queue_purge(&hdev->dump.dump_q); >> > +} >> > + >> > +/* Call with hci_dev_lock only. */ >> > +static void hci_devcoredump_free(struct hci_dev *hdev) >> > +{ >> > + if (hdev->dump.head) >> > + vfree(hdev->dump.head); >> > + >> > + hci_devcoredump_reset(hdev); >> > +} >> > + >> > +/* Call with hci_dev_lock only. */ >> > +static int hci_devcoredump_alloc(struct hci_dev *hdev, u32 size) >> > +{ >> > + hdev->dump.head = vmalloc(size); >> > + if (!hdev->dump.head) >> > + return -ENOMEM; >> > + >> > + hdev->dump.alloc_size = size; >> > + hdev->dump.tail = hdev->dump.head; >> > + hdev->dump.end = hdev->dump.head + size; >> > + >> > + hci_devcoredump_update_state(hdev, HCI_DEVCOREDUMP_IDLE); >> > + >> > + return 0; >> > +} >> > + >> > +/* Call with hci_dev_lock only. */ >> > +static bool hci_devcoredump_copy(struct hci_dev *hdev, char *buf, u32 size) >> > +{ >> > + if (hdev->dump.tail + size > hdev->dump.end) >> > + return false; >> > + >> > + memcpy(hdev->dump.tail, buf, size); >> > + hdev->dump.tail += size; >> > + >> > + return true; >> > +} >> > + >> > +/* Call with hci_dev_lock only. */ >> > +static bool hci_devcoredump_memset(struct hci_dev *hdev, u8 pattern, u32 len) >> > +{ >> > + if (hdev->dump.tail + len > hdev->dump.end) >> > + return false; >> > + >> > + memset(hdev->dump.tail, pattern, len); >> > + hdev->dump.tail += len; >> > + >> > + return true; >> > +} >> > + >> > +/* Call with hci_dev_lock only. */ >> > +static int hci_devcoredump_prepare(struct hci_dev *hdev, u32 dump_size) >> > +{ >> > + char *dump_hdr; >> > + int dump_hdr_size; >> > + u32 size; >> > + int err = 0; >> > + >> > + dump_hdr = vmalloc(MAX_DEVCOREDUMP_HDR_SIZE); >> > + if (!dump_hdr) { >> > + err = -ENOMEM; >> > + goto hdr_free; >> > + } >> > + >> > + dump_hdr_size = hci_devcoredump_mkheader(hdev, dump_hdr, >> > + MAX_DEVCOREDUMP_HDR_SIZE); >> > + size = dump_hdr_size + dump_size; >> > + >> > + if (hci_devcoredump_alloc(hdev, size)) { >> > + err = -ENOMEM; >> > + goto hdr_free; >> > + } >> > + >> > + /* Insert the device header */ >> > + if (!hci_devcoredump_copy(hdev, dump_hdr, dump_hdr_size)) { >> > + bt_dev_err(hdev, "Failed to insert header"); >> > + hci_devcoredump_free(hdev); >> > + >> > + err = -ENOMEM; >> > + goto hdr_free; >> > + } >> > + >> > +hdr_free: >> > + if (dump_hdr) >> > + vfree(dump_hdr); >> > + >> > + return err; >> > +} >> > + >> > +/* Bluetooth devcoredump state machine. >> > + * >> > + * Devcoredump states: >> > + * >> > + * HCI_DEVCOREDUMP_IDLE: The default state. >> > + * >> > + * HCI_DEVCOREDUMP_ACTIVE: A devcoredump will be in this state once it has >> > + * been initialized using hci_devcoredump_init(). Once active, the >> > + * driver can append data using hci_devcoredump_append() or insert >> > + * a pattern using hci_devcoredump_append_pattern(). >> > + * >> > + * HCI_DEVCOREDUMP_DONE: Once the dump collection is complete, the drive >> > + * can signal the completion using hci_devcoredump_complete(). A >> > + * devcoredump is generated indicating the completion event and >> > + * then the state machine is reset to the default state. >> > + * >> > + * HCI_DEVCOREDUMP_ABORT: The driver can cancel ongoing dump collection in >> > + * case of any error using hci_devcoredump_abort(). A devcoredump >> > + * is still generated with the available data indicating the abort >> > + * event and then the state machine is reset to the default state. >> > + * >> > + * HCI_DEVCOREDUMP_TIMEOUT: A timeout timer for HCI_DEVCOREDUMP_TIMEOUT sec >> > + * is started during devcoredump initialization. Once the timeout >> > + * occurs, the driver is notified, a devcoredump is generated with >> > + * the available data indicating the timeout event and then the >> > + * state machine is reset to the default state. >> > + * >> > + * The driver must register using hci_devcoredump_register() before using the >> > + * hci devcoredump APIs. >> > + */ >> > +void hci_devcoredump_rx(struct work_struct *work) >> > +{ >> > + struct hci_dev *hdev = container_of(work, struct hci_dev, dump.dump_rx); >> > + struct sk_buff *skb; >> > + struct hci_devcoredump_skb_pattern *pattern; >> > + u32 dump_size; >> > + int start_state; >> > + >> > +#define DBG_UNEXPECTED_STATE() \ >> > + bt_dev_dbg(hdev, \ >> > + "Unexpected packet (%d) for state (%d). ", \ >> > + hci_dmp_cb(skb)->pkt_type, hdev->dump.state) >> > + >> > + while ((skb = skb_dequeue(&hdev->dump.dump_q))) { >> > + hci_dev_lock(hdev); >> > + start_state = hdev->dump.state; >> > + >> > + switch (hci_dmp_cb(skb)->pkt_type) { >> > + case HCI_DEVCOREDUMP_PKT_INIT: >> > + if (hdev->dump.state != HCI_DEVCOREDUMP_IDLE) { >> > + DBG_UNEXPECTED_STATE(); >> > + goto loop_continue; >> > + } >> > + >> > + if (skb->len != sizeof(dump_size)) { >> > + bt_dev_dbg(hdev, "Invalid dump init pkt"); >> > + goto loop_continue; >> > + } >> > + >> > + dump_size = *((u32 *)skb->data); >> > + if (!dump_size) { >> > + bt_dev_err(hdev, "Zero size dump init pkt"); >> > + goto loop_continue; >> > + } >> > + >> > + if (hci_devcoredump_prepare(hdev, dump_size)) { >> > + bt_dev_err(hdev, "Failed to prepare for dump"); >> > + goto loop_continue; >> > + } >> > + >> > + hci_devcoredump_update_state(hdev, >> > + HCI_DEVCOREDUMP_ACTIVE); >> > + queue_delayed_work(hdev->workqueue, >> > + &hdev->dump.dump_timeout, >> > + DEVCOREDUMP_TIMEOUT); >> > + break; >> > + >> > + case HCI_DEVCOREDUMP_PKT_SKB: >> > + if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) { >> > + DBG_UNEXPECTED_STATE(); >> > + goto loop_continue; >> > + } >> > + >> > + if (!hci_devcoredump_copy(hdev, skb->data, skb->len)) >> > + bt_dev_dbg(hdev, "Failed to insert skb"); >> > + break; >> > + >> > + case HCI_DEVCOREDUMP_PKT_PATTERN: >> > + if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) { >> > + DBG_UNEXPECTED_STATE(); >> > + goto loop_continue; >> > + } >> > + >> > + if (skb->len != sizeof(*pattern)) { >> > + bt_dev_dbg(hdev, "Invalid pattern skb"); >> > + goto loop_continue; >> > + } >> > + >> > + pattern = (void *)skb->data; >> > + >> > + if (!hci_devcoredump_memset(hdev, pattern->pattern, >> > + pattern->len)) >> > + bt_dev_dbg(hdev, "Failed to set pattern"); >> > + break; >> > + >> > + case HCI_DEVCOREDUMP_PKT_COMPLETE: >> > + if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) { >> > + DBG_UNEXPECTED_STATE(); >> > + goto loop_continue; >> > + } >> > + >> > + hci_devcoredump_update_state(hdev, >> > + HCI_DEVCOREDUMP_DONE); >> > + dump_size = hdev->dump.tail - hdev->dump.head; >> > + >> > + bt_dev_info(hdev, >> > + "Devcoredump complete with size %u " >> > + "(expect %u)", >> > + dump_size, hdev->dump.alloc_size); >> > + >> > + dev_coredumpv(&hdev->dev, hdev->dump.head, dump_size, >> > + GFP_KERNEL); >> > + break; >> > + >> > + case HCI_DEVCOREDUMP_PKT_ABORT: >> > + if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) { >> > + DBG_UNEXPECTED_STATE(); >> > + goto loop_continue; >> > + } >> > + >> > + hci_devcoredump_update_state(hdev, >> > + HCI_DEVCOREDUMP_ABORT); >> > + dump_size = hdev->dump.tail - hdev->dump.head; >> > + >> > + bt_dev_info(hdev, >> > + "Devcoredump aborted with size %u " >> > + "(expect %u)", >> > + dump_size, hdev->dump.alloc_size); >> > + >> > + /* Emit a devcoredump with the available data */ >> > + dev_coredumpv(&hdev->dev, hdev->dump.head, dump_size, >> > + GFP_KERNEL); >> > + break; >> > + >> > + default: >> > + bt_dev_dbg(hdev, >> > + "Unknown packet (%d) for state (%d). ", >> > + hci_dmp_cb(skb)->pkt_type, hdev->dump.state); >> > + break; >> > + } >> > + >> > +loop_continue: >> > + kfree_skb(skb); >> > + hci_dev_unlock(hdev); >> > + >> > + if (start_state != hdev->dump.state) >> > + hci_devcoredump_notify(hdev, hdev->dump.state); >> > + >> > + hci_dev_lock(hdev); >> > + if (hdev->dump.state == HCI_DEVCOREDUMP_DONE || >> > + hdev->dump.state == HCI_DEVCOREDUMP_ABORT) >> > + hci_devcoredump_reset(hdev); >> > + hci_dev_unlock(hdev); >> > + } >> > +} >> > +EXPORT_SYMBOL(hci_devcoredump_rx); >> > + >> > +void hci_devcoredump_timeout(struct work_struct *work) >> > +{ >> > + struct hci_dev *hdev = container_of(work, struct hci_dev, >> > + dump.dump_timeout.work); >> > + u32 dump_size; >> > + >> > + hci_devcoredump_notify(hdev, HCI_DEVCOREDUMP_TIMEOUT); >> > + >> > + hci_dev_lock(hdev); >> > + >> > + cancel_work_sync(&hdev->dump.dump_rx); >> > + >> > + hci_devcoredump_update_state(hdev, HCI_DEVCOREDUMP_TIMEOUT); >> > + dump_size = hdev->dump.tail - hdev->dump.head; >> > + bt_dev_info(hdev, "Devcoredump timeout with size %u (expect %u)", >> > + dump_size, hdev->dump.alloc_size); >> > + >> > + /* Emit a devcoredump with the available data */ >> > + dev_coredumpv(&hdev->dev, hdev->dump.head, dump_size, GFP_KERNEL); >> > + >> > + hci_devcoredump_reset(hdev); >> > + >> > + hci_dev_unlock(hdev); >> > +} >> > +EXPORT_SYMBOL(hci_devcoredump_timeout); >> > + >> > +int hci_devcoredump_register(struct hci_dev *hdev, dmp_hdr_t dmp_hdr, >> > + notify_change_t notify_change) >> > +{ >> > + /* driver must implement dmp_hdr() function for bluetooth devcoredump */ >> > + if (!dmp_hdr) >> > + return -EINVAL; >> > + >> > + hci_dev_lock(hdev); >> > + hdev->dump.dmp_hdr = dmp_hdr; >> > + hdev->dump.notify_change = notify_change; >> > + hdev->dump.supported = true; >> > + hci_dev_unlock(hdev); >> > + >> > + return 0; >> > +} >> > +EXPORT_SYMBOL(hci_devcoredump_register); >> > + >> > +int hci_devcoredump_init(struct hci_dev *hdev, u32 dmp_size) >> > +{ >> > + struct sk_buff *skb = NULL; >> > + >> > + if (!hdev->dump.supported) >> > + return -EOPNOTSUPP; >> > + >> > + skb = alloc_skb(sizeof(dmp_size), GFP_ATOMIC); >> > + if (!skb) { >> > + bt_dev_err(hdev, "Failed to allocate devcoredump init"); >> > + return -ENOMEM; >> > + } >> > + >> > + hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_INIT; >> > + skb_put_data(skb, &dmp_size, sizeof(dmp_size)); >> > + >> > + skb_queue_tail(&hdev->dump.dump_q, skb); >> > + queue_work(hdev->workqueue, &hdev->dump.dump_rx); >> > + >> > + return 0; >> > +} >> > +EXPORT_SYMBOL(hci_devcoredump_init); >> > + >> > +int hci_devcoredump_append(struct hci_dev *hdev, struct sk_buff *skb) >> > +{ >> > + if (!skb) >> > + return -ENOMEM; >> > + >> > + if (!hdev->dump.supported) { >> > + kfree_skb(skb); >> > + return -EOPNOTSUPP; >> > + } >> > + >> > + hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_SKB; >> > + >> > + skb_queue_tail(&hdev->dump.dump_q, skb); >> > + queue_work(hdev->workqueue, &hdev->dump.dump_rx); >> > + >> > + return 0; >> > +} >> > +EXPORT_SYMBOL(hci_devcoredump_append); >> > + >> > +int hci_devcoredump_append_pattern(struct hci_dev *hdev, u8 pattern, u32 len) >> > +{ >> > + struct hci_devcoredump_skb_pattern p; >> > + struct sk_buff *skb = NULL; >> > + >> > + if (!hdev->dump.supported) >> > + return -EOPNOTSUPP; >> > + >> > + skb = alloc_skb(sizeof(p), GFP_ATOMIC); >> > + if (!skb) { >> > + bt_dev_err(hdev, "Failed to allocate devcoredump pattern"); >> > + return -ENOMEM; >> > + } >> > + >> > + p.pattern = pattern; >> > + p.len = len; >> > + >> > + hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_PATTERN; >> > + skb_put_data(skb, &p, sizeof(p)); >> > + >> > + skb_queue_tail(&hdev->dump.dump_q, skb); >> > + queue_work(hdev->workqueue, &hdev->dump.dump_rx); >> > + >> > + return 0; >> > +} >> > +EXPORT_SYMBOL(hci_devcoredump_append_pattern); >> > + >> > +int hci_devcoredump_complete(struct hci_dev *hdev) >> > +{ >> > + struct sk_buff *skb = NULL; >> > + >> > + if (!hdev->dump.supported) >> > + return -EOPNOTSUPP; >> > + >> > + skb = alloc_skb(0, GFP_ATOMIC); >> > + if (!skb) { >> > + bt_dev_err(hdev, "Failed to allocate devcoredump complete"); >> > + return -ENOMEM; >> > + } >> > + >> > + hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_COMPLETE; >> > + >> > + skb_queue_tail(&hdev->dump.dump_q, skb); >> > + queue_work(hdev->workqueue, &hdev->dump.dump_rx); >> > + >> > + return 0; >> > +} >> > +EXPORT_SYMBOL(hci_devcoredump_complete); >> > + >> > +int hci_devcoredump_abort(struct hci_dev *hdev) >> > +{ >> > + struct sk_buff *skb = NULL; >> > + >> > + if (!hdev->dump.supported) >> > + return -EOPNOTSUPP; >> > + >> > + skb = alloc_skb(0, GFP_ATOMIC); >> > + if (!skb) { >> > + bt_dev_err(hdev, "Failed to allocate devcoredump abort"); >> > + return -ENOMEM; >> > + } >> > + >> > + hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_ABORT; >> > + >> > + skb_queue_tail(&hdev->dump.dump_q, skb); >> > + queue_work(hdev->workqueue, &hdev->dump.dump_rx); >> > + >> > + return 0; >> > +} >> > +EXPORT_SYMBOL(hci_devcoredump_abort); >> > diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c >> > index 05c13f639b94..743c5bdb8daa 100644 >> > --- a/net/bluetooth/hci_core.c >> > +++ b/net/bluetooth/hci_core.c >> > @@ -2516,14 +2516,23 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv) >> > INIT_WORK(&hdev->tx_work, hci_tx_work); >> > INIT_WORK(&hdev->power_on, hci_power_on); >> > INIT_WORK(&hdev->error_reset, hci_error_reset); >> > +#ifdef CONFIG_DEV_COREDUMP >> > + INIT_WORK(&hdev->dump.dump_rx, hci_devcoredump_rx); >> > +#endif >> > >> > hci_cmd_sync_init(hdev); >> > >> > INIT_DELAYED_WORK(&hdev->power_off, hci_power_off); >> > +#ifdef CONFIG_DEV_COREDUMP >> > + INIT_DELAYED_WORK(&hdev->dump.dump_timeout, hci_devcoredump_timeout); >> > +#endif >> > >> > skb_queue_head_init(&hdev->rx_q); >> > skb_queue_head_init(&hdev->cmd_q); >> > skb_queue_head_init(&hdev->raw_q); >> > +#ifdef CONFIG_DEV_COREDUMP >> > + skb_queue_head_init(&hdev->dump.dump_q); >> > +#endif >> > >> > init_waitqueue_head(&hdev->req_wait_q); >> > >> > diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c >> > index e5602e209b63..a3d049b55b70 100644 >> > --- a/net/bluetooth/hci_sync.c >> > +++ b/net/bluetooth/hci_sync.c >> > @@ -3924,6 +3924,8 @@ int hci_dev_open_sync(struct hci_dev *hdev) >> > goto done; >> > } >> > >> > + hci_devcoredump_reset(hdev); >> > + >> > set_bit(HCI_RUNNING, &hdev->flags); >> > hci_sock_dev_event(hdev, HCI_DEV_OPEN); >> > >> > -- >> > 2.37.0.rc0.104.g0611611a94-goog >> > >> >> >> -- >> Luiz Augusto von Dentz > > > Regards, > Manish.
diff --git a/include/net/bluetooth/coredump.h b/include/net/bluetooth/coredump.h new file mode 100644 index 000000000000..73601c409c6e --- /dev/null +++ b/include/net/bluetooth/coredump.h @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 Google Corporation + */ + +#ifndef __COREDUMP_H +#define __COREDUMP_H + +#define DEVCOREDUMP_TIMEOUT msecs_to_jiffies(10000) /* 10 sec */ + +typedef int (*dmp_hdr_t)(struct hci_dev *hdev, char *buf, size_t size); +typedef void (*notify_change_t)(struct hci_dev *hdev, int state); + +/* struct hci_devcoredump - Devcoredump state + * + * @supported: Indicates if FW dump collection is supported by driver + * @state: Current state of dump collection + * @alloc_size: Total size of the dump + * @head: Start of the dump + * @tail: Pointer to current end of dump + * @end: head + alloc_size for easy comparisons + * + * @dump_q: Dump queue for state machine to process + * @dump_rx: Devcoredump state machine work + * @dump_timeout: Devcoredump timeout work + * + * @dmp_hdr: Create a dump header to identify controller/fw/driver info + * @notify_change: Notify driver when devcoredump state has changed + */ +struct hci_devcoredump { + bool supported; + + enum devcoredump_state { + HCI_DEVCOREDUMP_IDLE, + HCI_DEVCOREDUMP_ACTIVE, + HCI_DEVCOREDUMP_DONE, + HCI_DEVCOREDUMP_ABORT, + HCI_DEVCOREDUMP_TIMEOUT + } state; + + u32 alloc_size; + char *head; + char *tail; + char *end; + + struct sk_buff_head dump_q; + struct work_struct dump_rx; + struct delayed_work dump_timeout; + + dmp_hdr_t dmp_hdr; + notify_change_t notify_change; +}; + +#ifdef CONFIG_DEV_COREDUMP + +void hci_devcoredump_reset(struct hci_dev *hdev); +void hci_devcoredump_rx(struct work_struct *work); +void hci_devcoredump_timeout(struct work_struct *work); +int hci_devcoredump_register(struct hci_dev *hdev, dmp_hdr_t dmp_hdr, + notify_change_t notify_change); +int hci_devcoredump_init(struct hci_dev *hdev, u32 dmp_size); +int hci_devcoredump_append(struct hci_dev *hdev, struct sk_buff *skb); +int hci_devcoredump_append_pattern(struct hci_dev *hdev, u8 pattern, u32 len); +int hci_devcoredump_complete(struct hci_dev *hdev); +int hci_devcoredump_abort(struct hci_dev *hdev); + +#else + +static inline void hci_devcoredump_reset(struct hci_dev *hdev) {} +static inline void hci_devcoredump_rx(struct work_struct *work) {} +static inline void hci_devcoredump_timeout(struct work_struct *work) {} + +static inline int hci_devcoredump_register(struct hci_dev *hdev, + dmp_hdr_t dmp_hdr, + notify_change_t notify_change) +{ + return -EOPNOTSUPP; +} + +static inline int hci_devcoredump_init(struct hci_dev *hdev, u32 dmp_size) +{ + return -EOPNOTSUPP; +} + +static inline int hci_devcoredump_append(struct hci_dev *hdev, + struct sk_buff *skb) +{ + return -EOPNOTSUPP; +} + +static inline int hci_devcoredump_append_pattern(struct hci_dev *hdev, + u8 pattern, u32 len) +{ + return -EOPNOTSUPP; +} + +static inline int hci_devcoredump_complete(struct hci_dev *hdev) +{ + return -EOPNOTSUPP; +} + +static inline int hci_devcoredump_abort(struct hci_dev *hdev) +{ + return -EOPNOTSUPP; +} + +#endif /* CONFIG_DEV_COREDUMP */ + +#endif /* __COREDUMP_H */ diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 15237ee5f761..9ccc034c8fde 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -32,6 +32,7 @@ #include <net/bluetooth/hci.h> #include <net/bluetooth/hci_sync.h> #include <net/bluetooth/hci_sock.h> +#include <net/bluetooth/coredump.h> /* HCI priority */ #define HCI_PRIO_MAX 7 @@ -582,6 +583,10 @@ struct hci_dev { const char *fw_info; struct dentry *debugfs; +#ifdef CONFIG_DEV_COREDUMP + struct hci_devcoredump dump; +#endif + struct device dev; struct rfkill *rfkill; diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile index a52bba8500e1..4e894e452869 100644 --- a/net/bluetooth/Makefile +++ b/net/bluetooth/Makefile @@ -17,6 +17,8 @@ bluetooth-y := af_bluetooth.o hci_core.o hci_conn.o hci_event.o mgmt.o \ ecdh_helper.o hci_request.o mgmt_util.o mgmt_config.o hci_codec.o \ eir.o hci_sync.o +bluetooth-$(CONFIG_DEV_COREDUMP) += coredump.o + bluetooth-$(CONFIG_BT_BREDR) += sco.o bluetooth-$(CONFIG_BT_HS) += a2mp.o amp.o bluetooth-$(CONFIG_BT_LEDS) += leds.o diff --git a/net/bluetooth/coredump.c b/net/bluetooth/coredump.c new file mode 100644 index 000000000000..43c355cd7ad3 --- /dev/null +++ b/net/bluetooth/coredump.c @@ -0,0 +1,504 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 Google Corporation + */ + +#include <linux/devcoredump.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +enum hci_devcoredump_pkt_type { + HCI_DEVCOREDUMP_PKT_INIT, + HCI_DEVCOREDUMP_PKT_SKB, + HCI_DEVCOREDUMP_PKT_PATTERN, + HCI_DEVCOREDUMP_PKT_COMPLETE, + HCI_DEVCOREDUMP_PKT_ABORT, +}; + +struct hci_devcoredump_skb_cb { + u16 pkt_type; +}; + +struct hci_devcoredump_skb_pattern { + u8 pattern; + u32 len; +} __packed; + +#define hci_dmp_cb(skb) ((struct hci_devcoredump_skb_cb *)((skb)->cb)) + +#define MAX_DEVCOREDUMP_HDR_SIZE 512 /* bytes */ + +static int hci_devcoredump_update_hdr_state(char *buf, size_t size, int state) +{ + if (!buf) + return 0; + + return snprintf(buf, size, "Bluetooth devcoredump\nState: %d\n", state); +} + +/* Call with hci_dev_lock only. */ +static int hci_devcoredump_update_state(struct hci_dev *hdev, int state) +{ + hdev->dump.state = state; + + return hci_devcoredump_update_hdr_state(hdev->dump.head, + hdev->dump.alloc_size, state); +} + +static int hci_devcoredump_mkheader(struct hci_dev *hdev, char *buf, + size_t buf_size) +{ + char *ptr = buf; + size_t rem = buf_size; + size_t read = 0; + + read = hci_devcoredump_update_hdr_state(ptr, rem, HCI_DEVCOREDUMP_IDLE); + read += 1; /* update_hdr_state adds \0 at the end upon state rewrite */ + rem -= read; + ptr += read; + + if (hdev->dump.dmp_hdr) { + /* dmp_hdr() should return number of bytes written */ + read = hdev->dump.dmp_hdr(hdev, ptr, rem); + rem -= read; + ptr += read; + } + + read = snprintf(ptr, rem, "--- Start dump ---\n"); + rem -= read; + ptr += read; + + return buf_size - rem; +} + +/* Do not call with hci_dev_lock since this calls driver code. */ +static void hci_devcoredump_notify(struct hci_dev *hdev, int state) +{ + if (hdev->dump.notify_change) + hdev->dump.notify_change(hdev, state); +} + +/* Call with hci_dev_lock only. */ +void hci_devcoredump_reset(struct hci_dev *hdev) +{ + hdev->dump.head = NULL; + hdev->dump.tail = NULL; + hdev->dump.alloc_size = 0; + + hci_devcoredump_update_state(hdev, HCI_DEVCOREDUMP_IDLE); + + cancel_delayed_work(&hdev->dump.dump_timeout); + skb_queue_purge(&hdev->dump.dump_q); +} + +/* Call with hci_dev_lock only. */ +static void hci_devcoredump_free(struct hci_dev *hdev) +{ + if (hdev->dump.head) + vfree(hdev->dump.head); + + hci_devcoredump_reset(hdev); +} + +/* Call with hci_dev_lock only. */ +static int hci_devcoredump_alloc(struct hci_dev *hdev, u32 size) +{ + hdev->dump.head = vmalloc(size); + if (!hdev->dump.head) + return -ENOMEM; + + hdev->dump.alloc_size = size; + hdev->dump.tail = hdev->dump.head; + hdev->dump.end = hdev->dump.head + size; + + hci_devcoredump_update_state(hdev, HCI_DEVCOREDUMP_IDLE); + + return 0; +} + +/* Call with hci_dev_lock only. */ +static bool hci_devcoredump_copy(struct hci_dev *hdev, char *buf, u32 size) +{ + if (hdev->dump.tail + size > hdev->dump.end) + return false; + + memcpy(hdev->dump.tail, buf, size); + hdev->dump.tail += size; + + return true; +} + +/* Call with hci_dev_lock only. */ +static bool hci_devcoredump_memset(struct hci_dev *hdev, u8 pattern, u32 len) +{ + if (hdev->dump.tail + len > hdev->dump.end) + return false; + + memset(hdev->dump.tail, pattern, len); + hdev->dump.tail += len; + + return true; +} + +/* Call with hci_dev_lock only. */ +static int hci_devcoredump_prepare(struct hci_dev *hdev, u32 dump_size) +{ + char *dump_hdr; + int dump_hdr_size; + u32 size; + int err = 0; + + dump_hdr = vmalloc(MAX_DEVCOREDUMP_HDR_SIZE); + if (!dump_hdr) { + err = -ENOMEM; + goto hdr_free; + } + + dump_hdr_size = hci_devcoredump_mkheader(hdev, dump_hdr, + MAX_DEVCOREDUMP_HDR_SIZE); + size = dump_hdr_size + dump_size; + + if (hci_devcoredump_alloc(hdev, size)) { + err = -ENOMEM; + goto hdr_free; + } + + /* Insert the device header */ + if (!hci_devcoredump_copy(hdev, dump_hdr, dump_hdr_size)) { + bt_dev_err(hdev, "Failed to insert header"); + hci_devcoredump_free(hdev); + + err = -ENOMEM; + goto hdr_free; + } + +hdr_free: + if (dump_hdr) + vfree(dump_hdr); + + return err; +} + +/* Bluetooth devcoredump state machine. + * + * Devcoredump states: + * + * HCI_DEVCOREDUMP_IDLE: The default state. + * + * HCI_DEVCOREDUMP_ACTIVE: A devcoredump will be in this state once it has + * been initialized using hci_devcoredump_init(). Once active, the + * driver can append data using hci_devcoredump_append() or insert + * a pattern using hci_devcoredump_append_pattern(). + * + * HCI_DEVCOREDUMP_DONE: Once the dump collection is complete, the drive + * can signal the completion using hci_devcoredump_complete(). A + * devcoredump is generated indicating the completion event and + * then the state machine is reset to the default state. + * + * HCI_DEVCOREDUMP_ABORT: The driver can cancel ongoing dump collection in + * case of any error using hci_devcoredump_abort(). A devcoredump + * is still generated with the available data indicating the abort + * event and then the state machine is reset to the default state. + * + * HCI_DEVCOREDUMP_TIMEOUT: A timeout timer for HCI_DEVCOREDUMP_TIMEOUT sec + * is started during devcoredump initialization. Once the timeout + * occurs, the driver is notified, a devcoredump is generated with + * the available data indicating the timeout event and then the + * state machine is reset to the default state. + * + * The driver must register using hci_devcoredump_register() before using the + * hci devcoredump APIs. + */ +void hci_devcoredump_rx(struct work_struct *work) +{ + struct hci_dev *hdev = container_of(work, struct hci_dev, dump.dump_rx); + struct sk_buff *skb; + struct hci_devcoredump_skb_pattern *pattern; + u32 dump_size; + int start_state; + +#define DBG_UNEXPECTED_STATE() \ + bt_dev_dbg(hdev, \ + "Unexpected packet (%d) for state (%d). ", \ + hci_dmp_cb(skb)->pkt_type, hdev->dump.state) + + while ((skb = skb_dequeue(&hdev->dump.dump_q))) { + hci_dev_lock(hdev); + start_state = hdev->dump.state; + + switch (hci_dmp_cb(skb)->pkt_type) { + case HCI_DEVCOREDUMP_PKT_INIT: + if (hdev->dump.state != HCI_DEVCOREDUMP_IDLE) { + DBG_UNEXPECTED_STATE(); + goto loop_continue; + } + + if (skb->len != sizeof(dump_size)) { + bt_dev_dbg(hdev, "Invalid dump init pkt"); + goto loop_continue; + } + + dump_size = *((u32 *)skb->data); + if (!dump_size) { + bt_dev_err(hdev, "Zero size dump init pkt"); + goto loop_continue; + } + + if (hci_devcoredump_prepare(hdev, dump_size)) { + bt_dev_err(hdev, "Failed to prepare for dump"); + goto loop_continue; + } + + hci_devcoredump_update_state(hdev, + HCI_DEVCOREDUMP_ACTIVE); + queue_delayed_work(hdev->workqueue, + &hdev->dump.dump_timeout, + DEVCOREDUMP_TIMEOUT); + break; + + case HCI_DEVCOREDUMP_PKT_SKB: + if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) { + DBG_UNEXPECTED_STATE(); + goto loop_continue; + } + + if (!hci_devcoredump_copy(hdev, skb->data, skb->len)) + bt_dev_dbg(hdev, "Failed to insert skb"); + break; + + case HCI_DEVCOREDUMP_PKT_PATTERN: + if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) { + DBG_UNEXPECTED_STATE(); + goto loop_continue; + } + + if (skb->len != sizeof(*pattern)) { + bt_dev_dbg(hdev, "Invalid pattern skb"); + goto loop_continue; + } + + pattern = (void *)skb->data; + + if (!hci_devcoredump_memset(hdev, pattern->pattern, + pattern->len)) + bt_dev_dbg(hdev, "Failed to set pattern"); + break; + + case HCI_DEVCOREDUMP_PKT_COMPLETE: + if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) { + DBG_UNEXPECTED_STATE(); + goto loop_continue; + } + + hci_devcoredump_update_state(hdev, + HCI_DEVCOREDUMP_DONE); + dump_size = hdev->dump.tail - hdev->dump.head; + + bt_dev_info(hdev, + "Devcoredump complete with size %u " + "(expect %u)", + dump_size, hdev->dump.alloc_size); + + dev_coredumpv(&hdev->dev, hdev->dump.head, dump_size, + GFP_KERNEL); + break; + + case HCI_DEVCOREDUMP_PKT_ABORT: + if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) { + DBG_UNEXPECTED_STATE(); + goto loop_continue; + } + + hci_devcoredump_update_state(hdev, + HCI_DEVCOREDUMP_ABORT); + dump_size = hdev->dump.tail - hdev->dump.head; + + bt_dev_info(hdev, + "Devcoredump aborted with size %u " + "(expect %u)", + dump_size, hdev->dump.alloc_size); + + /* Emit a devcoredump with the available data */ + dev_coredumpv(&hdev->dev, hdev->dump.head, dump_size, + GFP_KERNEL); + break; + + default: + bt_dev_dbg(hdev, + "Unknown packet (%d) for state (%d). ", + hci_dmp_cb(skb)->pkt_type, hdev->dump.state); + break; + } + +loop_continue: + kfree_skb(skb); + hci_dev_unlock(hdev); + + if (start_state != hdev->dump.state) + hci_devcoredump_notify(hdev, hdev->dump.state); + + hci_dev_lock(hdev); + if (hdev->dump.state == HCI_DEVCOREDUMP_DONE || + hdev->dump.state == HCI_DEVCOREDUMP_ABORT) + hci_devcoredump_reset(hdev); + hci_dev_unlock(hdev); + } +} +EXPORT_SYMBOL(hci_devcoredump_rx); + +void hci_devcoredump_timeout(struct work_struct *work) +{ + struct hci_dev *hdev = container_of(work, struct hci_dev, + dump.dump_timeout.work); + u32 dump_size; + + hci_devcoredump_notify(hdev, HCI_DEVCOREDUMP_TIMEOUT); + + hci_dev_lock(hdev); + + cancel_work_sync(&hdev->dump.dump_rx); + + hci_devcoredump_update_state(hdev, HCI_DEVCOREDUMP_TIMEOUT); + dump_size = hdev->dump.tail - hdev->dump.head; + bt_dev_info(hdev, "Devcoredump timeout with size %u (expect %u)", + dump_size, hdev->dump.alloc_size); + + /* Emit a devcoredump with the available data */ + dev_coredumpv(&hdev->dev, hdev->dump.head, dump_size, GFP_KERNEL); + + hci_devcoredump_reset(hdev); + + hci_dev_unlock(hdev); +} +EXPORT_SYMBOL(hci_devcoredump_timeout); + +int hci_devcoredump_register(struct hci_dev *hdev, dmp_hdr_t dmp_hdr, + notify_change_t notify_change) +{ + /* driver must implement dmp_hdr() function for bluetooth devcoredump */ + if (!dmp_hdr) + return -EINVAL; + + hci_dev_lock(hdev); + hdev->dump.dmp_hdr = dmp_hdr; + hdev->dump.notify_change = notify_change; + hdev->dump.supported = true; + hci_dev_unlock(hdev); + + return 0; +} +EXPORT_SYMBOL(hci_devcoredump_register); + +int hci_devcoredump_init(struct hci_dev *hdev, u32 dmp_size) +{ + struct sk_buff *skb = NULL; + + if (!hdev->dump.supported) + return -EOPNOTSUPP; + + skb = alloc_skb(sizeof(dmp_size), GFP_ATOMIC); + if (!skb) { + bt_dev_err(hdev, "Failed to allocate devcoredump init"); + return -ENOMEM; + } + + hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_INIT; + skb_put_data(skb, &dmp_size, sizeof(dmp_size)); + + skb_queue_tail(&hdev->dump.dump_q, skb); + queue_work(hdev->workqueue, &hdev->dump.dump_rx); + + return 0; +} +EXPORT_SYMBOL(hci_devcoredump_init); + +int hci_devcoredump_append(struct hci_dev *hdev, struct sk_buff *skb) +{ + if (!skb) + return -ENOMEM; + + if (!hdev->dump.supported) { + kfree_skb(skb); + return -EOPNOTSUPP; + } + + hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_SKB; + + skb_queue_tail(&hdev->dump.dump_q, skb); + queue_work(hdev->workqueue, &hdev->dump.dump_rx); + + return 0; +} +EXPORT_SYMBOL(hci_devcoredump_append); + +int hci_devcoredump_append_pattern(struct hci_dev *hdev, u8 pattern, u32 len) +{ + struct hci_devcoredump_skb_pattern p; + struct sk_buff *skb = NULL; + + if (!hdev->dump.supported) + return -EOPNOTSUPP; + + skb = alloc_skb(sizeof(p), GFP_ATOMIC); + if (!skb) { + bt_dev_err(hdev, "Failed to allocate devcoredump pattern"); + return -ENOMEM; + } + + p.pattern = pattern; + p.len = len; + + hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_PATTERN; + skb_put_data(skb, &p, sizeof(p)); + + skb_queue_tail(&hdev->dump.dump_q, skb); + queue_work(hdev->workqueue, &hdev->dump.dump_rx); + + return 0; +} +EXPORT_SYMBOL(hci_devcoredump_append_pattern); + +int hci_devcoredump_complete(struct hci_dev *hdev) +{ + struct sk_buff *skb = NULL; + + if (!hdev->dump.supported) + return -EOPNOTSUPP; + + skb = alloc_skb(0, GFP_ATOMIC); + if (!skb) { + bt_dev_err(hdev, "Failed to allocate devcoredump complete"); + return -ENOMEM; + } + + hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_COMPLETE; + + skb_queue_tail(&hdev->dump.dump_q, skb); + queue_work(hdev->workqueue, &hdev->dump.dump_rx); + + return 0; +} +EXPORT_SYMBOL(hci_devcoredump_complete); + +int hci_devcoredump_abort(struct hci_dev *hdev) +{ + struct sk_buff *skb = NULL; + + if (!hdev->dump.supported) + return -EOPNOTSUPP; + + skb = alloc_skb(0, GFP_ATOMIC); + if (!skb) { + bt_dev_err(hdev, "Failed to allocate devcoredump abort"); + return -ENOMEM; + } + + hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_ABORT; + + skb_queue_tail(&hdev->dump.dump_q, skb); + queue_work(hdev->workqueue, &hdev->dump.dump_rx); + + return 0; +} +EXPORT_SYMBOL(hci_devcoredump_abort); diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 05c13f639b94..743c5bdb8daa 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2516,14 +2516,23 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv) INIT_WORK(&hdev->tx_work, hci_tx_work); INIT_WORK(&hdev->power_on, hci_power_on); INIT_WORK(&hdev->error_reset, hci_error_reset); +#ifdef CONFIG_DEV_COREDUMP + INIT_WORK(&hdev->dump.dump_rx, hci_devcoredump_rx); +#endif hci_cmd_sync_init(hdev); INIT_DELAYED_WORK(&hdev->power_off, hci_power_off); +#ifdef CONFIG_DEV_COREDUMP + INIT_DELAYED_WORK(&hdev->dump.dump_timeout, hci_devcoredump_timeout); +#endif skb_queue_head_init(&hdev->rx_q); skb_queue_head_init(&hdev->cmd_q); skb_queue_head_init(&hdev->raw_q); +#ifdef CONFIG_DEV_COREDUMP + skb_queue_head_init(&hdev->dump.dump_q); +#endif init_waitqueue_head(&hdev->req_wait_q); diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index e5602e209b63..a3d049b55b70 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -3924,6 +3924,8 @@ int hci_dev_open_sync(struct hci_dev *hdev) goto done; } + hci_devcoredump_reset(hdev); + set_bit(HCI_RUNNING, &hdev->flags); hci_sock_dev_event(hdev, HCI_DEV_OPEN);