Commit | Line | Data |
---|---|---|
38aa4da5 PRG |
1 | /** |
2 | * Copyright (c) 2017 Redpine Signals Inc. | |
3 | * | |
4 | * Permission to use, copy, modify, and/or distribute this software for any | |
5 | * purpose with or without fee is hereby granted, provided that the above | |
6 | * copyright notice and this permission notice appear in all copies. | |
7 | * | |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
15 | */ | |
38aa4da5 PRG |
16 | #include <linux/module.h> |
17 | #include <linux/kernel.h> | |
18 | #include <net/bluetooth/bluetooth.h> | |
19 | #include <net/bluetooth/hci_core.h> | |
20 | #include <asm/unaligned.h> | |
21 | #include <net/rsi_91x.h> | |
22 | #include <net/genetlink.h> | |
23 | ||
7cbfd1e2 | 24 | #define RSI_DMA_ALIGN 8 |
38aa4da5 | 25 | #define RSI_FRAME_DESC_SIZE 16 |
7cbfd1e2 | 26 | #define RSI_HEADROOM_FOR_BT_HAL (RSI_FRAME_DESC_SIZE + RSI_DMA_ALIGN) |
38aa4da5 PRG |
27 | |
28 | struct rsi_hci_adapter { | |
29 | void *priv; | |
30 | struct rsi_proto_ops *proto_ops; | |
31 | struct hci_dev *hdev; | |
32 | }; | |
33 | ||
34 | static int rsi_hci_open(struct hci_dev *hdev) | |
35 | { | |
36 | return 0; | |
37 | } | |
38 | ||
39 | static int rsi_hci_close(struct hci_dev *hdev) | |
40 | { | |
41 | return 0; | |
42 | } | |
43 | ||
44 | static int rsi_hci_flush(struct hci_dev *hdev) | |
45 | { | |
46 | return 0; | |
47 | } | |
48 | ||
49 | static int rsi_hci_send_pkt(struct hci_dev *hdev, struct sk_buff *skb) | |
50 | { | |
51 | struct rsi_hci_adapter *h_adapter = hci_get_drvdata(hdev); | |
52 | struct sk_buff *new_skb = NULL; | |
53 | ||
54 | switch (hci_skb_pkt_type(skb)) { | |
55 | case HCI_COMMAND_PKT: | |
56 | hdev->stat.cmd_tx++; | |
57 | break; | |
58 | case HCI_ACLDATA_PKT: | |
59 | hdev->stat.acl_tx++; | |
60 | break; | |
61 | case HCI_SCODATA_PKT: | |
62 | hdev->stat.sco_tx++; | |
63 | break; | |
64 | } | |
65 | ||
66 | if (skb_headroom(skb) < RSI_HEADROOM_FOR_BT_HAL) { | |
67 | /* Insufficient skb headroom - allocate a new skb */ | |
68 | new_skb = skb_realloc_headroom(skb, RSI_HEADROOM_FOR_BT_HAL); | |
69 | if (unlikely(!new_skb)) | |
70 | return -ENOMEM; | |
71 | bt_cb(new_skb)->pkt_type = hci_skb_pkt_type(skb); | |
72 | kfree_skb(skb); | |
73 | skb = new_skb; | |
7cbfd1e2 SKK |
74 | if (!IS_ALIGNED((unsigned long)skb->data, RSI_DMA_ALIGN)) { |
75 | u8 *skb_data = skb->data; | |
76 | int skb_len = skb->len; | |
77 | ||
78 | skb_push(skb, RSI_DMA_ALIGN); | |
79 | skb_pull(skb, PTR_ALIGN(skb->data, | |
80 | RSI_DMA_ALIGN) - skb->data); | |
81 | memmove(skb->data, skb_data, skb_len); | |
82 | skb_trim(skb, skb_len); | |
83 | } | |
38aa4da5 PRG |
84 | } |
85 | ||
86 | return h_adapter->proto_ops->coex_send_pkt(h_adapter->priv, skb, | |
87 | RSI_BT_Q); | |
88 | } | |
89 | ||
90 | static int rsi_hci_recv_pkt(void *priv, const u8 *pkt) | |
91 | { | |
92 | struct rsi_hci_adapter *h_adapter = priv; | |
93 | struct hci_dev *hdev = h_adapter->hdev; | |
94 | struct sk_buff *skb; | |
95 | int pkt_len = get_unaligned_le16(pkt) & 0x0fff; | |
96 | ||
97 | skb = dev_alloc_skb(pkt_len); | |
98 | if (!skb) | |
99 | return -ENOMEM; | |
100 | ||
101 | memcpy(skb->data, pkt + RSI_FRAME_DESC_SIZE, pkt_len); | |
102 | skb_put(skb, pkt_len); | |
103 | h_adapter->hdev->stat.byte_rx += skb->len; | |
104 | ||
105 | hci_skb_pkt_type(skb) = pkt[14]; | |
106 | ||
107 | return hci_recv_frame(hdev, skb); | |
108 | } | |
109 | ||
110 | static int rsi_hci_attach(void *priv, struct rsi_proto_ops *ops) | |
111 | { | |
112 | struct rsi_hci_adapter *h_adapter = NULL; | |
113 | struct hci_dev *hdev; | |
114 | int err = 0; | |
115 | ||
116 | h_adapter = kzalloc(sizeof(*h_adapter), GFP_KERNEL); | |
117 | if (!h_adapter) | |
118 | return -ENOMEM; | |
119 | ||
120 | h_adapter->priv = priv; | |
121 | ops->set_bt_context(priv, h_adapter); | |
122 | h_adapter->proto_ops = ops; | |
123 | ||
124 | hdev = hci_alloc_dev(); | |
125 | if (!hdev) { | |
126 | BT_ERR("Failed to alloc HCI device"); | |
127 | goto err; | |
128 | } | |
129 | ||
130 | h_adapter->hdev = hdev; | |
131 | ||
132 | if (ops->get_host_intf(priv) == RSI_HOST_INTF_SDIO) | |
133 | hdev->bus = HCI_SDIO; | |
134 | else | |
135 | hdev->bus = HCI_USB; | |
136 | ||
137 | hci_set_drvdata(hdev, h_adapter); | |
138 | hdev->dev_type = HCI_PRIMARY; | |
139 | hdev->open = rsi_hci_open; | |
140 | hdev->close = rsi_hci_close; | |
141 | hdev->flush = rsi_hci_flush; | |
142 | hdev->send = rsi_hci_send_pkt; | |
143 | ||
144 | err = hci_register_dev(hdev); | |
145 | if (err < 0) { | |
146 | BT_ERR("HCI registration failed with errcode %d", err); | |
147 | hci_free_dev(hdev); | |
148 | goto err; | |
149 | } | |
150 | ||
151 | return 0; | |
152 | err: | |
153 | h_adapter->hdev = NULL; | |
154 | kfree(h_adapter); | |
155 | return -EINVAL; | |
156 | } | |
157 | ||
158 | static void rsi_hci_detach(void *priv) | |
159 | { | |
160 | struct rsi_hci_adapter *h_adapter = priv; | |
161 | struct hci_dev *hdev; | |
162 | ||
163 | if (!h_adapter) | |
164 | return; | |
165 | ||
166 | hdev = h_adapter->hdev; | |
167 | if (hdev) { | |
168 | hci_unregister_dev(hdev); | |
169 | hci_free_dev(hdev); | |
170 | h_adapter->hdev = NULL; | |
171 | } | |
172 | ||
173 | kfree(h_adapter); | |
174 | } | |
175 | ||
176 | const struct rsi_mod_ops rsi_bt_ops = { | |
177 | .attach = rsi_hci_attach, | |
178 | .detach = rsi_hci_detach, | |
179 | .recv_pkt = rsi_hci_recv_pkt, | |
180 | }; | |
181 | EXPORT_SYMBOL(rsi_bt_ops); | |
182 | ||
183 | static int rsi_91x_bt_module_init(void) | |
184 | { | |
185 | return 0; | |
186 | } | |
187 | ||
188 | static void rsi_91x_bt_module_exit(void) | |
189 | { | |
190 | return; | |
191 | } | |
192 | ||
193 | module_init(rsi_91x_bt_module_init); | |
194 | module_exit(rsi_91x_bt_module_exit); | |
195 | MODULE_AUTHOR("Redpine Signals Inc"); | |
196 | MODULE_DESCRIPTION("RSI BT driver"); | |
197 | MODULE_SUPPORTED_DEVICE("RSI-BT"); | |
198 | MODULE_LICENSE("Dual BSD/GPL"); |