mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-08-15 14:37:33 +00:00

Currently both dev_coredumpv and skb_put_data in hci_devcd_dump use
hdev->dump.head. However, dev_coredumpv can free the buffer. From
dev_coredumpm_timeout documentation, which is used by dev_coredumpv:
> Creates a new device coredump for the given device. If a previous one hasn't
> been read yet, the new coredump is discarded. The data lifetime is determined
> by the device coredump framework and when it is no longer needed the @free
> function will be called to free the data.
If the data has not been read by the userspace yet, dev_coredumpv will
discard new buffer, freeing hdev->dump.head. This leads to
vmalloc-out-of-bounds error when skb_put_data tries to access
hdev->dump.head.
A crash report from syzbot illustrates this:
==================================================================
BUG: KASAN: vmalloc-out-of-bounds in skb_put_data
include/linux/skbuff.h:2752 [inline]
BUG: KASAN: vmalloc-out-of-bounds in hci_devcd_dump+0x142/0x240
net/bluetooth/coredump.c:258
Read of size 140 at addr ffffc90004ed5000 by task kworker/u9:2/5844
CPU: 1 UID: 0 PID: 5844 Comm: kworker/u9:2 Not tainted
6.14.0-syzkaller-10892-g4e82c87058f4 #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS
Google 02/12/2025
Workqueue: hci0 hci_devcd_timeout
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:94 [inline]
dump_stack_lvl+0x116/0x1f0 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:408 [inline]
print_report+0xc3/0x670 mm/kasan/report.c:521
kasan_report+0xe0/0x110 mm/kasan/report.c:634
check_region_inline mm/kasan/generic.c:183 [inline]
kasan_check_range+0xef/0x1a0 mm/kasan/generic.c:189
__asan_memcpy+0x23/0x60 mm/kasan/shadow.c:105
skb_put_data include/linux/skbuff.h:2752 [inline]
hci_devcd_dump+0x142/0x240 net/bluetooth/coredump.c:258
hci_devcd_timeout+0xb5/0x2e0 net/bluetooth/coredump.c:413
process_one_work+0x9cc/0x1b70 kernel/workqueue.c:3238
process_scheduled_works kernel/workqueue.c:3319 [inline]
worker_thread+0x6c8/0xf10 kernel/workqueue.c:3400
kthread+0x3c2/0x780 kernel/kthread.c:464
ret_from_fork+0x45/0x80 arch/x86/kernel/process.c:153
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
The buggy address ffffc90004ed5000 belongs to a vmalloc virtual mapping
Memory state around the buggy address:
ffffc90004ed4f00: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
ffffc90004ed4f80: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
>ffffc90004ed5000: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
^
ffffc90004ed5080: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
ffffc90004ed5100: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
==================================================================
To avoid this issue, reorder dev_coredumpv to be called after
skb_put_data that does not free the data.
Reported-by: syzbot+ac3c79181f6aecc5120c@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=ac3c79181f6aecc5120c
Fixes: b257e02ecc
("HCI: coredump: Log devcd dumps into the monitor")
Tested-by: syzbot+ac3c79181f6aecc5120c@syzkaller.appspotmail.com
Signed-off-by: Ivan Pravdin <ipravdin.official@gmail.com>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
554 lines
13 KiB
C
554 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2023 Google Corporation
|
|
*/
|
|
|
|
#include <linux/devcoredump.h>
|
|
|
|
#include <linux/unaligned.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)
|
|
{
|
|
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 = get_unaligned_le32(skb_pull_data(skb, 4));
|
|
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,
|
|
hdev->dump.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_dump(struct hci_dev *hdev)
|
|
{
|
|
struct sk_buff *skb;
|
|
u32 size;
|
|
|
|
bt_dev_dbg(hdev, "state %d", hdev->dump.state);
|
|
|
|
size = hdev->dump.tail - hdev->dump.head;
|
|
|
|
/* Send a copy to monitor as a diagnostic packet */
|
|
skb = bt_skb_alloc(size, GFP_ATOMIC);
|
|
if (skb) {
|
|
skb_put_data(skb, hdev->dump.head, size);
|
|
hci_recv_diag(hdev, skb);
|
|
}
|
|
|
|
/* Emit a devcoredump with the available data */
|
|
dev_coredumpv(&hdev->dev, hdev->dump.head, size, GFP_KERNEL);
|
|
}
|
|
|
|
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_dbg(hdev, "complete with size %u (expect %zu)", dump_size,
|
|
hdev->dump.alloc_size);
|
|
|
|
hci_devcd_dump(hdev);
|
|
}
|
|
|
|
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_dbg(hdev, "aborted with size %u (expect %zu)", dump_size,
|
|
hdev->dump.alloc_size);
|
|
|
|
hci_devcd_dump(hdev);
|
|
}
|
|
|
|
/* 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))) {
|
|
/* Return if timeout occurs. The timeout handler function
|
|
* hci_devcd_timeout() will report the available dump data.
|
|
*/
|
|
if (hdev->dump.state == HCI_DEVCOREDUMP_TIMEOUT) {
|
|
kfree_skb(skb);
|
|
return;
|
|
}
|
|
|
|
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(&hdev->dump.dump_rx);
|
|
|
|
hci_devcd_update_state(hdev, HCI_DEVCOREDUMP_TIMEOUT);
|
|
|
|
dump_size = hdev->dump.tail - hdev->dump.head;
|
|
bt_dev_dbg(hdev, "timeout with size %u (expect %zu)", dump_size,
|
|
hdev->dump.alloc_size);
|
|
|
|
hci_devcd_dump(hdev);
|
|
|
|
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;
|
|
hdev->dump.timeout = DEVCOREDUMP_TIMEOUT;
|
|
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;
|
|
put_unaligned_le32(dump_size, skb_put(skb, 4));
|
|
|
|
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);
|