diff options
-rw-r--r-- | drivers/ieee802154/Kconfig | 8 | ||||
-rw-r--r-- | drivers/ieee802154/Makefile | 1 | ||||
-rw-r--r-- | drivers/ieee802154/fakelb.c | 294 |
3 files changed, 303 insertions, 0 deletions
diff --git a/drivers/ieee802154/Kconfig b/drivers/ieee802154/Kconfig index 9b9f43aa2f85..15c064073701 100644 --- a/drivers/ieee802154/Kconfig +++ b/drivers/ieee802154/Kconfig | |||
@@ -19,4 +19,12 @@ config IEEE802154_FAKEHARD | |||
19 | 19 | ||
20 | This driver can also be built as a module. To do so say M here. | 20 | This driver can also be built as a module. To do so say M here. |
21 | The module will be called 'fakehard'. | 21 | The module will be called 'fakehard'. |
22 | config IEEE802154_FAKELB | ||
23 | depends on IEEE802154_DRIVERS && MAC802154 | ||
24 | tristate "IEEE 802.15.4 loopback driver" | ||
25 | ---help--- | ||
26 | Say Y here to enable the fake driver that can emulate a net | ||
27 | of several interconnected radio devices. | ||
22 | 28 | ||
29 | This driver can also be built as a module. To do so say M here. | ||
30 | The module will be called 'fakelb'. | ||
diff --git a/drivers/ieee802154/Makefile b/drivers/ieee802154/Makefile index 800a3894af0d..ea784ea6f0f8 100644 --- a/drivers/ieee802154/Makefile +++ b/drivers/ieee802154/Makefile | |||
@@ -1 +1,2 @@ | |||
1 | obj-$(CONFIG_IEEE802154_FAKEHARD) += fakehard.o | 1 | obj-$(CONFIG_IEEE802154_FAKEHARD) += fakehard.o |
2 | obj-$(CONFIG_IEEE802154_FAKELB) += fakelb.o | ||
diff --git a/drivers/ieee802154/fakelb.c b/drivers/ieee802154/fakelb.c new file mode 100644 index 000000000000..e7456fcd0913 --- /dev/null +++ b/drivers/ieee802154/fakelb.c | |||
@@ -0,0 +1,294 @@ | |||
1 | /* | ||
2 | * Loopback IEEE 802.15.4 interface | ||
3 | * | ||
4 | * Copyright 2007-2012 Siemens AG | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License version 2 | ||
8 | * as published by the Free Software Foundation. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License along | ||
16 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | * | ||
19 | * Written by: | ||
20 | * Sergey Lapin <slapin@ossfans.org> | ||
21 | * Dmitry Eremin-Solenikov <dbaryshkov@gmail.com> | ||
22 | * Alexander Smirnov <alex.bluesman.smirnov@gmail.com> | ||
23 | */ | ||
24 | |||
25 | #include <linux/module.h> | ||
26 | #include <linux/timer.h> | ||
27 | #include <linux/platform_device.h> | ||
28 | #include <linux/netdevice.h> | ||
29 | #include <linux/spinlock.h> | ||
30 | #include <net/mac802154.h> | ||
31 | #include <net/wpan-phy.h> | ||
32 | |||
33 | static int numlbs = 1; | ||
34 | |||
35 | struct fakelb_dev_priv { | ||
36 | struct ieee802154_dev *dev; | ||
37 | |||
38 | struct list_head list; | ||
39 | struct fakelb_priv *fake; | ||
40 | |||
41 | spinlock_t lock; | ||
42 | bool working; | ||
43 | }; | ||
44 | |||
45 | struct fakelb_priv { | ||
46 | struct list_head list; | ||
47 | rwlock_t lock; | ||
48 | }; | ||
49 | |||
50 | static int | ||
51 | fakelb_hw_ed(struct ieee802154_dev *dev, u8 *level) | ||
52 | { | ||
53 | might_sleep(); | ||
54 | BUG_ON(!level); | ||
55 | *level = 0xbe; | ||
56 | |||
57 | return 0; | ||
58 | } | ||
59 | |||
60 | static int | ||
61 | fakelb_hw_channel(struct ieee802154_dev *dev, int page, int channel) | ||
62 | { | ||
63 | pr_debug("set channel to %d\n", channel); | ||
64 | |||
65 | might_sleep(); | ||
66 | dev->phy->current_page = page; | ||
67 | dev->phy->current_channel = channel; | ||
68 | |||
69 | return 0; | ||
70 | } | ||
71 | |||
72 | static void | ||
73 | fakelb_hw_deliver(struct fakelb_dev_priv *priv, struct sk_buff *skb) | ||
74 | { | ||
75 | struct sk_buff *newskb; | ||
76 | |||
77 | spin_lock(&priv->lock); | ||
78 | if (priv->working) { | ||
79 | newskb = pskb_copy(skb, GFP_ATOMIC); | ||
80 | ieee802154_rx_irqsafe(priv->dev, newskb, 0xcc); | ||
81 | } | ||
82 | spin_unlock(&priv->lock); | ||
83 | } | ||
84 | |||
85 | static int | ||
86 | fakelb_hw_xmit(struct ieee802154_dev *dev, struct sk_buff *skb) | ||
87 | { | ||
88 | struct fakelb_dev_priv *priv = dev->priv; | ||
89 | struct fakelb_priv *fake = priv->fake; | ||
90 | |||
91 | might_sleep(); | ||
92 | |||
93 | read_lock_bh(&fake->lock); | ||
94 | if (priv->list.next == priv->list.prev) { | ||
95 | /* we are the only one device */ | ||
96 | fakelb_hw_deliver(priv, skb); | ||
97 | } else { | ||
98 | struct fakelb_dev_priv *dp; | ||
99 | list_for_each_entry(dp, &priv->fake->list, list) { | ||
100 | if (dp != priv && | ||
101 | (dp->dev->phy->current_channel == | ||
102 | priv->dev->phy->current_channel)) | ||
103 | fakelb_hw_deliver(dp, skb); | ||
104 | } | ||
105 | } | ||
106 | read_unlock_bh(&fake->lock); | ||
107 | |||
108 | return 0; | ||
109 | } | ||
110 | |||
111 | static int | ||
112 | fakelb_hw_start(struct ieee802154_dev *dev) { | ||
113 | struct fakelb_dev_priv *priv = dev->priv; | ||
114 | int ret = 0; | ||
115 | |||
116 | spin_lock(&priv->lock); | ||
117 | if (priv->working) | ||
118 | ret = -EBUSY; | ||
119 | else | ||
120 | priv->working = 1; | ||
121 | spin_unlock(&priv->lock); | ||
122 | |||
123 | return ret; | ||
124 | } | ||
125 | |||
126 | static void | ||
127 | fakelb_hw_stop(struct ieee802154_dev *dev) { | ||
128 | struct fakelb_dev_priv *priv = dev->priv; | ||
129 | |||
130 | spin_lock(&priv->lock); | ||
131 | priv->working = 0; | ||
132 | spin_unlock(&priv->lock); | ||
133 | } | ||
134 | |||
135 | static struct ieee802154_ops fakelb_ops = { | ||
136 | .owner = THIS_MODULE, | ||
137 | .xmit = fakelb_hw_xmit, | ||
138 | .ed = fakelb_hw_ed, | ||
139 | .set_channel = fakelb_hw_channel, | ||
140 | .start = fakelb_hw_start, | ||
141 | .stop = fakelb_hw_stop, | ||
142 | }; | ||
143 | |||
144 | /* Number of dummy devices to be set up by this module. */ | ||
145 | module_param(numlbs, int, 0); | ||
146 | MODULE_PARM_DESC(numlbs, " number of pseudo devices"); | ||
147 | |||
148 | static int fakelb_add_one(struct device *dev, struct fakelb_priv *fake) | ||
149 | { | ||
150 | struct fakelb_dev_priv *priv; | ||
151 | int err; | ||
152 | struct ieee802154_dev *ieee; | ||
153 | |||
154 | ieee = ieee802154_alloc_device(sizeof(*priv), &fakelb_ops); | ||
155 | if (!ieee) | ||
156 | return -ENOMEM; | ||
157 | |||
158 | priv = ieee->priv; | ||
159 | priv->dev = ieee; | ||
160 | |||
161 | /* 868 MHz BPSK 802.15.4-2003 */ | ||
162 | ieee->phy->channels_supported[0] |= 1; | ||
163 | /* 915 MHz BPSK 802.15.4-2003 */ | ||
164 | ieee->phy->channels_supported[0] |= 0x7fe; | ||
165 | /* 2.4 GHz O-QPSK 802.15.4-2003 */ | ||
166 | ieee->phy->channels_supported[0] |= 0x7FFF800; | ||
167 | /* 868 MHz ASK 802.15.4-2006 */ | ||
168 | ieee->phy->channels_supported[1] |= 1; | ||
169 | /* 915 MHz ASK 802.15.4-2006 */ | ||
170 | ieee->phy->channels_supported[1] |= 0x7fe; | ||
171 | /* 868 MHz O-QPSK 802.15.4-2006 */ | ||
172 | ieee->phy->channels_supported[2] |= 1; | ||
173 | /* 915 MHz O-QPSK 802.15.4-2006 */ | ||
174 | ieee->phy->channels_supported[2] |= 0x7fe; | ||
175 | /* 2.4 GHz CSS 802.15.4a-2007 */ | ||
176 | ieee->phy->channels_supported[3] |= 0x3fff; | ||
177 | /* UWB Sub-gigahertz 802.15.4a-2007 */ | ||
178 | ieee->phy->channels_supported[4] |= 1; | ||
179 | /* UWB Low band 802.15.4a-2007 */ | ||
180 | ieee->phy->channels_supported[4] |= 0x1e; | ||
181 | /* UWB High band 802.15.4a-2007 */ | ||
182 | ieee->phy->channels_supported[4] |= 0xffe0; | ||
183 | /* 750 MHz O-QPSK 802.15.4c-2009 */ | ||
184 | ieee->phy->channels_supported[5] |= 0xf; | ||
185 | /* 750 MHz MPSK 802.15.4c-2009 */ | ||
186 | ieee->phy->channels_supported[5] |= 0xf0; | ||
187 | /* 950 MHz BPSK 802.15.4d-2009 */ | ||
188 | ieee->phy->channels_supported[6] |= 0x3ff; | ||
189 | /* 950 MHz GFSK 802.15.4d-2009 */ | ||
190 | ieee->phy->channels_supported[6] |= 0x3ffc00; | ||
191 | |||
192 | INIT_LIST_HEAD(&priv->list); | ||
193 | priv->fake = fake; | ||
194 | |||
195 | spin_lock_init(&priv->lock); | ||
196 | |||
197 | ieee->parent = dev; | ||
198 | |||
199 | err = ieee802154_register_device(ieee); | ||
200 | if (err) | ||
201 | goto err_reg; | ||
202 | |||
203 | write_lock_bh(&fake->lock); | ||
204 | list_add_tail(&priv->list, &fake->list); | ||
205 | write_unlock_bh(&fake->lock); | ||
206 | |||
207 | return 0; | ||
208 | |||
209 | err_reg: | ||
210 | ieee802154_free_device(priv->dev); | ||
211 | return err; | ||
212 | } | ||
213 | |||
214 | static void fakelb_del(struct fakelb_dev_priv *priv) | ||
215 | { | ||
216 | write_lock_bh(&priv->fake->lock); | ||
217 | list_del(&priv->list); | ||
218 | write_unlock_bh(&priv->fake->lock); | ||
219 | |||
220 | ieee802154_unregister_device(priv->dev); | ||
221 | ieee802154_free_device(priv->dev); | ||
222 | } | ||
223 | |||
224 | static int __devinit fakelb_probe(struct platform_device *pdev) | ||
225 | { | ||
226 | struct fakelb_priv *priv; | ||
227 | struct fakelb_dev_priv *dp; | ||
228 | int err = -ENOMEM; | ||
229 | int i; | ||
230 | |||
231 | priv = kzalloc(sizeof(struct fakelb_priv), GFP_KERNEL); | ||
232 | if (!priv) | ||
233 | goto err_alloc; | ||
234 | |||
235 | INIT_LIST_HEAD(&priv->list); | ||
236 | rwlock_init(&priv->lock); | ||
237 | |||
238 | for (i = 0; i < numlbs; i++) { | ||
239 | err = fakelb_add_one(&pdev->dev, priv); | ||
240 | if (err < 0) | ||
241 | goto err_slave; | ||
242 | } | ||
243 | |||
244 | platform_set_drvdata(pdev, priv); | ||
245 | dev_info(&pdev->dev, "added ieee802154 hardware\n"); | ||
246 | return 0; | ||
247 | |||
248 | err_slave: | ||
249 | list_for_each_entry(dp, &priv->list, list) | ||
250 | fakelb_del(dp); | ||
251 | kfree(priv); | ||
252 | err_alloc: | ||
253 | return err; | ||
254 | } | ||
255 | |||
256 | static int __devexit fakelb_remove(struct platform_device *pdev) | ||
257 | { | ||
258 | struct fakelb_priv *priv = platform_get_drvdata(pdev); | ||
259 | struct fakelb_dev_priv *dp, *temp; | ||
260 | |||
261 | list_for_each_entry_safe(dp, temp, &priv->list, list) | ||
262 | fakelb_del(dp); | ||
263 | kfree(priv); | ||
264 | |||
265 | return 0; | ||
266 | } | ||
267 | |||
268 | static struct platform_device *ieee802154fake_dev; | ||
269 | |||
270 | static struct platform_driver ieee802154fake_driver = { | ||
271 | .probe = fakelb_probe, | ||
272 | .remove = __devexit_p(fakelb_remove), | ||
273 | .driver = { | ||
274 | .name = "ieee802154fakelb", | ||
275 | .owner = THIS_MODULE, | ||
276 | }, | ||
277 | }; | ||
278 | |||
279 | static __init int fakelb_init_module(void) | ||
280 | { | ||
281 | ieee802154fake_dev = platform_device_register_simple( | ||
282 | "ieee802154fakelb", -1, NULL, 0); | ||
283 | return platform_driver_register(&ieee802154fake_driver); | ||
284 | } | ||
285 | |||
286 | static __exit void fake_remove_module(void) | ||
287 | { | ||
288 | platform_driver_unregister(&ieee802154fake_driver); | ||
289 | platform_device_unregister(ieee802154fake_dev); | ||
290 | } | ||
291 | |||
292 | module_init(fakelb_init_module); | ||
293 | module_exit(fake_remove_module); | ||
294 | MODULE_LICENSE("GPL"); | ||