This is a from scratch written driver to run H:4 on serdev based system with a Bluetooth controller attached via an UART. It is currently tested on RPi3 and it has Broadcom integration. It is DT only and is missing GPIO and runtime power management integration. Also Apple or ACPI support is currently not added. To integrate with controllers from Intel and Qualcomm, similar handling like with btusb.c has to be done. The goal is to run individual drivers on serdev capable systems so that we can retire hci_uart on these system and continue with a lot simpler and easier to maintain driver. It seems that hci_uart has too many race conditions due to handling TTY and line disciplines. And fixes for that are not really related to serdev based drivers. In a serdev only world it makes sense to remove any of the complex code. Don't mind that I overloaded CONFIG_BT_HCIBTUART since essentially the old btuart_cs.c driver is non-functional and will be removed soon. Signed-off-by: Marcel Holtmann <marcel@xxxxxxxxxxxx> --- drivers/bluetooth/Makefile | 1 + drivers/bluetooth/btuart.c | 441 ++++++++++++++++++++++++++++++++++++++++++++ drivers/bluetooth/h4_recv.h | 158 ++++++++++++++++ 3 files changed, 600 insertions(+) create mode 100644 drivers/bluetooth/btuart.c create mode 100644 drivers/bluetooth/h4_recv.h diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index 4e4e44d09796..6d68ab32229a 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_BT_HCIBTUART) += btuart_cs.o obj-$(CONFIG_BT_HCIBTUSB) += btusb.o obj-$(CONFIG_BT_HCIBTSDIO) += btsdio.o +obj-$(CONFIG_BT_HCIBTUART) += btuart.o obj-$(CONFIG_BT_INTEL) += btintel.o obj-$(CONFIG_BT_ATH3K) += ath3k.o diff --git a/drivers/bluetooth/btuart.c b/drivers/bluetooth/btuart.c new file mode 100644 index 000000000000..a69b1fcf4831 --- /dev/null +++ b/drivers/bluetooth/btuart.c @@ -0,0 +1,441 @@ +/* + * + * Generic Bluetooth HCI UART driver + * + * Copyright (C) 2015-2018 Intel Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/skbuff.h> +#include <linux/serdev.h> +#include <linux/of.h> +#include <linux/firmware.h> +#include <asm/unaligned.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#include "h4_recv.h" +#include "btbcm.h" + +#define VERSION "1.0" + +struct btuart_dev { + struct hci_dev *hdev; + struct serdev_device *serdev; + + struct work_struct tx_work; + unsigned long tx_state; + struct sk_buff_head txq; + + struct sk_buff *rx_skb; +}; + +#define BTUART_TX_STATE_ACTIVE 1 +#define BTUART_TX_STATE_WAKEUP 2 + +#define BCM_NULL_PKT 0x00 +#define BCM_NULL_SIZE 0 + +#define BCM_LM_DIAG_PKT 0x07 +#define BCM_LM_DIAG_SIZE 63 + +#define BCM_RECV_LM_DIAG \ + .type = BCM_LM_DIAG_PKT, \ + .hlen = BCM_LM_DIAG_SIZE, \ + .loff = 0, \ + .lsize = 0, \ + .maxlen = BCM_LM_DIAG_SIZE + +#define BCM_RECV_NULL \ + .type = BCM_NULL_PKT, \ + .hlen = BCM_NULL_SIZE, \ + .loff = 0, \ + .lsize = 0, \ + .maxlen = BCM_NULL_SIZE + +static const struct h4_recv_pkt bcm_recv_pkts[] = { + { H4_RECV_ACL, .recv = hci_recv_frame }, + { H4_RECV_SCO, .recv = hci_recv_frame }, + { H4_RECV_EVENT, .recv = hci_recv_frame }, + { BCM_RECV_LM_DIAG, .recv = hci_recv_diag }, + { BCM_RECV_NULL, .recv = hci_recv_diag }, +}; + +static void btuart_tx_work(struct work_struct *work) +{ + struct btuart_dev *bdev = container_of(work, struct btuart_dev, + tx_work); + struct serdev_device *serdev = bdev->serdev; + struct hci_dev *hdev = bdev->hdev; + + while (1) { + clear_bit(BTUART_TX_STATE_WAKEUP, &bdev->tx_state); + + while (1) { + struct sk_buff *skb = skb_dequeue(&bdev->txq); + int len; + + if (!skb) + break; + + len = serdev_device_write_buf(serdev, skb->data, + skb->len); + hdev->stat.byte_tx += len; + + skb_pull(skb, len); + if (skb->len > 0) { + skb_queue_head(&bdev->txq, skb); + break; + } + + switch (hci_skb_pkt_type(skb)) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + } + + kfree_skb(skb); + } + + if (!test_bit(BTUART_TX_STATE_WAKEUP, &bdev->tx_state)) + break; + } + + clear_bit(BTUART_TX_STATE_ACTIVE, &bdev->tx_state); +} + +static int btuart_tx_wakeup(struct btuart_dev *bdev) +{ + if (test_and_set_bit(BTUART_TX_STATE_ACTIVE, &bdev->tx_state)) { + set_bit(BTUART_TX_STATE_WAKEUP, &bdev->tx_state); + return 0; + } + + schedule_work(&bdev->tx_work); + return 0; +} + +static int btuart_open(struct hci_dev *hdev) +{ + struct btuart_dev *bdev = hci_get_drvdata(hdev); + int err; + + err = serdev_device_open(bdev->serdev); + if (err) { + bt_dev_err(hdev, "Unable to open UART device %s", + dev_name(&bdev->serdev->dev)); + return err; + } + + return 0; +} + +static int btuart_close(struct hci_dev *hdev) +{ + struct btuart_dev *bdev = hci_get_drvdata(hdev); + + serdev_device_close(bdev->serdev); + + return 0; +} + +static int btuart_flush(struct hci_dev *hdev) +{ + struct btuart_dev *bdev = hci_get_drvdata(hdev); + + /* Flush any pending characters */ + serdev_device_write_flush(bdev->serdev); + skb_queue_purge(&bdev->txq); + + cancel_work_sync(&bdev->tx_work); + + kfree_skb(bdev->rx_skb); + bdev->rx_skb = NULL; + + return 0; +} + +static int bcm_set_diag(struct hci_dev *hdev, bool enable) +{ + struct btuart_dev *bdev = hci_get_drvdata(hdev); + struct sk_buff *skb; + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -ENETDOWN; + + skb = bt_skb_alloc(3, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + skb_put_u8(skb, BCM_LM_DIAG_PKT); + skb_put_u8(skb, 0xf0); + skb_put_u8(skb, enable); + + skb_queue_tail(&bdev->txq, skb); + btuart_tx_wakeup(bdev); + + return 0; +} + +static int bcm_set_baudrate(struct btuart_dev *bdev, unsigned int speed) +{ + struct hci_dev *hdev = bdev->hdev; + struct sk_buff *skb; + struct bcm_update_uart_baud_rate param; + + if (speed > 3000000) { + struct bcm_write_uart_clock_setting clock; + + clock.type = BCM_UART_CLOCK_48MHZ; + + bt_dev_dbg(hdev, "Set Controller clock (%d)", clock.type); + + /* This Broadcom specific command changes the UART's controller + * clock for baud rate > 3000000. + */ + skb = __hci_cmd_sync(hdev, 0xfc45, 1, &clock, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + int err = PTR_ERR(skb); + bt_dev_err(hdev, "Failed to write clock (%d)", err); + return err; + } + + kfree_skb(skb); + } + + bt_dev_dbg(hdev, "Set Controller UART speed to %d bit/s", speed); + + param.zero = cpu_to_le16(0); + param.baud_rate = cpu_to_le32(speed); + + /* This Broadcom specific command changes the UART's controller baud + * rate. + */ + skb = __hci_cmd_sync(hdev, 0xfc18, sizeof(param), ¶m, + HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + int err = PTR_ERR(skb); + bt_dev_err(hdev, "Failed to write update baudrate (%d)", err); + return err; + } + + kfree_skb(skb); + + return 0; +} + +static int btuart_setup(struct hci_dev *hdev) +{ + struct btuart_dev *bdev = hci_get_drvdata(hdev); + char fw_name[64]; + const struct firmware *fw; + unsigned int speed; + int err; + + /* Init speed if any */ + speed = 115200; + + if (speed) + serdev_device_set_baudrate(bdev->serdev, speed); + + /* Operational speed if any */ + speed = 115200; + + if (speed) { + err = bcm_set_baudrate(bdev, speed); + if (err) + bt_dev_err(hdev, "Failed to set baudrate"); + else + serdev_device_set_baudrate(bdev->serdev, speed); + } + + hdev->set_diag = bcm_set_diag; + hdev->set_bdaddr = btbcm_set_bdaddr; + + err = btbcm_initialize(hdev, fw_name, sizeof(fw_name)); + if (err) + return err; + + err = request_firmware(&fw, fw_name, &hdev->dev); + if (err < 0) { + bt_dev_warn(hdev, "Patch %s not found", fw_name); + return 0; + } + + err = btbcm_patchram(bdev->hdev, fw); + if (err) { + bt_dev_err(hdev, "Patching failed (%d)", err); + goto finalize; + } + + /* Init speed if any */ + speed = 115200; + + if (speed) + serdev_device_set_baudrate(bdev->serdev, speed); + + /* Operational speed if any */ + speed = 115200; + + if (speed) { + err = bcm_set_baudrate(bdev, speed); + if (!err) + serdev_device_set_baudrate(bdev->serdev, speed); + } + +finalize: + release_firmware(fw); + + err = btbcm_finalize(hdev); + if (err) + return err; + + return err; +} + +static int btuart_send_frame(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct btuart_dev *bdev = hci_get_drvdata(hdev); + + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &hci_skb_pkt_type(skb), 1); + skb_queue_tail(&bdev->txq, skb); + + btuart_tx_wakeup(bdev); + return 0; +} + +static int btuart_receive_buf(struct serdev_device *serdev, const u8 *data, + size_t count) +{ + struct btuart_dev *bdev = serdev_device_get_drvdata(serdev); + + bdev->rx_skb = h4_recv_buf(bdev->hdev, bdev->rx_skb, data, count, + bcm_recv_pkts, ARRAY_SIZE(bcm_recv_pkts)); + if (IS_ERR(bdev->rx_skb)) { + int err = PTR_ERR(bdev->rx_skb); + bt_dev_err(bdev->hdev, "Frame reassembly failed (%d)", err); + bdev->rx_skb = NULL; + return err; + } + + bdev->hdev->stat.byte_rx += count; + + return count; +} + +static void btuart_write_wakeup(struct serdev_device *serdev) +{ + struct btuart_dev *bdev = serdev_device_get_drvdata(serdev); + + btuart_tx_wakeup(bdev); +} + +static const struct serdev_device_ops btuart_client_ops = { + .receive_buf = btuart_receive_buf, + .write_wakeup = btuart_write_wakeup, +}; + +static int btuart_probe(struct serdev_device *serdev) +{ + struct btuart_dev *bdev; + struct hci_dev *hdev; + + bdev = devm_kzalloc(&serdev->dev, sizeof(*bdev), GFP_KERNEL); + if (!bdev) + return -ENOMEM; + + bdev->serdev = serdev; + serdev_device_set_drvdata(serdev, bdev); + + serdev_device_set_client_ops(serdev, &btuart_client_ops); + + INIT_WORK(&bdev->tx_work, btuart_tx_work); + skb_queue_head_init(&bdev->txq); + + /* Initialize and register HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + dev_err(&serdev->dev, "Can't allocate HCI device\n"); + return -ENOMEM; + } + + bdev->hdev = hdev; + + hdev->bus = HCI_UART; + hci_set_drvdata(hdev, bdev); + + hdev->manufacturer = 15; + + hdev->open = btuart_open; + hdev->close = btuart_close; + hdev->flush = btuart_flush; + hdev->setup = btuart_setup; + hdev->send = btuart_send_frame; + SET_HCIDEV_DEV(hdev, &serdev->dev); + + if (hci_register_dev(hdev) < 0) { + dev_err(&serdev->dev, "Can't register HCI device\n"); + hci_free_dev(hdev); + return -ENODEV; + } + + return 0; +} + +static void btuart_remove(struct serdev_device *serdev) +{ + struct btuart_dev *bdev = serdev_device_get_drvdata(serdev); + struct hci_dev *hdev = bdev->hdev; + + hci_unregister_dev(hdev); + hci_free_dev(hdev); +} + +#ifdef CONFIG_OF +static const struct of_device_id btuart_of_match[] = { + { .compatible = "brcm,bcm43438-bt" }, + { }, +}; +MODULE_DEVICE_TABLE(of, btuart_of_match); +#endif + +static struct serdev_device_driver btuart_driver = { + .probe = btuart_probe, + .remove = btuart_remove, + .driver = { + .name = "btuart", + .of_match_table = of_match_ptr(btuart_of_match), + }, +}; + +module_serdev_device_driver(btuart_driver); + +MODULE_AUTHOR("Marcel Holtmann <marcel@xxxxxxxxxxxx>"); +MODULE_DESCRIPTION("Generic Bluetooth UART driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/bluetooth/h4_recv.h b/drivers/bluetooth/h4_recv.h new file mode 100644 index 000000000000..b0d5babca392 --- /dev/null +++ b/drivers/bluetooth/h4_recv.h @@ -0,0 +1,158 @@ +/* + * + * Generic Bluetooth HCI UART driver + * + * Copyright (C) 2015-2018 Intel Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +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 + +static inline struct sk_buff *h4_recv_buf(struct hci_dev *hdev, + struct sk_buff *skb, + const unsigned char *buffer, + int count, + const struct h4_recv_pkt *pkts, + int pkts_count) +{ + while (count) { + int i, len; + + 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) { + /* No more data, complete frame */ + (&pkts[i])->recv(hdev, skb); + skb = NULL; + } + } else { + /* Complete frame */ + (&pkts[i])->recv(hdev, skb); + skb = NULL; + } + } + + return skb; +} -- 2.14.3 -- To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html