diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000000..60d318f805ff1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: CI + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + checks: write + pull-requests: write + +jobs: + ci: + runs-on: ubuntu-latest + name: CI for Pull Request + steps: + - name: Checkout the source code + uses: actions/checkout@v3 + with: + path: src/src + + - name: CI + uses: bluez/action-ci@main + with: + task: ci + base_folder: src + space: kernel + github_token: ${{ secrets.GITHUB_TOKEN }} + email_token: ${{ secrets.EMAIL_TOKEN }} + patchwork_token: ${{ secrets.PATCHWORK_TOKEN }} + patchwork_user: ${{ secrets.PATCHWORK_USER }} + diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml new file mode 100644 index 0000000000000..5e95af92ab1ab --- /dev/null +++ b/.github/workflows/sync.yml @@ -0,0 +1,44 @@ +name: Sync + +on: + schedule: + - cron: "*/5 * * * *" + +jobs: + sync_repo: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + ref: master + + - name: Sync Repo + uses: bluez/action-ci@main + with: + task: sync + workflow: workflow + upstream_repo: 'https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git' + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Cleanup PR + uses: bluez/action-ci@main + with: + task: cleanup + github_token: ${{ secrets.ACTION_TOKEN }} + + sync_patchwork: + needs: sync_repo + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Sync Patchwork + uses: bluez/action-ci@main + with: + task: patchwork + workflow: workflow + space: kernel + github_token: ${{ secrets.ACTION_TOKEN }} + email_token: ${{ secrets.EMAIL_TOKEN }} + patchwork_token: ${{ secrets.PATCHWORK_TOKEN }} + patchwork_user: ${{ secrets.PATCHWORK_USER }} diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 84614e60d142f..b9c62d6acab56 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -27,8 +27,9 @@ #include "btbcm.h" #include "btrtl.h" #include "btmtk.h" +#include "hci_uart.h" -#define VERSION "0.8" +#define VERSION "1.0" static bool disable_scofix; static bool force_scofix; @@ -983,6 +984,9 @@ struct btqca_data { #define BTUSB_ALT6_CONTINUOUS_TX 16 #define BTUSB_HW_SSR_ACTIVE 17 +#define BTUSB_PROTO_LEGACY 0x00 +#define BTUSB_PROTO_H4 0x01 + struct btusb_data { struct hci_dev *hdev; struct usb_device *udev; @@ -1016,6 +1020,7 @@ struct btusb_data { struct sk_buff *evt_skb; struct sk_buff *acl_skb; struct sk_buff *sco_skb; + struct sk_buff *rx_skb; struct usb_endpoint_descriptor *intr_ep; struct usb_endpoint_descriptor *bulk_tx_ep; @@ -1029,6 +1034,7 @@ struct btusb_data { __u8 cmdreq_type; __u8 cmdreq; + __u8 proto; unsigned int sco_num; unsigned int air_mode; @@ -1256,6 +1262,11 @@ static inline void btusb_free_frags(struct btusb_data *data) dev_kfree_skb_irq(data->sco_skb); data->sco_skb = NULL; + /* rx_skb may hold an ERR_PTR from a previous h4_recv_skb() call */ + if (!IS_ERR(data->rx_skb)) + dev_kfree_skb_irq(data->rx_skb); + data->rx_skb = NULL; + spin_unlock_irqrestore(&data->rxlock, flags); } @@ -1355,12 +1366,41 @@ static int btusb_recv_acl(struct hci_dev *hdev, struct sk_buff *skb) return 0; } +/* Dispatch through the btusb_recv_* wrappers so that vendor specific + * handling (data->recv_event, data->recv_acl) is preserved in H:4 mode. + */ +static const struct h4_recv_pkt btusb_recv_pkts[] = { + { H4_RECV_ACL, .recv = btusb_recv_acl }, + { H4_RECV_SCO, .recv = hci_recv_frame }, + { H4_RECV_EVENT, .recv = btusb_recv_event }, + { H4_RECV_ISO, .recv = hci_recv_frame }, +}; + +static int btusb_recv_h4(struct btusb_data *data, void *buffer, int count) +{ + unsigned long flags; + int err = 0; + + spin_lock_irqsave(&data->rxlock, flags); + data->rx_skb = h4_recv_skb(data->hdev, NULL, NULL, data->rx_skb, buffer, + count, btusb_recv_pkts, + ARRAY_SIZE(btusb_recv_pkts)); + if (IS_ERR(data->rx_skb)) + err = PTR_ERR(data->rx_skb); + spin_unlock_irqrestore(&data->rxlock, flags); + + return err; +} + static int btusb_recv_bulk(struct btusb_data *data, void *buffer, int count) { struct sk_buff *skb; unsigned long flags; int err = 0; + if (data->proto == BTUSB_PROTO_H4) + return btusb_recv_h4(data, buffer, count); + spin_lock_irqsave(&data->rxlock, flags); skb = data->acl_skb; @@ -2038,12 +2078,14 @@ static int btusb_open(struct hci_dev *hdev) data->intf->needs_remote_wakeup = 1; - if (test_and_set_bit(BTUSB_INTR_RUNNING, &data->flags)) - goto done; + if (data->proto == BTUSB_PROTO_LEGACY) { + if (test_and_set_bit(BTUSB_INTR_RUNNING, &data->flags)) + goto done; - err = btusb_submit_intr_urb(hdev, GFP_KERNEL); - if (err < 0) - goto failed; + err = btusb_submit_intr_urb(hdev, GFP_KERNEL); + if (err < 0) + goto failed; + } err = btusb_submit_bulk_urb(hdev, GFP_KERNEL); if (err < 0) { @@ -2141,56 +2183,64 @@ static int btusb_flush(struct hci_dev *hdev) return 0; } -static struct urb *alloc_ctrl_urb(struct hci_dev *hdev, struct sk_buff *skb) +static struct urb *alloc_bulk_urb(struct hci_dev *hdev, struct sk_buff *skb) { struct btusb_data *data = hci_get_drvdata(hdev); - struct usb_ctrlrequest *dr; struct urb *urb; unsigned int pipe; + if (!data->bulk_tx_ep) + return ERR_PTR(-ENODEV); + urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) return ERR_PTR(-ENOMEM); - dr = kmalloc_obj(*dr); - if (!dr) { - usb_free_urb(urb); - return ERR_PTR(-ENOMEM); - } - - dr->bRequestType = data->cmdreq_type; - dr->bRequest = data->cmdreq; - dr->wIndex = 0; - dr->wValue = 0; - dr->wLength = __cpu_to_le16(skb->len); + pipe = usb_sndbulkpipe(data->udev, data->bulk_tx_ep->bEndpointAddress); - pipe = usb_sndctrlpipe(data->udev, 0x00); + if (data->proto == BTUSB_PROTO_H4) { + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &hci_skb_pkt_type(skb), 1); + } - usb_fill_control_urb(urb, data->udev, pipe, (void *)dr, - skb->data, skb->len, btusb_tx_complete, skb); + usb_fill_bulk_urb(urb, data->udev, pipe, + skb->data, skb->len, btusb_tx_complete, skb); skb->dev = (void *)hdev; return urb; } -static struct urb *alloc_bulk_urb(struct hci_dev *hdev, struct sk_buff *skb) +static struct urb *alloc_ctrl_urb(struct hci_dev *hdev, struct sk_buff *skb) { struct btusb_data *data = hci_get_drvdata(hdev); + struct usb_ctrlrequest *dr; struct urb *urb; unsigned int pipe; - if (!data->bulk_tx_ep) - return ERR_PTR(-ENODEV); + if (data->proto == BTUSB_PROTO_H4) + return alloc_bulk_urb(hdev, skb); urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) return ERR_PTR(-ENOMEM); - pipe = usb_sndbulkpipe(data->udev, data->bulk_tx_ep->bEndpointAddress); + dr = kmalloc_obj(*dr); + if (!dr) { + usb_free_urb(urb); + return ERR_PTR(-ENOMEM); + } - usb_fill_bulk_urb(urb, data->udev, pipe, - skb->data, skb->len, btusb_tx_complete, skb); + dr->bRequestType = data->cmdreq_type; + dr->bRequest = data->cmdreq; + dr->wIndex = 0; + dr->wValue = 0; + dr->wLength = __cpu_to_le16(skb->len); + + pipe = usb_sndctrlpipe(data->udev, 0x00); + + usb_fill_control_urb(urb, data->udev, pipe, (void *)dr, + skb->data, skb->len, btusb_tx_complete, skb); skb->dev = (void *)hdev; @@ -2203,6 +2253,9 @@ static struct urb *alloc_isoc_urb(struct hci_dev *hdev, struct sk_buff *skb) struct urb *urb; unsigned int pipe; + if (data->proto == BTUSB_PROTO_H4) + return alloc_bulk_urb(hdev, skb); + if (!data->isoc_tx_ep) return ERR_PTR(-ENODEV); @@ -2416,10 +2469,9 @@ static int btusb_switch_alt_setting(struct hci_dev *hdev, int new_alts) return 0; } -static struct usb_host_interface *btusb_find_altsetting(struct btusb_data *data, - int alt) +static struct usb_host_interface * +btusb_find_altsetting(struct usb_interface *intf, int alt) { - struct usb_interface *intf = data->isoc; int i; BT_DBG("Looking for Alt no :%d", alt); @@ -2475,9 +2527,9 @@ static void btusb_work(struct work_struct *work) * MTU >= 3 (packets) * 25 (size) - 3 (headers) = 72 * see also Core spec 5, vol 4, B 2.1.1 & Table 2.1. */ - if (btusb_find_altsetting(data, 6)) + if (btusb_find_altsetting(data->isoc, 6)) new_alts = 6; - else if (btusb_find_altsetting(data, 3) && + else if (btusb_find_altsetting(data->isoc, 3) && hdev->sco_mtu >= 72 && test_bit(BTUSB_USE_ALT3_FOR_WBS, &data->flags)) new_alts = 3; @@ -3951,8 +4003,11 @@ static ssize_t force_poll_sync_write(struct file *file, if (err) return err; - /* Only allow changes while the adapter is down */ - if (test_bit(HCI_UP, &data->hdev->flags)) + /* Only allow changes while the adapter is down and it is using legacy + * protocol. + */ + if (test_bit(HCI_UP, &data->hdev->flags) || + data->proto != BTUSB_PROTO_LEGACY) return -EPERM; if (data->poll_sync == enable) @@ -4051,7 +4106,7 @@ static int btusb_hci_drv_supported_altsettings(struct hci_dev *hdev, void *data, goto done; for (i = 0; i <= 6; i++) { - if (btusb_find_altsetting(drvdata, i)) + if (btusb_find_altsetting(drvdata->isoc, i)) rp->altsettings[rp->num++] = i; } @@ -4105,6 +4160,8 @@ static int btusb_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct gpio_desc *reset_gpio; + struct usb_host_interface *alt; + struct usb_endpoint_descriptor *ep; struct btusb_data *data; struct hci_dev *hdev; unsigned ifnum_base; @@ -4146,10 +4203,36 @@ static int btusb_probe(struct usb_interface *intf, return -ENOMEM; data->match_id = id; + + /* Alternate setting 1 with a single pair of bulk endpoints means the + * controller supports Bulk Serialization Mode, in which every packet + * is prefixed with an H:4 header and carried over the bulk endpoints. + */ + alt = btusb_find_altsetting(intf, 1); + if (alt && usb_find_int_in_endpoint(alt, &ep) && + !usb_find_common_endpoints(alt, &ep, &ep, NULL, NULL)) { + err = usb_set_interface(interface_to_usbdev(intf), ifnum_base, 1); + if (!err) + data->proto = BTUSB_PROTO_H4; + else + dev_warn(&intf->dev, + "failed to select alt setting 1 (%d), using legacy mode", + err); + } + + /* Check if all endpoints could be enumerated, legacy mode requires + * interrupt and bulk endpoints while H4 mode only requires bulk + * endpoints. + */ err = usb_find_common_endpoints(intf->cur_altsetting, &data->bulk_rx_ep, - &data->bulk_tx_ep, &data->intr_ep, NULL); - if (err) + &data->bulk_tx_ep, + data->proto == BTUSB_PROTO_LEGACY ? + &data->intr_ep : NULL, + NULL); + if (err) { + dev_err(&intf->dev, "failed to enumerate endpoints\n"); goto err_free_data; + } if (id->driver_info & BTUSB_AMP) { data->cmdreq_type = USB_TYPE_CLASS | 0x01; @@ -4361,6 +4444,12 @@ static int btusb_probe(struct usb_interface *intf, if (id->driver_info & BTUSB_AMP) { /* AMP controllers do not support SCO packets */ data->isoc = NULL; + } else if (data->proto == BTUSB_PROTO_H4) { + /* In H:4 mode every packet, including SCO/ISO, is carried over + * the bulk endpoints, so the isochronous interface must not be + * claimed nor have its alternate settings switched. + */ + data->isoc = NULL; } else { /* Interface orders are hardcoded in the specification */ data->isoc = usb_ifnum_to_if(data->udev, ifnum_base + 1); @@ -4470,7 +4559,8 @@ static int btusb_probe(struct usb_interface *intf, if (enable_autosuspend) usb_enable_autosuspend(data->udev); - data->poll_sync = enable_poll_sync; + if (data->proto == BTUSB_PROTO_LEGACY) + data->poll_sync = enable_poll_sync; err = hci_register_dev(hdev); if (err < 0) diff --git a/drivers/bluetooth/hci_h4.c b/drivers/bluetooth/hci_h4.c index 7673727074985..cbdf51458ec3c 100644 --- a/drivers/bluetooth/hci_h4.c +++ b/drivers/bluetooth/hci_h4.c @@ -29,6 +29,7 @@ #include #include +#include #include "hci_uart.h" @@ -112,8 +113,9 @@ static int h4_recv(struct hci_uart *hu, const void *data, int count) if (!h4) return -ENODEV; - h4->rx_skb = h4_recv_buf(hu, h4->rx_skb, data, count, - h4_recv_pkts, ARRAY_SIZE(h4_recv_pkts)); + h4->rx_skb = h4_recv_skb(hu->hdev, &hu->alignment, &hu->padding, + h4->rx_skb, data, count, h4_recv_pkts, + ARRAY_SIZE(h4_recv_pkts)); if (IS_ERR(h4->rx_skb)) { int err = PTR_ERR(h4->rx_skb); bt_dev_err(hu->hdev, "Frame reassembly failed (%d)", err); @@ -155,120 +157,7 @@ struct sk_buff *h4_recv_buf(struct hci_uart *hu, struct sk_buff *skb, const unsigned char *buffer, int count, const struct h4_recv_pkt *pkts, int pkts_count) { - u8 alignment = hu->alignment ? hu->alignment : 1; - struct hci_dev *hdev = hu->hdev; - - /* Check for error from previous call */ - if (IS_ERR(skb)) - skb = NULL; - - while (count) { - int i, len; - - /* remove padding bytes from buffer */ - for (; hu->padding && count > 0; hu->padding--) { - count--; - buffer++; - } - if (!count) - break; - - if (!skb) { - for (i = 0; i < pkts_count; i++) { - if (buffer[0] != (&pkts[i])->type) - continue; - - skb = bt_skb_alloc((&pkts[i])->maxlen, - GFP_ATOMIC); - if (!skb) - return ERR_PTR(-ENOMEM); - - hci_skb_pkt_type(skb) = (&pkts[i])->type; - hci_skb_expect(skb) = (&pkts[i])->hlen; - break; - } - - /* Check for invalid packet type */ - if (!skb) - return ERR_PTR(-EILSEQ); - - count -= 1; - buffer += 1; - } - - len = min_t(uint, hci_skb_expect(skb) - skb->len, count); - skb_put_data(skb, buffer, len); - - count -= len; - buffer += len; - - /* Check for partial packet */ - if (skb->len < hci_skb_expect(skb)) - continue; - - for (i = 0; i < pkts_count; i++) { - if (hci_skb_pkt_type(skb) == (&pkts[i])->type) - break; - } - - if (i >= pkts_count) { - kfree_skb(skb); - return ERR_PTR(-EILSEQ); - } - - if (skb->len == (&pkts[i])->hlen) { - u16 dlen; - - switch ((&pkts[i])->lsize) { - case 0: - /* No variable data length */ - dlen = 0; - break; - case 1: - /* Single octet variable length */ - dlen = skb->data[(&pkts[i])->loff]; - hci_skb_expect(skb) += dlen; - - if (skb_tailroom(skb) < dlen) { - kfree_skb(skb); - return ERR_PTR(-EMSGSIZE); - } - break; - case 2: - /* Double octet variable length */ - dlen = get_unaligned_le16(skb->data + - (&pkts[i])->loff); - hci_skb_expect(skb) += dlen; - - if (skb_tailroom(skb) < dlen) { - kfree_skb(skb); - return ERR_PTR(-EMSGSIZE); - } - break; - default: - /* Unsupported variable length */ - kfree_skb(skb); - return ERR_PTR(-EILSEQ); - } - - if (!dlen) { - hu->padding = (skb->len + 1) % alignment; - hu->padding = (alignment - hu->padding) % alignment; - - /* No more data, complete frame */ - (&pkts[i])->recv(hdev, skb); - skb = NULL; - } - } else { - hu->padding = (skb->len + 1) % alignment; - hu->padding = (alignment - hu->padding) % alignment; - - /* Complete frame */ - (&pkts[i])->recv(hdev, skb); - skb = NULL; - } - } - - return skb; + return h4_recv_skb(hu->hdev, &hu->alignment, &hu->padding, skb, buffer, + count, pkts, pkts_count); } EXPORT_SYMBOL_GPL(h4_recv_buf); diff --git a/drivers/bluetooth/hci_uart.h b/drivers/bluetooth/hci_uart.h index 48ac7ca9334e5..7fbe8dffab988 100644 --- a/drivers/bluetooth/hci_uart.h +++ b/drivers/bluetooth/hci_uart.h @@ -8,6 +8,8 @@ * Copyright (C) 2004-2005 Marcel Holtmann */ +#include + #ifndef N_HCI #define N_HCI 15 #endif @@ -121,43 +123,6 @@ void hci_uart_set_flow_control(struct hci_uart *hu, bool enable); void hci_uart_set_speeds(struct hci_uart *hu, unsigned int init_speed, unsigned int oper_speed); -struct h4_recv_pkt { - u8 type; /* Packet type */ - u8 hlen; /* Header length */ - u8 loff; /* Data length offset in header */ - u8 lsize; /* Data length field size */ - u16 maxlen; /* Max overall packet length */ - int (*recv)(struct hci_dev *hdev, struct sk_buff *skb); -}; - -#define H4_RECV_ACL \ - .type = HCI_ACLDATA_PKT, \ - .hlen = HCI_ACL_HDR_SIZE, \ - .loff = 2, \ - .lsize = 2, \ - .maxlen = HCI_MAX_FRAME_SIZE \ - -#define H4_RECV_SCO \ - .type = HCI_SCODATA_PKT, \ - .hlen = HCI_SCO_HDR_SIZE, \ - .loff = 2, \ - .lsize = 1, \ - .maxlen = HCI_MAX_SCO_SIZE - -#define H4_RECV_EVENT \ - .type = HCI_EVENT_PKT, \ - .hlen = HCI_EVENT_HDR_SIZE, \ - .loff = 1, \ - .lsize = 1, \ - .maxlen = HCI_MAX_EVENT_SIZE - -#define H4_RECV_ISO \ - .type = HCI_ISODATA_PKT, \ - .hlen = HCI_ISO_HDR_SIZE, \ - .loff = 2, \ - .lsize = 2, \ - .maxlen = HCI_MAX_FRAME_SIZE \ - #ifdef CONFIG_BT_HCIUART_H4 int h4_init(void); int h4_deinit(void); diff --git a/include/net/bluetooth/hci_h4.h b/include/net/bluetooth/hci_h4.h new file mode 100644 index 0000000000000..a37e7df8c9ce8 --- /dev/null +++ b/include/net/bluetooth/hci_h4.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Bluetooth HCI H:4 packet reassembly + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2004-2005 Marcel Holtmann + */ + +#ifndef __HCI_H4_H +#define __HCI_H4_H + +#include +#include + +struct hci_dev; + +struct h4_recv_pkt { + u8 type; /* Packet type */ + u8 hlen; /* Header length */ + u8 loff; /* Data length offset in header */ + u8 lsize; /* Data length field size */ + u16 maxlen; /* Max overall packet length */ + int (*recv)(struct hci_dev *hdev, struct sk_buff *skb); +}; + +#define H4_RECV_ACL \ + .type = HCI_ACLDATA_PKT, \ + .hlen = HCI_ACL_HDR_SIZE, \ + .loff = 2, \ + .lsize = 2, \ + .maxlen = HCI_MAX_FRAME_SIZE \ + +#define H4_RECV_SCO \ + .type = HCI_SCODATA_PKT, \ + .hlen = HCI_SCO_HDR_SIZE, \ + .loff = 2, \ + .lsize = 1, \ + .maxlen = HCI_MAX_SCO_SIZE + +#define H4_RECV_EVENT \ + .type = HCI_EVENT_PKT, \ + .hlen = HCI_EVENT_HDR_SIZE, \ + .loff = 1, \ + .lsize = 1, \ + .maxlen = HCI_MAX_EVENT_SIZE + +#define H4_RECV_ISO \ + .type = HCI_ISODATA_PKT, \ + .hlen = HCI_ISO_HDR_SIZE, \ + .loff = 2, \ + .lsize = 2, \ + .maxlen = HCI_MAX_FRAME_SIZE \ + +struct sk_buff *h4_recv_skb(struct hci_dev *hdev, u8 *alignment, u8 *padding, + struct sk_buff *skb, const unsigned char *buffer, + int count, const struct h4_recv_pkt *pkts, + int pkts_count); + +#endif /* __HCI_H4_H */ diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile index ff466ea97436a..b78ad98864d4f 100644 --- a/net/bluetooth/Makefile +++ b/net/bluetooth/Makefile @@ -14,7 +14,7 @@ bluetooth_6lowpan-y := 6lowpan.o bluetooth-y := af_bluetooth.o hci_core.o hci_conn.o hci_event.o mgmt.o \ hci_sock.o hci_sysfs.o l2cap_core.o l2cap_sock.o smp.o lib.o \ ecdh_helper.o mgmt_util.o mgmt_config.o hci_codec.o eir.o hci_sync.o \ - hci_drv.o + hci_drv.o hci_h4.o bluetooth-$(CONFIG_DEV_COREDUMP) += coredump.o diff --git a/net/bluetooth/hci_h4.c b/net/bluetooth/hci_h4.c new file mode 100644 index 0000000000000..86f809018062f --- /dev/null +++ b/net/bluetooth/hci_h4.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Bluetooth HCI H:4 packet reassembly + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2004-2005 Marcel Holtmann + */ + +#include +#include +#include + +#include +#include +#include + +/* h4_recv_skb - Reassemble H:4 framed packets + * @hdev: HCI device the packets are received on + * @alignment: optional packet alignment, NULL or 0 means no alignment + * @padding: optional padding state carried over between calls + * @skb: partially received packet from a previous call, may be NULL or an + * ERR_PTR returned by a previous call + * @buffer: buffer holding the received data + * @count: number of bytes in @buffer + * @pkts: table of supported packet types + * @pkts_count: number of entries in @pkts + * + * Returns the partially received packet to be passed to the next call, or an + * ERR_PTR on error. The returned value can be fed back into this function as + * is, but must be checked with IS_ERR() before being freed. + */ +struct sk_buff *h4_recv_skb(struct hci_dev *hdev, u8 *alignment, u8 *padding, + struct sk_buff *skb, const unsigned char *buffer, + int count, const struct h4_recv_pkt *pkts, + int pkts_count) +{ + u8 align = alignment && *alignment ? *alignment : 1; + + /* Check for error from previous call */ + if (IS_ERR(skb)) + skb = NULL; + + while (count) { + int i, len; + + /* remove padding bytes from buffer */ + if (padding) { + for (; (*padding) && count > 0; (*padding)--) { + count--; + buffer++; + } + } + + if (!count) + break; + + if (!skb) { + for (i = 0; i < pkts_count; i++) { + if (buffer[0] != pkts[i].type) + continue; + + skb = bt_skb_alloc(pkts[i].maxlen, + GFP_ATOMIC); + if (!skb) + return ERR_PTR(-ENOMEM); + + hci_skb_pkt_type(skb) = pkts[i].type; + hci_skb_expect(skb) = pkts[i].hlen; + break; + } + + /* Check for invalid packet type */ + if (!skb) + return ERR_PTR(-EILSEQ); + + count -= 1; + buffer += 1; + } + + len = min_t(uint, hci_skb_expect(skb) - skb->len, count); + skb_put_data(skb, buffer, len); + + count -= len; + buffer += len; + + /* Check for partial packet */ + if (skb->len < hci_skb_expect(skb)) + continue; + + for (i = 0; i < pkts_count; i++) { + if (hci_skb_pkt_type(skb) == pkts[i].type) + break; + } + + if (i >= pkts_count) { + kfree_skb(skb); + return ERR_PTR(-EILSEQ); + } + + if (skb->len == pkts[i].hlen) { + u16 dlen; + + switch (pkts[i].lsize) { + case 0: + /* No variable data length */ + dlen = 0; + break; + case 1: + /* Single octet variable length */ + dlen = skb->data[pkts[i].loff]; + hci_skb_expect(skb) += dlen; + + if (skb_tailroom(skb) < dlen) { + kfree_skb(skb); + return ERR_PTR(-EMSGSIZE); + } + break; + case 2: + /* Double octet variable length */ + dlen = get_unaligned_le16(skb->data + + pkts[i].loff); + hci_skb_expect(skb) += dlen; + + if (skb_tailroom(skb) < dlen) { + kfree_skb(skb); + return ERR_PTR(-EMSGSIZE); + } + break; + default: + /* Unsupported variable length */ + kfree_skb(skb); + return ERR_PTR(-EILSEQ); + } + + if (!dlen) { + if (padding) { + *padding = (skb->len + 1) % align; + *padding = (align - *padding) % align; + } + + /* No more data, complete frame */ + pkts[i].recv(hdev, skb); + skb = NULL; + } + } else { + if (padding) { + *padding = (skb->len + 1) % align; + *padding = (align - *padding) % align; + } + + /* Complete frame */ + pkts[i].recv(hdev, skb); + skb = NULL; + } + } + + return skb; +} +EXPORT_SYMBOL_GPL(h4_recv_skb);