Re: [PATCH v11 1/4] Bluetooth: Add support for hci devcoredump

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Manish,

On Tue, Mar 28, 2023 at 9:42 AM Manish Mandlik <mmandlik@xxxxxxxxxx> wrote:
>
> From: Abhishek Pandit-Subedi <abhishekpandit@xxxxxxxxxxxx>
>
> 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@xxxxxxxxxxxx>
> Signed-off-by: Manish Mandlik <mmandlik@xxxxxxxxxx>
> Reviewed-by: Abhishek Pandit-Subedi <abhishekpandit@xxxxxxxxxxxx>
> ---
>
> Changes in v11:
> - Fix formatting/indentation
>
> Changes in v10:
> - Fix compiler warnings
>
> Changes in v9:
> - Use scnprintf instead of snprintf
> - Remove unnecessary out-of-memory logs
> - Add a function for each devcoredump state
> - Use skb_pull_data whenever possible
> - Rename hci_devcoredump_*() to hci_devcd_*()
>
> Changes in v8:
> - Update hci_devcoredump_mkheader() and .dmp_hdr() to use skb
>
> Changes in v6:
> - Remove #ifdef from .c and move to function in .h as per suggestion
> - Remove coredump_enabled from hci_devcoredump struct since the sysfs
>   flag related change has been abandoned
>
> Changes in v5:
> - No changes in v5
>
> Changes in v4:
> - Add .enabled() and .coredump() to hci_devcoredump struct
>
> Changes in v3:
> - Add attribute to enable/disable and set default state to disabled
>
> 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 | 112 +++++++
>  include/net/bluetooth/hci_core.h |  14 +
>  net/bluetooth/Makefile           |   2 +
>  net/bluetooth/coredump.c         | 525 +++++++++++++++++++++++++++++++
>  net/bluetooth/hci_core.c         |   1 +
>  net/bluetooth/hci_sync.c         |   2 +
>  6 files changed, 656 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..331e27e3f24c
> --- /dev/null
> +++ b/include/net/bluetooth/coredump.h
> @@ -0,0 +1,112 @@
> +/* 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 void (*coredump_t)(struct hci_dev *hdev);
> +typedef void (*dmp_hdr_t)(struct hci_dev *hdev, struct sk_buff *skb);
> +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
> + *
> + * @coredump: Called from the driver's .coredump() function.
> + * @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;
> +
> +       size_t          alloc_size;
> +       char            *head;
> +       char            *tail;
> +       char            *end;
> +
> +       struct sk_buff_head     dump_q;
> +       struct work_struct      dump_rx;
> +       struct delayed_work     dump_timeout;
> +
> +       coredump_t              coredump;
> +       dmp_hdr_t               dmp_hdr;
> +       notify_change_t         notify_change;
> +};
> +
> +#ifdef CONFIG_DEV_COREDUMP
> +
> +void hci_devcd_reset(struct hci_dev *hdev);
> +void hci_devcd_rx(struct work_struct *work);
> +void hci_devcd_timeout(struct work_struct *work);
> +
> +int hci_devcd_register(struct hci_dev *hdev, coredump_t coredump,
> +                      dmp_hdr_t dmp_hdr, notify_change_t notify_change);
> +int hci_devcd_init(struct hci_dev *hdev, u32 dump_size);
> +int hci_devcd_append(struct hci_dev *hdev, struct sk_buff *skb);
> +int hci_devcd_append_pattern(struct hci_dev *hdev, u8 pattern, u32 len);
> +int hci_devcd_complete(struct hci_dev *hdev);
> +int hci_devcd_abort(struct hci_dev *hdev);
> +
> +#else
> +
> +static inline void hci_devcd_reset(struct hci_dev *hdev) {}
> +static inline void hci_devcd_rx(struct work_struct *work) {}
> +static inline void hci_devcd_timeout(struct work_struct *work) {}
> +
> +static inline int hci_devcd_register(struct hci_dev *hdev, coredump_t coredump,
> +                                    dmp_hdr_t dmp_hdr,
> +                                    notify_change_t notify_change)
> +{
> +       return -EOPNOTSUPP;
> +}
> +
> +static inline int hci_devcd_init(struct hci_dev *hdev, u32 dump_size)
> +{
> +       return -EOPNOTSUPP;
> +}
> +
> +static inline int hci_devcd_append(struct hci_dev *hdev, struct sk_buff *skb)
> +{
> +       return -EOPNOTSUPP;
> +}
> +
> +static inline int hci_devcd_append_pattern(struct hci_dev *hdev,
> +                                          u8 pattern, u32 len)
> +{
> +       return -EOPNOTSUPP;
> +}
> +
> +static inline int hci_devcd_complete(struct hci_dev *hdev)
> +{
> +       return -EOPNOTSUPP;
> +}
> +
> +static inline int hci_devcd_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 9488671c1219..300aaac84adf 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
> @@ -590,6 +591,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;
> @@ -1496,6 +1501,15 @@ static inline void hci_set_aosp_capable(struct hci_dev *hdev)
>  #endif
>  }
>
> +static inline void hci_devcd_setup(struct hci_dev *hdev)
> +{
> +#ifdef CONFIG_DEV_COREDUMP
> +       INIT_WORK(&hdev->dump.dump_rx, hci_devcd_rx);
> +       INIT_DELAYED_WORK(&hdev->dump.dump_timeout, hci_devcd_timeout);
> +       skb_queue_head_init(&hdev->dump.dump_q);
> +#endif
> +}
> +
>  int hci_dev_open(__u16 dev);
>  int hci_dev_close(__u16 dev);
>  int hci_dev_do_close(struct hci_dev *hdev);
> diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile
> index 0e7b7db42750..141ac1fda0bf 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_LE) += iso.o
>  bluetooth-$(CONFIG_BT_HS) += a2mp.o amp.o
> diff --git a/net/bluetooth/coredump.c b/net/bluetooth/coredump.c
> new file mode 100644
> index 000000000000..fb5545c16636
> --- /dev/null
> +++ b/net/bluetooth/coredump.c
> @@ -0,0 +1,525 @@
> +// 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 DBG_UNEXPECTED_STATE() \
> +       bt_dev_dbg(hdev, \
> +                  "Unexpected packet (%d) for state (%d). ", \
> +                  hci_dmp_cb(skb)->pkt_type, hdev->dump.state)
> +
> +#define MAX_DEVCOREDUMP_HDR_SIZE       512     /* bytes */
> +
> +static int hci_devcd_update_hdr_state(char *buf, size_t size, int state)
> +{
> +       int len = 0;
> +
> +       if (!buf)
> +               return 0;
> +
> +       len = scnprintf(buf, size, "Bluetooth devcoredump\nState: %d\n", state);
> +
> +       return len + 1; /* scnprintf adds \0 at the end upon state rewrite */
> +}
> +
> +/* Call with hci_dev_lock only. */
> +static int hci_devcd_update_state(struct hci_dev *hdev, int state)
> +{
> +       bt_dev_dbg(hdev, "Updating devcoredump state from %d to %d.",
> +                  hdev->dump.state, state);
> +
> +       hdev->dump.state = state;
> +
> +       return hci_devcd_update_hdr_state(hdev->dump.head,
> +                                         hdev->dump.alloc_size, state);
> +}
> +
> +static int hci_devcd_mkheader(struct hci_dev *hdev, struct sk_buff *skb)
> +{
> +       char dump_start[] = "--- Start dump ---\n";
> +       char hdr[80];
> +       int hdr_len;
> +
> +       hdr_len = hci_devcd_update_hdr_state(hdr, sizeof(hdr),
> +                                            HCI_DEVCOREDUMP_IDLE);
> +       skb_put_data(skb, hdr, hdr_len);
> +
> +       if (hdev->dump.dmp_hdr)
> +               hdev->dump.dmp_hdr(hdev, skb);
> +
> +       skb_put_data(skb, dump_start, strlen(dump_start));
> +
> +       return skb->len;
> +}
> +
> +/* Do not call with hci_dev_lock since this calls driver code. */
> +static void hci_devcd_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_devcd_reset(struct hci_dev *hdev)
> +{
> +       hdev->dump.head = NULL;
> +       hdev->dump.tail = NULL;
> +       hdev->dump.alloc_size = 0;
> +
> +       hci_devcd_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_devcd_free(struct hci_dev *hdev)
> +{
> +       if (hdev->dump.head)
> +               vfree(hdev->dump.head);
> +
> +       hci_devcd_reset(hdev);
> +}
> +
> +/* Call with hci_dev_lock only. */
> +static int hci_devcd_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_devcd_update_state(hdev, HCI_DEVCOREDUMP_IDLE);
> +
> +       return 0;
> +}
> +
> +/* Call with hci_dev_lock only. */
> +static bool hci_devcd_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_devcd_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_devcd_prepare(struct hci_dev *hdev, u32 dump_size)
> +{
> +       struct sk_buff *skb;
> +       int dump_hdr_size;
> +       int err = 0;
> +
> +       skb = alloc_skb(MAX_DEVCOREDUMP_HDR_SIZE, GFP_ATOMIC);
> +       if (!skb)
> +               return -ENOMEM;
> +
> +       dump_hdr_size = hci_devcd_mkheader(hdev, skb);
> +
> +       if (hci_devcd_alloc(hdev, dump_hdr_size + dump_size)) {
> +               err = -ENOMEM;
> +               goto hdr_free;
> +       }
> +
> +       /* Insert the device header */
> +       if (!hci_devcd_copy(hdev, skb->data, skb->len)) {
> +               bt_dev_err(hdev, "Failed to insert header");
> +               hci_devcd_free(hdev);
> +
> +               err = -ENOMEM;
> +               goto hdr_free;
> +       }
> +
> +hdr_free:
> +       kfree_skb(skb);
> +
> +       return err;
> +}
> +
> +static void hci_devcd_handle_pkt_init(struct hci_dev *hdev, struct sk_buff *skb)
> +{
> +       u32 *dump_size;
> +
> +       if (hdev->dump.state != HCI_DEVCOREDUMP_IDLE) {
> +               DBG_UNEXPECTED_STATE();
> +               return;
> +       }
> +
> +       if (skb->len != sizeof(*dump_size)) {
> +               bt_dev_dbg(hdev, "Invalid dump init pkt");
> +               return;
> +       }
> +
> +       dump_size = skb_pull_data(skb, sizeof(*dump_size));
> +       if (!*dump_size) {
> +               bt_dev_err(hdev, "Zero size dump init pkt");
> +               return;
> +       }
> +
> +       if (hci_devcd_prepare(hdev, *dump_size)) {
> +               bt_dev_err(hdev, "Failed to prepare for dump");
> +               return;
> +       }
> +
> +       hci_devcd_update_state(hdev, HCI_DEVCOREDUMP_ACTIVE);
> +       queue_delayed_work(hdev->workqueue, &hdev->dump.dump_timeout,
> +                          DEVCOREDUMP_TIMEOUT);
> +}
> +
> +static void hci_devcd_handle_pkt_skb(struct hci_dev *hdev, struct sk_buff *skb)
> +{
> +       if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) {
> +               DBG_UNEXPECTED_STATE();
> +               return;
> +       }
> +
> +       if (!hci_devcd_copy(hdev, skb->data, skb->len))
> +               bt_dev_dbg(hdev, "Failed to insert skb");
> +}
> +
> +static void hci_devcd_handle_pkt_pattern(struct hci_dev *hdev,
> +                                        struct sk_buff *skb)
> +{
> +       struct hci_devcoredump_skb_pattern *pattern;
> +
> +       if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) {
> +               DBG_UNEXPECTED_STATE();
> +               return;
> +       }
> +
> +       if (skb->len != sizeof(*pattern)) {
> +               bt_dev_dbg(hdev, "Invalid pattern skb");
> +               return;
> +       }
> +
> +       pattern = skb_pull_data(skb, sizeof(*pattern));
> +
> +       if (!hci_devcd_memset(hdev, pattern->pattern, pattern->len))
> +               bt_dev_dbg(hdev, "Failed to set pattern");
> +}
> +
> +static void hci_devcd_handle_pkt_complete(struct hci_dev *hdev,
> +                                         struct sk_buff *skb)
> +{
> +       u32 dump_size;
> +
> +       if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) {
> +               DBG_UNEXPECTED_STATE();
> +               return;
> +       }
> +
> +       hci_devcd_update_state(hdev, HCI_DEVCOREDUMP_DONE);
> +       dump_size = hdev->dump.tail - hdev->dump.head;
> +
> +       bt_dev_info(hdev, "Devcoredump complete with size %u (expect %zu)",
> +                   dump_size, hdev->dump.alloc_size);
> +
> +       dev_coredumpv(&hdev->dev, hdev->dump.head, dump_size, GFP_KERNEL);
> +}
> +
> +static void hci_devcd_handle_pkt_abort(struct hci_dev *hdev,
> +                                      struct sk_buff *skb)
> +{
> +       u32 dump_size;
> +
> +       if (hdev->dump.state != HCI_DEVCOREDUMP_ACTIVE) {
> +               DBG_UNEXPECTED_STATE();
> +               return;
> +       }
> +
> +       hci_devcd_update_state(hdev, HCI_DEVCOREDUMP_ABORT);
> +       dump_size = hdev->dump.tail - hdev->dump.head;
> +
> +       bt_dev_info(hdev, "Devcoredump aborted with size %u (expect %zu)",
> +                   dump_size, hdev->dump.alloc_size);
> +
> +       /* Emit a devcoredump with the available data */
> +       dev_coredumpv(&hdev->dev, hdev->dump.head, dump_size, GFP_KERNEL);
> +}
> +
> +/* 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_devcd_init(). Once active, the driver
> + *              can append data using hci_devcd_append() or insert a pattern
> + *              using hci_devcd_append_pattern().
> + *
> + *      HCI_DEVCOREDUMP_DONE: Once the dump collection is complete, the drive
> + *              can signal the completion using hci_devcd_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_devcd_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_devcd_register() before using the hci
> + * devcoredump APIs.
> + */
> +void hci_devcd_rx(struct work_struct *work)
> +{
> +       struct hci_dev *hdev = container_of(work, struct hci_dev, dump.dump_rx);
> +       struct sk_buff *skb;
> +       int start_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:
> +                       hci_devcd_handle_pkt_init(hdev, skb);
> +                       break;
> +
> +               case HCI_DEVCOREDUMP_PKT_SKB:
> +                       hci_devcd_handle_pkt_skb(hdev, skb);
> +                       break;
> +
> +               case HCI_DEVCOREDUMP_PKT_PATTERN:
> +                       hci_devcd_handle_pkt_pattern(hdev, skb);
> +                       break;
> +
> +               case HCI_DEVCOREDUMP_PKT_COMPLETE:
> +                       hci_devcd_handle_pkt_complete(hdev, skb);
> +                       break;
> +
> +               case HCI_DEVCOREDUMP_PKT_ABORT:
> +                       hci_devcd_handle_pkt_abort(hdev, skb);
> +                       break;
> +
> +               default:
> +                       bt_dev_dbg(hdev, "Unknown packet (%d) for state (%d). ",
> +                                  hci_dmp_cb(skb)->pkt_type, hdev->dump.state);
> +                       break;
> +               }
> +
> +               hci_dev_unlock(hdev);
> +               kfree_skb(skb);
> +
> +               /* Notify the driver about any state changes before resetting
> +                * the state machine
> +                */
> +               if (start_state != hdev->dump.state)
> +                       hci_devcd_notify(hdev, hdev->dump.state);
> +
> +               /* Reset the state machine if the devcoredump is complete */
> +               hci_dev_lock(hdev);
> +               if (hdev->dump.state == HCI_DEVCOREDUMP_DONE ||
> +                   hdev->dump.state == HCI_DEVCOREDUMP_ABORT)
> +                       hci_devcd_reset(hdev);
> +               hci_dev_unlock(hdev);
> +       }
> +}
> +EXPORT_SYMBOL(hci_devcd_rx);
> +
> +void hci_devcd_timeout(struct work_struct *work)
> +{
> +       struct hci_dev *hdev = container_of(work, struct hci_dev,
> +                                           dump.dump_timeout.work);
> +       u32 dump_size;
> +
> +       hci_devcd_notify(hdev, HCI_DEVCOREDUMP_TIMEOUT);
> +
> +       hci_dev_lock(hdev);
> +
> +       cancel_work_sync(&hdev->dump.dump_rx);

Sorry I haven't realized this sooner, but the calls to
cancel_work_sync may resume hci_devcd_rx which in turn will attempt to
acquire hci_dev_lock causing a deadlock so you might need to switch to
cancel_work.

> +
> +       hci_devcd_update_state(hdev, HCI_DEVCOREDUMP_TIMEOUT);

Since you are updating the state above I guess hci_devcd_rx should be
able to detect it had timeout, you should probably add a some code
that checks for HCI_DEVCOREDUMP_TIMEOUT and then bails out, that said
it would be great to that emulates a timeout as well to check if this
wouldn't cause deadlocks.

> +       dump_size = hdev->dump.tail - hdev->dump.head;
> +       bt_dev_info(hdev, "Devcoredump timeout with size %u (expect %zu)",
> +                   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_devcd_reset(hdev);
> +
> +       hci_dev_unlock(hdev);
> +}
> +EXPORT_SYMBOL(hci_devcd_timeout);
> +
> +int hci_devcd_register(struct hci_dev *hdev, coredump_t coredump,
> +                      dmp_hdr_t dmp_hdr, notify_change_t notify_change)
> +{
> +       /* Driver must implement coredump() and dmp_hdr() functions for
> +        * bluetooth devcoredump. The coredump() should trigger a coredump
> +        * event on the controller when the device's coredump sysfs entry is
> +        * written to. The dmp_hdr() should create a dump header to identify
> +        * the controller/fw/driver info.
> +        */
> +       if (!coredump || !dmp_hdr)
> +               return -EINVAL;
> +
> +       hci_dev_lock(hdev);
> +       hdev->dump.coredump = coredump;
> +       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_devcd_register);
> +
> +static inline bool hci_devcd_enabled(struct hci_dev *hdev)
> +{
> +       return hdev->dump.supported;
> +}
> +
> +int hci_devcd_init(struct hci_dev *hdev, u32 dump_size)
> +{
> +       struct sk_buff *skb;
> +
> +       if (!hci_devcd_enabled(hdev))
> +               return -EOPNOTSUPP;
> +
> +       skb = alloc_skb(sizeof(dump_size), GFP_ATOMIC);
> +       if (!skb)
> +               return -ENOMEM;
> +
> +       hci_dmp_cb(skb)->pkt_type = HCI_DEVCOREDUMP_PKT_INIT;
> +       skb_put_data(skb, &dump_size, sizeof(dump_size));
> +
> +       skb_queue_tail(&hdev->dump.dump_q, skb);
> +       queue_work(hdev->workqueue, &hdev->dump.dump_rx);
> +
> +       return 0;
> +}
> +EXPORT_SYMBOL(hci_devcd_init);
> +
> +int hci_devcd_append(struct hci_dev *hdev, struct sk_buff *skb)
> +{
> +       if (!skb)
> +               return -ENOMEM;
> +
> +       if (!hci_devcd_enabled(hdev)) {
> +               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_devcd_append);
> +
> +int hci_devcd_append_pattern(struct hci_dev *hdev, u8 pattern, u32 len)
> +{
> +       struct hci_devcoredump_skb_pattern p;
> +       struct sk_buff *skb;
> +
> +       if (!hci_devcd_enabled(hdev))
> +               return -EOPNOTSUPP;
> +
> +       skb = alloc_skb(sizeof(p), GFP_ATOMIC);
> +       if (!skb)
> +               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_devcd_append_pattern);
> +
> +int hci_devcd_complete(struct hci_dev *hdev)
> +{
> +       struct sk_buff *skb;
> +
> +       if (!hci_devcd_enabled(hdev))
> +               return -EOPNOTSUPP;
> +
> +       skb = alloc_skb(0, GFP_ATOMIC);
> +       if (!skb)
> +               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_devcd_complete);
> +
> +int hci_devcd_abort(struct hci_dev *hdev)
> +{
> +       struct sk_buff *skb;
> +
> +       if (!hci_devcd_enabled(hdev))
> +               return -EOPNOTSUPP;
> +
> +       skb = alloc_skb(0, GFP_ATOMIC);
> +       if (!skb)
> +               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_devcd_abort);
> diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
> index 334e308451f5..393b317ae68f 100644
> --- a/net/bluetooth/hci_core.c
> +++ b/net/bluetooth/hci_core.c
> @@ -2544,6 +2544,7 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv)
>         INIT_DELAYED_WORK(&hdev->cmd_timer, hci_cmd_timeout);
>         INIT_DELAYED_WORK(&hdev->ncmd_timer, hci_ncmd_timeout);
>
> +       hci_devcd_setup(hdev);
>         hci_request_setup(hdev);
>
>         hci_init_sysfs(hdev);
> diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
> index 561a519a11bd..2448423912fd 100644
> --- a/net/bluetooth/hci_sync.c
> +++ b/net/bluetooth/hci_sync.c
> @@ -4722,6 +4722,8 @@ int hci_dev_open_sync(struct hci_dev *hdev)
>                 goto done;
>         }
>
> +       hci_devcd_reset(hdev);
> +
>         set_bit(HCI_RUNNING, &hdev->flags);
>         hci_sock_dev_event(hdev, HCI_DEV_OPEN);
>
> --
> 2.40.0.348.gf938b09366-goog
>


-- 
Luiz Augusto von Dentz




[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux