diff options
Diffstat (limited to 'drivers/net/caif')
-rw-r--r-- | drivers/net/caif/Kconfig | 17 | ||||
-rw-r--r-- | drivers/net/caif/Makefile | 12 | ||||
-rw-r--r-- | drivers/net/caif/caif_serial.c | 449 |
3 files changed, 478 insertions, 0 deletions
diff --git a/drivers/net/caif/Kconfig b/drivers/net/caif/Kconfig new file mode 100644 index 000000000000..0b28e0107697 --- /dev/null +++ b/drivers/net/caif/Kconfig | |||
@@ -0,0 +1,17 @@ | |||
1 | # | ||
2 | # CAIF physical drivers | ||
3 | # | ||
4 | |||
5 | if CAIF | ||
6 | |||
7 | comment "CAIF transport drivers" | ||
8 | |||
9 | config CAIF_TTY | ||
10 | tristate "CAIF TTY transport driver" | ||
11 | default n | ||
12 | ---help--- | ||
13 | The CAIF TTY transport driver is a Line Discipline (ldisc) | ||
14 | identified as N_CAIF. When this ldisc is opened from user space | ||
15 | it will redirect the TTY's traffic into the CAIF stack. | ||
16 | |||
17 | endif # CAIF | ||
diff --git a/drivers/net/caif/Makefile b/drivers/net/caif/Makefile new file mode 100644 index 000000000000..52b6d1f826f8 --- /dev/null +++ b/drivers/net/caif/Makefile | |||
@@ -0,0 +1,12 @@ | |||
1 | ifeq ($(CONFIG_CAIF_DEBUG),1) | ||
2 | CAIF_DBG_FLAGS := -DDEBUG | ||
3 | endif | ||
4 | |||
5 | KBUILD_EXTRA_SYMBOLS=net/caif/Module.symvers | ||
6 | |||
7 | ccflags-y := $(CAIF_FLAGS) $(CAIF_DBG_FLAGS) | ||
8 | clean-dirs:= .tmp_versions | ||
9 | clean-files:= Module.symvers modules.order *.cmd *~ \ | ||
10 | |||
11 | # Serial interface | ||
12 | obj-$(CONFIG_CAIF_TTY) += caif_serial.o | ||
diff --git a/drivers/net/caif/caif_serial.c b/drivers/net/caif/caif_serial.c new file mode 100644 index 000000000000..09257ca8f563 --- /dev/null +++ b/drivers/net/caif/caif_serial.c | |||
@@ -0,0 +1,449 @@ | |||
1 | /* | ||
2 | * Copyright (C) ST-Ericsson AB 2010 | ||
3 | * Author: Sjur Brendeland / sjur.brandeland@stericsson.com | ||
4 | * License terms: GNU General Public License (GPL) version 2 | ||
5 | */ | ||
6 | |||
7 | #include <linux/init.h> | ||
8 | #include <linux/version.h> | ||
9 | #include <linux/module.h> | ||
10 | #include <linux/device.h> | ||
11 | #include <linux/types.h> | ||
12 | #include <linux/skbuff.h> | ||
13 | #include <linux/netdevice.h> | ||
14 | #include <linux/rtnetlink.h> | ||
15 | #include <linux/tty.h> | ||
16 | #include <linux/file.h> | ||
17 | #include <linux/if_arp.h> | ||
18 | #include <net/caif/caif_device.h> | ||
19 | #include <net/caif/cfcnfg.h> | ||
20 | #include <linux/err.h> | ||
21 | #include <linux/debugfs.h> | ||
22 | |||
23 | MODULE_LICENSE("GPL"); | ||
24 | MODULE_AUTHOR("Sjur Brendeland<sjur.brandeland@stericsson.com>"); | ||
25 | MODULE_DESCRIPTION("CAIF serial device TTY line discipline"); | ||
26 | MODULE_LICENSE("GPL"); | ||
27 | MODULE_ALIAS_LDISC(N_CAIF); | ||
28 | |||
29 | #define SEND_QUEUE_LOW 10 | ||
30 | #define SEND_QUEUE_HIGH 100 | ||
31 | #define CAIF_SENDING 1 /* Bit 1 = 0x02*/ | ||
32 | #define CAIF_FLOW_OFF_SENT 4 /* Bit 4 = 0x10 */ | ||
33 | #define MAX_WRITE_CHUNK 4096 | ||
34 | #define ON 1 | ||
35 | #define OFF 0 | ||
36 | #define CAIF_MAX_MTU 4096 | ||
37 | |||
38 | /*This list is protected by the rtnl lock. */ | ||
39 | static LIST_HEAD(ser_list); | ||
40 | |||
41 | static int ser_loop; | ||
42 | module_param(ser_loop, bool, S_IRUGO); | ||
43 | MODULE_PARM_DESC(ser_loop, "Run in simulated loopback mode."); | ||
44 | |||
45 | static int ser_use_stx = 1; | ||
46 | module_param(ser_use_stx, bool, S_IRUGO); | ||
47 | MODULE_PARM_DESC(ser_use_stx, "STX enabled or not."); | ||
48 | |||
49 | static int ser_use_fcs = 1; | ||
50 | |||
51 | module_param(ser_use_fcs, bool, S_IRUGO); | ||
52 | MODULE_PARM_DESC(ser_use_fcs, "FCS enabled or not."); | ||
53 | |||
54 | static int ser_write_chunk = MAX_WRITE_CHUNK; | ||
55 | module_param(ser_write_chunk, int, S_IRUGO); | ||
56 | |||
57 | MODULE_PARM_DESC(ser_write_chunk, "Maximum size of data written to UART."); | ||
58 | |||
59 | static struct dentry *debugfsdir; | ||
60 | |||
61 | static int caif_net_open(struct net_device *dev); | ||
62 | static int caif_net_close(struct net_device *dev); | ||
63 | |||
64 | struct ser_device { | ||
65 | struct caif_dev_common common; | ||
66 | struct list_head node; | ||
67 | struct net_device *dev; | ||
68 | struct sk_buff_head head; | ||
69 | struct tty_struct *tty; | ||
70 | bool tx_started; | ||
71 | unsigned long state; | ||
72 | char *tty_name; | ||
73 | #ifdef CONFIG_DEBUG_FS | ||
74 | struct dentry *debugfs_tty_dir; | ||
75 | struct debugfs_blob_wrapper tx_blob; | ||
76 | struct debugfs_blob_wrapper rx_blob; | ||
77 | u8 rx_data[128]; | ||
78 | u8 tx_data[128]; | ||
79 | u8 tty_status; | ||
80 | |||
81 | #endif | ||
82 | }; | ||
83 | |||
84 | static void caifdev_setup(struct net_device *dev); | ||
85 | static void ldisc_tx_wakeup(struct tty_struct *tty); | ||
86 | #ifdef CONFIG_DEBUG_FS | ||
87 | static inline void update_tty_status(struct ser_device *ser) | ||
88 | { | ||
89 | ser->tty_status = | ||
90 | ser->tty->stopped << 5 | | ||
91 | ser->tty->hw_stopped << 4 | | ||
92 | ser->tty->flow_stopped << 3 | | ||
93 | ser->tty->packet << 2 | | ||
94 | ser->tty->low_latency << 1 | | ||
95 | ser->tty->warned; | ||
96 | } | ||
97 | static inline void debugfs_init(struct ser_device *ser, struct tty_struct *tty) | ||
98 | { | ||
99 | ser->debugfs_tty_dir = | ||
100 | debugfs_create_dir(tty->name, debugfsdir); | ||
101 | if (!IS_ERR(ser->debugfs_tty_dir)) { | ||
102 | debugfs_create_blob("last_tx_msg", S_IRUSR, | ||
103 | ser->debugfs_tty_dir, | ||
104 | &ser->tx_blob); | ||
105 | |||
106 | debugfs_create_blob("last_rx_msg", S_IRUSR, | ||
107 | ser->debugfs_tty_dir, | ||
108 | &ser->rx_blob); | ||
109 | |||
110 | debugfs_create_x32("ser_state", S_IRUSR, | ||
111 | ser->debugfs_tty_dir, | ||
112 | (u32 *)&ser->state); | ||
113 | |||
114 | debugfs_create_x8("tty_status", S_IRUSR, | ||
115 | ser->debugfs_tty_dir, | ||
116 | &ser->tty_status); | ||
117 | |||
118 | } | ||
119 | ser->tx_blob.data = ser->tx_data; | ||
120 | ser->tx_blob.size = 0; | ||
121 | ser->rx_blob.data = ser->rx_data; | ||
122 | ser->rx_blob.size = 0; | ||
123 | } | ||
124 | |||
125 | static inline void debugfs_deinit(struct ser_device *ser) | ||
126 | { | ||
127 | debugfs_remove_recursive(ser->debugfs_tty_dir); | ||
128 | } | ||
129 | |||
130 | static inline void debugfs_rx(struct ser_device *ser, const u8 *data, int size) | ||
131 | { | ||
132 | if (size > sizeof(ser->rx_data)) | ||
133 | size = sizeof(ser->rx_data); | ||
134 | memcpy(ser->rx_data, data, size); | ||
135 | ser->rx_blob.data = ser->rx_data; | ||
136 | ser->rx_blob.size = size; | ||
137 | } | ||
138 | |||
139 | static inline void debugfs_tx(struct ser_device *ser, const u8 *data, int size) | ||
140 | { | ||
141 | if (size > sizeof(ser->tx_data)) | ||
142 | size = sizeof(ser->tx_data); | ||
143 | memcpy(ser->tx_data, data, size); | ||
144 | ser->tx_blob.data = ser->tx_data; | ||
145 | ser->tx_blob.size = size; | ||
146 | } | ||
147 | #else | ||
148 | static inline void debugfs_init(struct ser_device *ser, struct tty_struct *tty) | ||
149 | { | ||
150 | } | ||
151 | |||
152 | static inline void debugfs_deinit(struct ser_device *ser) | ||
153 | { | ||
154 | } | ||
155 | |||
156 | static inline void update_tty_status(struct ser_device *ser) | ||
157 | { | ||
158 | } | ||
159 | |||
160 | static inline void debugfs_rx(struct ser_device *ser, const u8 *data, int size) | ||
161 | { | ||
162 | } | ||
163 | |||
164 | static inline void debugfs_tx(struct ser_device *ser, const u8 *data, int size) | ||
165 | { | ||
166 | } | ||
167 | |||
168 | #endif | ||
169 | |||
170 | static void ldisc_receive(struct tty_struct *tty, const u8 *data, | ||
171 | char *flags, int count) | ||
172 | { | ||
173 | struct sk_buff *skb = NULL; | ||
174 | struct ser_device *ser; | ||
175 | int ret; | ||
176 | u8 *p; | ||
177 | ser = tty->disc_data; | ||
178 | |||
179 | /* | ||
180 | * NOTE: flags may contain information about break or overrun. | ||
181 | * This is not yet handled. | ||
182 | */ | ||
183 | |||
184 | |||
185 | /* | ||
186 | * Workaround for garbage at start of transmission, | ||
187 | * only enable if STX handling is not enabled. | ||
188 | */ | ||
189 | if (!ser->common.use_stx && !ser->tx_started) { | ||
190 | dev_info(&ser->dev->dev, | ||
191 | "Bytes received before initial transmission -" | ||
192 | "bytes discarded.\n"); | ||
193 | return; | ||
194 | } | ||
195 | |||
196 | BUG_ON(ser->dev == NULL); | ||
197 | |||
198 | /* Get a suitable caif packet and copy in data. */ | ||
199 | skb = netdev_alloc_skb(ser->dev, count+1); | ||
200 | if (skb == NULL) | ||
201 | return; | ||
202 | p = skb_put(skb, count); | ||
203 | memcpy(p, data, count); | ||
204 | |||
205 | skb->protocol = htons(ETH_P_CAIF); | ||
206 | skb_reset_mac_header(skb); | ||
207 | skb->dev = ser->dev; | ||
208 | debugfs_rx(ser, data, count); | ||
209 | /* Push received packet up the stack. */ | ||
210 | ret = netif_rx_ni(skb); | ||
211 | if (!ret) { | ||
212 | ser->dev->stats.rx_packets++; | ||
213 | ser->dev->stats.rx_bytes += count; | ||
214 | } else | ||
215 | ++ser->dev->stats.rx_dropped; | ||
216 | update_tty_status(ser); | ||
217 | } | ||
218 | |||
219 | static int handle_tx(struct ser_device *ser) | ||
220 | { | ||
221 | struct tty_struct *tty; | ||
222 | struct sk_buff *skb; | ||
223 | int tty_wr, len, room; | ||
224 | tty = ser->tty; | ||
225 | ser->tx_started = true; | ||
226 | |||
227 | /* Enter critical section */ | ||
228 | if (test_and_set_bit(CAIF_SENDING, &ser->state)) | ||
229 | return 0; | ||
230 | |||
231 | /* skb_peek is safe because handle_tx is called after skb_queue_tail */ | ||
232 | while ((skb = skb_peek(&ser->head)) != NULL) { | ||
233 | |||
234 | /* Make sure you don't write too much */ | ||
235 | len = skb->len; | ||
236 | room = tty_write_room(tty); | ||
237 | if (!room) | ||
238 | break; | ||
239 | if (room > ser_write_chunk) | ||
240 | room = ser_write_chunk; | ||
241 | if (len > room) | ||
242 | len = room; | ||
243 | |||
244 | /* Write to tty or loopback */ | ||
245 | if (!ser_loop) { | ||
246 | tty_wr = tty->ops->write(tty, skb->data, len); | ||
247 | update_tty_status(ser); | ||
248 | } else { | ||
249 | tty_wr = len; | ||
250 | ldisc_receive(tty, skb->data, NULL, len); | ||
251 | } | ||
252 | ser->dev->stats.tx_packets++; | ||
253 | ser->dev->stats.tx_bytes += tty_wr; | ||
254 | |||
255 | /* Error on TTY ?! */ | ||
256 | if (tty_wr < 0) | ||
257 | goto error; | ||
258 | /* Reduce buffer written, and discard if empty */ | ||
259 | skb_pull(skb, tty_wr); | ||
260 | if (skb->len == 0) { | ||
261 | struct sk_buff *tmp = skb_dequeue(&ser->head); | ||
262 | BUG_ON(tmp != skb); | ||
263 | if (in_interrupt()) | ||
264 | dev_kfree_skb_irq(skb); | ||
265 | else | ||
266 | kfree_skb(skb); | ||
267 | } | ||
268 | } | ||
269 | /* Send flow off if queue is empty */ | ||
270 | if (ser->head.qlen <= SEND_QUEUE_LOW && | ||
271 | test_and_clear_bit(CAIF_FLOW_OFF_SENT, &ser->state) && | ||
272 | ser->common.flowctrl != NULL) | ||
273 | ser->common.flowctrl(ser->dev, ON); | ||
274 | clear_bit(CAIF_SENDING, &ser->state); | ||
275 | return 0; | ||
276 | error: | ||
277 | clear_bit(CAIF_SENDING, &ser->state); | ||
278 | return tty_wr; | ||
279 | } | ||
280 | |||
281 | static int caif_xmit(struct sk_buff *skb, struct net_device *dev) | ||
282 | { | ||
283 | struct ser_device *ser; | ||
284 | BUG_ON(dev == NULL); | ||
285 | ser = netdev_priv(dev); | ||
286 | |||
287 | /* Send flow off once, on high water mark */ | ||
288 | if (ser->head.qlen > SEND_QUEUE_HIGH && | ||
289 | !test_and_set_bit(CAIF_FLOW_OFF_SENT, &ser->state) && | ||
290 | ser->common.flowctrl != NULL) | ||
291 | |||
292 | ser->common.flowctrl(ser->dev, OFF); | ||
293 | |||
294 | skb_queue_tail(&ser->head, skb); | ||
295 | return handle_tx(ser); | ||
296 | } | ||
297 | |||
298 | |||
299 | static void ldisc_tx_wakeup(struct tty_struct *tty) | ||
300 | { | ||
301 | struct ser_device *ser; | ||
302 | ser = tty->disc_data; | ||
303 | BUG_ON(ser == NULL); | ||
304 | BUG_ON(ser->tty != tty); | ||
305 | handle_tx(ser); | ||
306 | } | ||
307 | |||
308 | |||
309 | static int ldisc_open(struct tty_struct *tty) | ||
310 | { | ||
311 | struct ser_device *ser; | ||
312 | struct net_device *dev; | ||
313 | char name[64]; | ||
314 | int result; | ||
315 | |||
316 | /* No write no play */ | ||
317 | if (tty->ops->write == NULL) | ||
318 | return -EOPNOTSUPP; | ||
319 | if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_TTY_CONFIG)) | ||
320 | return -EPERM; | ||
321 | |||
322 | sprintf(name, "cf%s", tty->name); | ||
323 | dev = alloc_netdev(sizeof(*ser), name, caifdev_setup); | ||
324 | ser = netdev_priv(dev); | ||
325 | ser->tty = tty_kref_get(tty); | ||
326 | ser->dev = dev; | ||
327 | debugfs_init(ser, tty); | ||
328 | tty->receive_room = N_TTY_BUF_SIZE; | ||
329 | tty->disc_data = ser; | ||
330 | set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); | ||
331 | rtnl_lock(); | ||
332 | result = register_netdevice(dev); | ||
333 | if (result) { | ||
334 | rtnl_unlock(); | ||
335 | free_netdev(dev); | ||
336 | return -ENODEV; | ||
337 | } | ||
338 | |||
339 | list_add(&ser->node, &ser_list); | ||
340 | rtnl_unlock(); | ||
341 | netif_stop_queue(dev); | ||
342 | update_tty_status(ser); | ||
343 | return 0; | ||
344 | } | ||
345 | |||
346 | static void ldisc_close(struct tty_struct *tty) | ||
347 | { | ||
348 | struct ser_device *ser = tty->disc_data; | ||
349 | /* Remove may be called inside or outside of rtnl_lock */ | ||
350 | int islocked = rtnl_is_locked(); | ||
351 | if (!islocked) | ||
352 | rtnl_lock(); | ||
353 | /* device is freed automagically by net-sysfs */ | ||
354 | dev_close(ser->dev); | ||
355 | unregister_netdevice(ser->dev); | ||
356 | list_del(&ser->node); | ||
357 | debugfs_deinit(ser); | ||
358 | tty_kref_put(ser->tty); | ||
359 | if (!islocked) | ||
360 | rtnl_unlock(); | ||
361 | } | ||
362 | |||
363 | /* The line discipline structure. */ | ||
364 | static struct tty_ldisc_ops caif_ldisc = { | ||
365 | .owner = THIS_MODULE, | ||
366 | .magic = TTY_LDISC_MAGIC, | ||
367 | .name = "n_caif", | ||
368 | .open = ldisc_open, | ||
369 | .close = ldisc_close, | ||
370 | .receive_buf = ldisc_receive, | ||
371 | .write_wakeup = ldisc_tx_wakeup | ||
372 | }; | ||
373 | |||
374 | static int register_ldisc(void) | ||
375 | { | ||
376 | int result; | ||
377 | result = tty_register_ldisc(N_CAIF, &caif_ldisc); | ||
378 | if (result < 0) { | ||
379 | pr_err("cannot register CAIF ldisc=%d err=%d\n", N_CAIF, | ||
380 | result); | ||
381 | return result; | ||
382 | } | ||
383 | return result; | ||
384 | } | ||
385 | static const struct net_device_ops netdev_ops = { | ||
386 | .ndo_open = caif_net_open, | ||
387 | .ndo_stop = caif_net_close, | ||
388 | .ndo_start_xmit = caif_xmit | ||
389 | }; | ||
390 | |||
391 | static void caifdev_setup(struct net_device *dev) | ||
392 | { | ||
393 | struct ser_device *serdev = netdev_priv(dev); | ||
394 | dev->features = 0; | ||
395 | dev->netdev_ops = &netdev_ops; | ||
396 | dev->type = ARPHRD_CAIF; | ||
397 | dev->flags = IFF_POINTOPOINT | IFF_NOARP; | ||
398 | dev->mtu = CAIF_MAX_MTU; | ||
399 | dev->hard_header_len = CAIF_NEEDED_HEADROOM; | ||
400 | dev->tx_queue_len = 0; | ||
401 | dev->destructor = free_netdev; | ||
402 | skb_queue_head_init(&serdev->head); | ||
403 | serdev->common.link_select = CAIF_LINK_LOW_LATENCY; | ||
404 | serdev->common.use_frag = true; | ||
405 | serdev->common.use_stx = ser_use_stx; | ||
406 | serdev->common.use_fcs = ser_use_fcs; | ||
407 | serdev->dev = dev; | ||
408 | } | ||
409 | |||
410 | |||
411 | static int caif_net_open(struct net_device *dev) | ||
412 | { | ||
413 | struct ser_device *ser; | ||
414 | ser = netdev_priv(dev); | ||
415 | netif_wake_queue(dev); | ||
416 | return 0; | ||
417 | } | ||
418 | |||
419 | static int caif_net_close(struct net_device *dev) | ||
420 | { | ||
421 | netif_stop_queue(dev); | ||
422 | return 0; | ||
423 | } | ||
424 | |||
425 | static int __init caif_ser_init(void) | ||
426 | { | ||
427 | int ret; | ||
428 | ret = register_ldisc(); | ||
429 | debugfsdir = debugfs_create_dir("caif_serial", NULL); | ||
430 | return ret; | ||
431 | } | ||
432 | |||
433 | static void __exit caif_ser_exit(void) | ||
434 | { | ||
435 | struct ser_device *ser = NULL; | ||
436 | struct list_head *node; | ||
437 | struct list_head *_tmp; | ||
438 | list_for_each_safe(node, _tmp, &ser_list) { | ||
439 | ser = list_entry(node, struct ser_device, node); | ||
440 | dev_close(ser->dev); | ||
441 | unregister_netdevice(ser->dev); | ||
442 | list_del(node); | ||
443 | } | ||
444 | tty_unregister_ldisc(N_CAIF); | ||
445 | debugfs_remove_recursive(debugfsdir); | ||
446 | } | ||
447 | |||
448 | module_init(caif_ser_init); | ||
449 | module_exit(caif_ser_exit); | ||