diff options
author | Sjur Braendeland <sjur.brandeland@stericsson.com> | 2010-03-30 09:56:30 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2010-03-30 22:08:50 -0400 |
commit | 9b27105b4a44c54bf91ecd7d0315034ae75684f7 (patch) | |
tree | f23691d85d007358a7e52b92f7f4f6a1d6d9f00c /drivers/net/caif | |
parent | edc7616c307ad315159a8aa050142237f524e079 (diff) |
net-caif-driver: add CAIF serial driver (ldisc)
Add CAIF Serial driver. This driver is implemented as a line discipline.
caif_serial uses the following module parameters:
ser_use_stx - specifies if STart of frame eXtension is in use.
ser_loop - sets the interface in loopback mode.
Signed-off-by: Sjur Braendeland <sjur.brandeland@stericsson.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
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 | 441 |
3 files changed, 470 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..3502f607117f --- /dev/null +++ b/drivers/net/caif/caif_serial.c | |||
@@ -0,0 +1,441 @@ | |||
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 | BUG_ON(skb == NULL); | ||
201 | p = skb_put(skb, count); | ||
202 | memcpy(p, data, count); | ||
203 | |||
204 | skb->protocol = htons(ETH_P_CAIF); | ||
205 | skb_reset_mac_header(skb); | ||
206 | skb->dev = ser->dev; | ||
207 | debugfs_rx(ser, data, count); | ||
208 | /* Push received packet up the stack. */ | ||
209 | ret = netif_rx_ni(skb); | ||
210 | if (!ret) { | ||
211 | ser->dev->stats.rx_packets++; | ||
212 | ser->dev->stats.rx_bytes += count; | ||
213 | } else | ||
214 | ++ser->dev->stats.rx_dropped; | ||
215 | update_tty_status(ser); | ||
216 | } | ||
217 | |||
218 | static int handle_tx(struct ser_device *ser) | ||
219 | { | ||
220 | struct tty_struct *tty; | ||
221 | struct sk_buff *skb; | ||
222 | int tty_wr, len, room; | ||
223 | tty = ser->tty; | ||
224 | ser->tx_started = true; | ||
225 | |||
226 | /* Enter critical section */ | ||
227 | if (test_and_set_bit(CAIF_SENDING, &ser->state)) | ||
228 | return 0; | ||
229 | |||
230 | /* skb_peek is safe because handle_tx is called after skb_queue_tail */ | ||
231 | while ((skb = skb_peek(&ser->head)) != NULL) { | ||
232 | |||
233 | /* Make sure you don't write too much */ | ||
234 | len = skb->len; | ||
235 | room = tty_write_room(tty); | ||
236 | if (!room) | ||
237 | break; | ||
238 | if (room > ser_write_chunk) | ||
239 | room = ser_write_chunk; | ||
240 | if (len > room) | ||
241 | len = room; | ||
242 | |||
243 | /* Write to tty or loopback */ | ||
244 | if (!ser_loop) { | ||
245 | tty_wr = tty->ops->write(tty, skb->data, len); | ||
246 | update_tty_status(ser); | ||
247 | } else { | ||
248 | tty_wr = len; | ||
249 | ldisc_receive(tty, skb->data, NULL, len); | ||
250 | } | ||
251 | ser->dev->stats.tx_packets++; | ||
252 | ser->dev->stats.tx_bytes += tty_wr; | ||
253 | |||
254 | /* Error on TTY ?! */ | ||
255 | if (tty_wr < 0) | ||
256 | goto error; | ||
257 | /* Reduce buffer written, and discard if empty */ | ||
258 | skb_pull(skb, tty_wr); | ||
259 | if (skb->len == 0) { | ||
260 | struct sk_buff *tmp = skb_dequeue(&ser->head); | ||
261 | BUG_ON(tmp != skb); | ||
262 | if (in_interrupt()) | ||
263 | dev_kfree_skb_irq(skb); | ||
264 | else | ||
265 | kfree_skb(skb); | ||
266 | } | ||
267 | } | ||
268 | /* Send flow off if queue is empty */ | ||
269 | if (ser->head.qlen <= SEND_QUEUE_LOW && | ||
270 | test_and_clear_bit(CAIF_FLOW_OFF_SENT, &ser->state) && | ||
271 | ser->common.flowctrl != NULL) | ||
272 | ser->common.flowctrl(ser->dev, ON); | ||
273 | clear_bit(CAIF_SENDING, &ser->state); | ||
274 | return 0; | ||
275 | error: | ||
276 | clear_bit(CAIF_SENDING, &ser->state); | ||
277 | return tty_wr; | ||
278 | } | ||
279 | |||
280 | static int caif_xmit(struct sk_buff *skb, struct net_device *dev) | ||
281 | { | ||
282 | struct ser_device *ser; | ||
283 | BUG_ON(dev == NULL); | ||
284 | ser = netdev_priv(dev); | ||
285 | |||
286 | /* Send flow off once, on high water mark */ | ||
287 | if (ser->head.qlen > SEND_QUEUE_HIGH && | ||
288 | !test_and_set_bit(CAIF_FLOW_OFF_SENT, &ser->state) && | ||
289 | ser->common.flowctrl != NULL) | ||
290 | |||
291 | ser->common.flowctrl(ser->dev, OFF); | ||
292 | |||
293 | skb_queue_tail(&ser->head, skb); | ||
294 | return handle_tx(ser); | ||
295 | } | ||
296 | |||
297 | |||
298 | static void ldisc_tx_wakeup(struct tty_struct *tty) | ||
299 | { | ||
300 | struct ser_device *ser; | ||
301 | ser = tty->disc_data; | ||
302 | BUG_ON(ser == NULL); | ||
303 | BUG_ON(ser->tty != tty); | ||
304 | handle_tx(ser); | ||
305 | } | ||
306 | |||
307 | |||
308 | static int ldisc_open(struct tty_struct *tty) | ||
309 | { | ||
310 | struct ser_device *ser; | ||
311 | struct net_device *dev; | ||
312 | char name[64]; | ||
313 | int result; | ||
314 | |||
315 | sprintf(name, "cf%s", tty->name); | ||
316 | dev = alloc_netdev(sizeof(*ser), name, caifdev_setup); | ||
317 | ser = netdev_priv(dev); | ||
318 | ser->tty = tty; | ||
319 | ser->dev = dev; | ||
320 | debugfs_init(ser, tty); | ||
321 | tty->receive_room = N_TTY_BUF_SIZE; | ||
322 | tty->disc_data = ser; | ||
323 | set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); | ||
324 | rtnl_lock(); | ||
325 | result = register_netdevice(dev); | ||
326 | if (result) { | ||
327 | rtnl_unlock(); | ||
328 | free_netdev(dev); | ||
329 | return -ENODEV; | ||
330 | } | ||
331 | |||
332 | list_add(&ser->node, &ser_list); | ||
333 | rtnl_unlock(); | ||
334 | netif_stop_queue(dev); | ||
335 | update_tty_status(ser); | ||
336 | return 0; | ||
337 | } | ||
338 | |||
339 | static void ldisc_close(struct tty_struct *tty) | ||
340 | { | ||
341 | struct ser_device *ser = tty->disc_data; | ||
342 | /* Remove may be called inside or outside of rtnl_lock */ | ||
343 | int islocked = rtnl_is_locked(); | ||
344 | if (!islocked) | ||
345 | rtnl_lock(); | ||
346 | /* device is freed automagically by net-sysfs */ | ||
347 | dev_close(ser->dev); | ||
348 | unregister_netdevice(ser->dev); | ||
349 | list_del(&ser->node); | ||
350 | debugfs_deinit(ser); | ||
351 | if (!islocked) | ||
352 | rtnl_unlock(); | ||
353 | } | ||
354 | |||
355 | /* The line discipline structure. */ | ||
356 | static struct tty_ldisc_ops caif_ldisc = { | ||
357 | .owner = THIS_MODULE, | ||
358 | .magic = TTY_LDISC_MAGIC, | ||
359 | .name = "n_caif", | ||
360 | .open = ldisc_open, | ||
361 | .close = ldisc_close, | ||
362 | .receive_buf = ldisc_receive, | ||
363 | .write_wakeup = ldisc_tx_wakeup | ||
364 | }; | ||
365 | |||
366 | static int register_ldisc(void) | ||
367 | { | ||
368 | int result; | ||
369 | result = tty_register_ldisc(N_CAIF, &caif_ldisc); | ||
370 | if (result < 0) { | ||
371 | pr_err("cannot register CAIF ldisc=%d err=%d\n", N_CAIF, | ||
372 | result); | ||
373 | return result; | ||
374 | } | ||
375 | return result; | ||
376 | } | ||
377 | static const struct net_device_ops netdev_ops = { | ||
378 | .ndo_open = caif_net_open, | ||
379 | .ndo_stop = caif_net_close, | ||
380 | .ndo_start_xmit = caif_xmit | ||
381 | }; | ||
382 | |||
383 | static void caifdev_setup(struct net_device *dev) | ||
384 | { | ||
385 | struct ser_device *serdev = netdev_priv(dev); | ||
386 | dev->features = 0; | ||
387 | dev->netdev_ops = &netdev_ops; | ||
388 | dev->type = ARPHRD_CAIF; | ||
389 | dev->flags = IFF_POINTOPOINT | IFF_NOARP; | ||
390 | dev->mtu = CAIF_MAX_MTU; | ||
391 | dev->hard_header_len = CAIF_NEEDED_HEADROOM; | ||
392 | dev->tx_queue_len = 0; | ||
393 | dev->destructor = free_netdev; | ||
394 | skb_queue_head_init(&serdev->head); | ||
395 | serdev->common.link_select = CAIF_LINK_LOW_LATENCY; | ||
396 | serdev->common.use_frag = true; | ||
397 | serdev->common.use_stx = ser_use_stx; | ||
398 | serdev->common.use_fcs = ser_use_fcs; | ||
399 | serdev->dev = dev; | ||
400 | } | ||
401 | |||
402 | |||
403 | static int caif_net_open(struct net_device *dev) | ||
404 | { | ||
405 | struct ser_device *ser; | ||
406 | ser = netdev_priv(dev); | ||
407 | netif_wake_queue(dev); | ||
408 | return 0; | ||
409 | } | ||
410 | |||
411 | static int caif_net_close(struct net_device *dev) | ||
412 | { | ||
413 | netif_stop_queue(dev); | ||
414 | return 0; | ||
415 | } | ||
416 | |||
417 | static int __init caif_ser_init(void) | ||
418 | { | ||
419 | int ret; | ||
420 | ret = register_ldisc(); | ||
421 | debugfsdir = debugfs_create_dir("caif_serial", NULL); | ||
422 | return ret; | ||
423 | } | ||
424 | |||
425 | static void __exit caif_ser_exit(void) | ||
426 | { | ||
427 | struct ser_device *ser = NULL; | ||
428 | struct list_head *node; | ||
429 | struct list_head *_tmp; | ||
430 | list_for_each_safe(node, _tmp, &ser_list) { | ||
431 | ser = list_entry(node, struct ser_device, node); | ||
432 | dev_close(ser->dev); | ||
433 | unregister_netdevice(ser->dev); | ||
434 | list_del(node); | ||
435 | } | ||
436 | tty_unregister_ldisc(N_CAIF); | ||
437 | debugfs_remove_recursive(debugfsdir); | ||
438 | } | ||
439 | |||
440 | module_init(caif_ser_init); | ||
441 | module_exit(caif_ser_exit); | ||