]>
Commit | Line | Data |
---|---|---|
af873fce | 1 | // SPDX-License-Identifier: GPL-2.0-only |
b482cd20 SB |
2 | /* |
3 | * Copyright (C) ST-Ericsson AB 2010 | |
26ee65e6 | 4 | * Author: Sjur Brendeland |
b482cd20 SB |
5 | */ |
6 | ||
b31fa5ba JP |
7 | #define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__ |
8 | ||
b482cd20 SB |
9 | #include <linux/stddef.h> |
10 | #include <linux/spinlock.h> | |
11 | #include <linux/slab.h> | |
5f60d5f6 | 12 | #include <linux/unaligned.h> |
b482cd20 SB |
13 | #include <net/caif/caif_layer.h> |
14 | #include <net/caif/cfsrvl.h> | |
15 | #include <net/caif/cfpkt.h> | |
16 | ||
a7da1f55 | 17 | #define container_obj(layr) container_of(layr, struct cfrfml, serv.layer) |
b482cd20 | 18 | #define RFM_SEGMENTATION_BIT 0x01 |
a7da1f55 | 19 | #define RFM_HEAD_SIZE 7 |
b482cd20 SB |
20 | |
21 | static int cfrfml_receive(struct cflayer *layr, struct cfpkt *pkt); | |
22 | static int cfrfml_transmit(struct cflayer *layr, struct cfpkt *pkt); | |
b482cd20 | 23 | |
a7da1f55 SB |
24 | struct cfrfml { |
25 | struct cfsrvl serv; | |
26 | struct cfpkt *incomplete_frm; | |
27 | int fragment_size; | |
28 | u8 seghead[6]; | |
29 | u16 pdu_size; | |
30 | /* Protects serialized processing of packets */ | |
31 | spinlock_t sync; | |
32 | }; | |
33 | ||
43e36921 | 34 | static void cfrfml_release(struct cflayer *layer) |
a7da1f55 | 35 | { |
43e36921 | 36 | struct cfsrvl *srvl = container_of(layer, struct cfsrvl, layer); |
a7da1f55 SB |
37 | struct cfrfml *rfml = container_obj(&srvl->layer); |
38 | ||
39 | if (rfml->incomplete_frm) | |
40 | cfpkt_destroy(rfml->incomplete_frm); | |
41 | ||
42 | kfree(srvl); | |
43 | } | |
44 | ||
45 | struct cflayer *cfrfml_create(u8 channel_id, struct dev_info *dev_info, | |
3bffc475 | 46 | int mtu_size) |
b482cd20 | 47 | { |
a7da1f55 | 48 | int tmp; |
7ac2ed0c | 49 | struct cfrfml *this = kzalloc(sizeof(struct cfrfml), GFP_ATOMIC); |
b1c74247 | 50 | |
7ac2ed0c | 51 | if (!this) |
b482cd20 | 52 | return NULL; |
b1c74247 | 53 | |
a7da1f55 SB |
54 | cfsrvl_init(&this->serv, channel_id, dev_info, false); |
55 | this->serv.release = cfrfml_release; | |
56 | this->serv.layer.receive = cfrfml_receive; | |
57 | this->serv.layer.transmit = cfrfml_transmit; | |
58 | ||
59 | /* Round down to closest multiple of 16 */ | |
60 | tmp = (mtu_size - RFM_HEAD_SIZE - 6) / 16; | |
61 | tmp *= 16; | |
62 | ||
63 | this->fragment_size = tmp; | |
64 | spin_lock_init(&this->sync); | |
65 | snprintf(this->serv.layer.name, CAIF_LAYER_NAME_SZ, | |
66 | "rfm%d", channel_id); | |
67 | ||
68 | return &this->serv.layer; | |
69 | } | |
70 | ||
71 | static struct cfpkt *rfm_append(struct cfrfml *rfml, char *seghead, | |
3bffc475 | 72 | struct cfpkt *pkt, int *err) |
a7da1f55 SB |
73 | { |
74 | struct cfpkt *tmppkt; | |
75 | *err = -EPROTO; | |
76 | /* n-th but not last segment */ | |
77 | ||
78 | if (cfpkt_extr_head(pkt, seghead, 6) < 0) | |
79 | return NULL; | |
80 | ||
81 | /* Verify correct header */ | |
82 | if (memcmp(seghead, rfml->seghead, 6) != 0) | |
83 | return NULL; | |
84 | ||
85 | tmppkt = cfpkt_append(rfml->incomplete_frm, pkt, | |
86 | rfml->pdu_size + RFM_HEAD_SIZE); | |
87 | ||
88 | /* If cfpkt_append failes input pkts are not freed */ | |
89 | *err = -ENOMEM; | |
90 | if (tmppkt == NULL) | |
91 | return NULL; | |
b1c74247 | 92 | |
a7da1f55 SB |
93 | *err = 0; |
94 | return tmppkt; | |
b482cd20 SB |
95 | } |
96 | ||
b482cd20 SB |
97 | static int cfrfml_receive(struct cflayer *layr, struct cfpkt *pkt) |
98 | { | |
99 | u8 tmp; | |
100 | bool segmented; | |
a7da1f55 SB |
101 | int err; |
102 | u8 seghead[6]; | |
103 | struct cfrfml *rfml; | |
104 | struct cfpkt *tmppkt = NULL; | |
105 | ||
b482cd20 SB |
106 | caif_assert(layr->up != NULL); |
107 | caif_assert(layr->receive != NULL); | |
a7da1f55 SB |
108 | rfml = container_obj(layr); |
109 | spin_lock(&rfml->sync); | |
110 | ||
111 | err = -EPROTO; | |
112 | if (cfpkt_extr_head(pkt, &tmp, 1) < 0) | |
113 | goto out; | |
114 | segmented = tmp & RFM_SEGMENTATION_BIT; | |
115 | ||
116 | if (segmented) { | |
117 | if (rfml->incomplete_frm == NULL) { | |
118 | /* Initial Segment */ | |
e1046841 | 119 | if (cfpkt_peek_head(pkt, rfml->seghead, 6) != 0) |
a7da1f55 SB |
120 | goto out; |
121 | ||
122 | rfml->pdu_size = get_unaligned_le16(rfml->seghead+4); | |
123 | ||
124 | if (cfpkt_erroneous(pkt)) | |
125 | goto out; | |
126 | rfml->incomplete_frm = pkt; | |
127 | pkt = NULL; | |
128 | } else { | |
129 | ||
130 | tmppkt = rfm_append(rfml, seghead, pkt, &err); | |
131 | if (tmppkt == NULL) | |
132 | goto out; | |
133 | ||
134 | if (cfpkt_erroneous(tmppkt)) | |
135 | goto out; | |
136 | ||
137 | rfml->incomplete_frm = tmppkt; | |
138 | ||
139 | ||
140 | if (cfpkt_erroneous(tmppkt)) | |
141 | goto out; | |
142 | } | |
143 | err = 0; | |
144 | goto out; | |
145 | } | |
146 | ||
147 | if (rfml->incomplete_frm) { | |
148 | ||
149 | /* Last Segment */ | |
150 | tmppkt = rfm_append(rfml, seghead, pkt, &err); | |
151 | if (tmppkt == NULL) | |
152 | goto out; | |
153 | ||
154 | if (cfpkt_erroneous(tmppkt)) | |
155 | goto out; | |
156 | ||
157 | rfml->incomplete_frm = NULL; | |
158 | pkt = tmppkt; | |
159 | tmppkt = NULL; | |
160 | ||
161 | /* Verify that length is correct */ | |
449f14f0 | 162 | err = -EPROTO; |
a7da1f55 SB |
163 | if (rfml->pdu_size != cfpkt_getlen(pkt) - RFM_HEAD_SIZE + 1) |
164 | goto out; | |
165 | } | |
166 | ||
167 | err = rfml->serv.layer.up->receive(rfml->serv.layer.up, pkt); | |
168 | ||
169 | out: | |
170 | ||
171 | if (err != 0) { | |
172 | if (tmppkt) | |
173 | cfpkt_destroy(tmppkt); | |
174 | if (pkt) | |
175 | cfpkt_destroy(pkt); | |
176 | if (rfml->incomplete_frm) | |
177 | cfpkt_destroy(rfml->incomplete_frm); | |
178 | rfml->incomplete_frm = NULL; | |
179 | ||
b31fa5ba | 180 | pr_info("Connection error %d triggered on RFM link\n", err); |
a7da1f55 SB |
181 | |
182 | /* Trigger connection error upon failure.*/ | |
183 | layr->up->ctrlcmd(layr->up, CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND, | |
184 | rfml->serv.dev_info.id); | |
185 | } | |
186 | spin_unlock(&rfml->sync); | |
374458b3 DT |
187 | |
188 | if (unlikely(err == -EAGAIN)) | |
189 | /* It is not possible to recover after drop of a fragment */ | |
190 | err = -EIO; | |
191 | ||
a7da1f55 SB |
192 | return err; |
193 | } | |
194 | ||
195 | ||
196 | static int cfrfml_transmit_segment(struct cfrfml *rfml, struct cfpkt *pkt) | |
197 | { | |
005b0b07 | 198 | caif_assert(cfpkt_getlen(pkt) < rfml->fragment_size + RFM_HEAD_SIZE); |
a7da1f55 SB |
199 | |
200 | /* Add info for MUX-layer to route the packet out. */ | |
201 | cfpkt_info(pkt)->channel_id = rfml->serv.layer.id; | |
b482cd20 SB |
202 | |
203 | /* | |
a7da1f55 SB |
204 | * To optimize alignment, we add up the size of CAIF header before |
205 | * payload. | |
b482cd20 | 206 | */ |
a7da1f55 SB |
207 | cfpkt_info(pkt)->hdr_len = RFM_HEAD_SIZE; |
208 | cfpkt_info(pkt)->dev_info = &rfml->serv.dev_info; | |
b482cd20 | 209 | |
a7da1f55 | 210 | return rfml->serv.layer.dn->transmit(rfml->serv.layer.dn, pkt); |
b482cd20 SB |
211 | } |
212 | ||
213 | static int cfrfml_transmit(struct cflayer *layr, struct cfpkt *pkt) | |
214 | { | |
a7da1f55 SB |
215 | int err; |
216 | u8 seg; | |
217 | u8 head[6]; | |
218 | struct cfpkt *rearpkt = NULL; | |
219 | struct cfpkt *frontpkt = pkt; | |
220 | struct cfrfml *rfml = container_obj(layr); | |
b482cd20 SB |
221 | |
222 | caif_assert(layr->dn != NULL); | |
223 | caif_assert(layr->dn->transmit != NULL); | |
224 | ||
a7da1f55 | 225 | if (!cfsrvl_ready(&rfml->serv, &err)) |
374458b3 | 226 | goto out; |
a7da1f55 SB |
227 | |
228 | err = -EPROTO; | |
229 | if (cfpkt_getlen(pkt) <= RFM_HEAD_SIZE-1) | |
230 | goto out; | |
231 | ||
232 | err = 0; | |
233 | if (cfpkt_getlen(pkt) > rfml->fragment_size + RFM_HEAD_SIZE) | |
234 | err = cfpkt_peek_head(pkt, head, 6); | |
235 | ||
e1046841 | 236 | if (err != 0) |
a7da1f55 SB |
237 | goto out; |
238 | ||
239 | while (cfpkt_getlen(frontpkt) > rfml->fragment_size + RFM_HEAD_SIZE) { | |
240 | ||
241 | seg = 1; | |
242 | err = -EPROTO; | |
243 | ||
244 | if (cfpkt_add_head(frontpkt, &seg, 1) < 0) | |
245 | goto out; | |
246 | /* | |
247 | * On OOM error cfpkt_split returns NULL. | |
248 | * | |
249 | * NOTE: Segmented pdu is not correctly aligned. | |
250 | * This has negative performance impact. | |
251 | */ | |
252 | ||
253 | rearpkt = cfpkt_split(frontpkt, rfml->fragment_size); | |
254 | if (rearpkt == NULL) | |
255 | goto out; | |
256 | ||
257 | err = cfrfml_transmit_segment(rfml, frontpkt); | |
258 | ||
374458b3 DT |
259 | if (err != 0) { |
260 | frontpkt = NULL; | |
a7da1f55 | 261 | goto out; |
374458b3 DT |
262 | } |
263 | ||
a7da1f55 SB |
264 | frontpkt = rearpkt; |
265 | rearpkt = NULL; | |
266 | ||
a7da1f55 SB |
267 | err = -EPROTO; |
268 | if (cfpkt_add_head(frontpkt, head, 6) < 0) | |
269 | goto out; | |
b482cd20 | 270 | |
b482cd20 | 271 | } |
a7da1f55 SB |
272 | |
273 | seg = 0; | |
274 | err = -EPROTO; | |
275 | ||
276 | if (cfpkt_add_head(frontpkt, &seg, 1) < 0) | |
277 | goto out; | |
278 | ||
279 | err = cfrfml_transmit_segment(rfml, frontpkt); | |
280 | ||
281 | frontpkt = NULL; | |
282 | out: | |
283 | ||
284 | if (err != 0) { | |
b31fa5ba | 285 | pr_info("Connection error %d triggered on RFM link\n", err); |
a7da1f55 SB |
286 | /* Trigger connection error upon failure.*/ |
287 | ||
288 | layr->up->ctrlcmd(layr->up, CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND, | |
289 | rfml->serv.dev_info.id); | |
290 | ||
291 | if (rearpkt) | |
292 | cfpkt_destroy(rearpkt); | |
293 | ||
374458b3 | 294 | if (frontpkt) |
a7da1f55 | 295 | cfpkt_destroy(frontpkt); |
b482cd20 SB |
296 | } |
297 | ||
a7da1f55 | 298 | return err; |
b482cd20 | 299 | } |