fw/sercomm: Additional locking needed in drv_pull
[osmocom-bb.git] / src / target / firmware / comm / sercomm.c
1 /* Serial communications layer, based on HDLC */
2
3 /* (C) 2010 by Harald Welte <laforge@gnumonks.org>
4  *
5  * All Rights Reserved
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  */
22
23 #include <stdint.h>
24 #include <stdio.h>
25 #include <errno.h>
26
27 #include <osmocom/core/msgb.h>
28
29 #ifdef HOST_BUILD
30
31 # define SERCOMM_RX_MSG_SIZE    2048
32 # ifndef ARRAY_SIZE
33 #  define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
34 # endif
35 # include <sercomm.h>
36
37 static inline void sercomm_lock(unsigned long __attribute__((unused)) *flags) {}
38 static inline void sercomm_unlock(unsigned long __attribute__((unused)) *flags) {}
39
40 #else
41
42 # define SERCOMM_RX_MSG_SIZE    256
43 # include <debug.h>
44 # include <osmocom/core/linuxlist.h>
45 # include <asm/system.h>
46
47 static inline void sercomm_lock(unsigned long *flags)
48 {
49         local_firq_save(*flags);
50 }
51
52 static inline void sercomm_unlock(unsigned long *flags)
53 {
54         local_irq_restore(*flags);
55 }
56
57 # include <comm/sercomm.h>
58 # include <uart.h>
59
60 #endif
61
62
63 enum rx_state {
64         RX_ST_WAIT_START,
65         RX_ST_ADDR,
66         RX_ST_CTRL,
67         RX_ST_DATA,
68         RX_ST_ESCAPE,
69 };
70
71 static struct {
72         int initialized;
73
74         /* transmit side */
75         struct {
76                 struct llist_head dlci_queues[_SC_DLCI_MAX];
77                 struct msgb *msg;
78                 enum rx_state state;
79                 uint8_t *next_char;
80         } tx;
81
82         /* receive side */
83         struct {
84                 dlci_cb_t dlci_handler[_SC_DLCI_MAX];
85                 struct msgb *msg;
86                 enum rx_state state;
87                 uint8_t dlci;
88                 uint8_t ctrl;
89         } rx;
90         
91 } sercomm;
92
93 void sercomm_init(void)
94 {
95         unsigned int i;
96         for (i = 0; i < ARRAY_SIZE(sercomm.tx.dlci_queues); i++)
97                 INIT_LLIST_HEAD(&sercomm.tx.dlci_queues[i]);
98
99         sercomm.rx.msg = NULL;
100         sercomm.initialized = 1;
101
102         /* set up the echo dlci */
103         sercomm_register_rx_cb(SC_DLCI_ECHO, &sercomm_sendmsg);
104 }
105
106 int sercomm_initialized(void)
107 {
108         return sercomm.initialized;
109 }
110
111 /* user interface for transmitting messages for a given DLCI */
112 void sercomm_sendmsg(uint8_t dlci, struct msgb *msg)
113 {
114         unsigned long flags;
115         uint8_t *hdr;
116
117         /* prepend address + control octet */
118         hdr = msgb_push(msg, 2);
119         hdr[0] = dlci;
120         hdr[1] = HDLC_C_UI;
121
122         /* This functiion can be called from any context: FIQ, IRQ
123          * and supervisor context.  Proper locking is important! */
124         sercomm_lock(&flags);
125         msgb_enqueue(&sercomm.tx.dlci_queues[dlci], msg);
126         sercomm_unlock(&flags);
127
128 #ifndef HOST_BUILD
129         /* tell UART that we have something to send */
130         uart_irq_enable(SERCOMM_UART_NR, UART_IRQ_TX_EMPTY, 1);
131 #endif
132 }
133
134 /* how deep is the Tx queue for a given DLCI */
135 unsigned int sercomm_tx_queue_depth(uint8_t dlci)
136 {
137         struct llist_head *le;
138         unsigned int num = 0;
139
140         llist_for_each(le, &sercomm.tx.dlci_queues[dlci]) {
141                 num++;
142         }
143
144         return num;
145 }
146
147 /* fetch one octet of to-be-transmitted serial data */
148 int sercomm_drv_pull(uint8_t *ch)
149 {
150         unsigned long flags;
151
152         /* we may be called from interrupt context, but we stiff need to lock
153          * because sercomm could be accessed from a FIQ context ... */
154
155         sercomm_lock(&flags);
156
157         if (!sercomm.tx.msg) {
158                 unsigned int i;
159                 /* dequeue a new message from the queues */
160                 for (i = 0; i < ARRAY_SIZE(sercomm.tx.dlci_queues); i++) {
161                         sercomm.tx.msg = msgb_dequeue(&sercomm.tx.dlci_queues[i]);
162                         if (sercomm.tx.msg)
163                                 break;
164                 }
165                 if (sercomm.tx.msg) {
166                         /* start of a new message, send start flag octet */
167                         *ch = HDLC_FLAG;
168                         sercomm.tx.next_char = sercomm.tx.msg->data;
169                         sercomm_unlock(&flags);
170                         return 1;
171                 } else {
172                         /* no more data avilable */
173                         sercomm_unlock(&flags);
174                         return 0;
175                 }
176         }
177
178         if (sercomm.tx.state == RX_ST_ESCAPE) {
179                 /* we've already transmitted the ESCAPE octet,
180                  * we now need to transmit the escaped data */
181                 *ch = *sercomm.tx.next_char++;
182                 sercomm.tx.state = RX_ST_DATA;
183         } else if (sercomm.tx.next_char >= sercomm.tx.msg->tail) {
184                 /* last character has already been transmitted,
185                  * send end-of-message octet */
186                 *ch = HDLC_FLAG;
187                 /* we've reached the end of the message buffer */
188                 msgb_free(sercomm.tx.msg);
189                 sercomm.tx.msg = NULL;
190                 sercomm.tx.next_char = NULL;
191         /* escaping for the two control octets */
192         } else if (*sercomm.tx.next_char == HDLC_FLAG ||
193                    *sercomm.tx.next_char == HDLC_ESCAPE ||
194                    *sercomm.tx.next_char == 0x00) {
195                 /* send an escape octet */
196                 *ch = HDLC_ESCAPE;
197                 /* invert bit 5 of the next octet to be sent */
198                 *sercomm.tx.next_char ^= (1 << 5);
199                 sercomm.tx.state = RX_ST_ESCAPE;
200         } else {
201                 /* standard case, simply send next octet */
202                 *ch = *sercomm.tx.next_char++;
203         }
204
205         sercomm_unlock(&flags);
206         return 1;
207 }
208
209 /* register a handler for a given DLCI */
210 int sercomm_register_rx_cb(uint8_t dlci, dlci_cb_t cb)
211 {
212         if (dlci >= ARRAY_SIZE(sercomm.rx.dlci_handler))
213                 return -EINVAL;
214
215         if (sercomm.rx.dlci_handler[dlci])
216                 return -EBUSY;
217
218         sercomm.rx.dlci_handler[dlci] = cb;
219         return 0;
220 }
221
222 /* dispatch an incoming message once it is completely received */
223 static void dispatch_rx_msg(uint8_t dlci, struct msgb *msg)
224 {
225         if (dlci >= ARRAY_SIZE(sercomm.rx.dlci_handler) ||
226             !sercomm.rx.dlci_handler[dlci]) {
227                 msgb_free(msg);
228                 return;
229         }
230         sercomm.rx.dlci_handler[dlci](dlci, msg);
231 }
232
233 /* the driver has received one byte, pass it into sercomm layer */
234 int sercomm_drv_rx_char(uint8_t ch)
235 {
236         uint8_t *ptr;
237
238         /* we are always called from interrupt context in this function,
239          * which means that any data structures we use need to be for
240          * our exclusive access */
241         if (!sercomm.rx.msg)
242                 sercomm.rx.msg = sercomm_alloc_msgb(SERCOMM_RX_MSG_SIZE);
243
244         if (msgb_tailroom(sercomm.rx.msg) == 0) {
245                 //cons_puts("sercomm_drv_rx_char() overflow!\n");
246                 msgb_free(sercomm.rx.msg);
247                 sercomm.rx.msg = sercomm_alloc_msgb(SERCOMM_RX_MSG_SIZE);
248                 sercomm.rx.state = RX_ST_WAIT_START;
249                 return 0;
250         }
251
252         switch (sercomm.rx.state) {
253         case RX_ST_WAIT_START:
254                 if (ch != HDLC_FLAG)
255                         break;
256                 sercomm.rx.state = RX_ST_ADDR;
257                 break;
258         case RX_ST_ADDR:
259                 sercomm.rx.dlci = ch;
260                 sercomm.rx.state = RX_ST_CTRL;
261                 break;
262         case RX_ST_CTRL:
263                 sercomm.rx.ctrl = ch;
264                 sercomm.rx.state = RX_ST_DATA;
265                 break;
266         case RX_ST_DATA:
267                 if (ch == HDLC_ESCAPE) {
268                         /* drop the escape octet, but change state */
269                         sercomm.rx.state = RX_ST_ESCAPE;
270                         break;
271                 } else if (ch == HDLC_FLAG) {
272                         /* message is finished */
273                         dispatch_rx_msg(sercomm.rx.dlci, sercomm.rx.msg);
274                         /* allocate new buffer */
275                         sercomm.rx.msg = NULL;
276                         /* start all over again */
277                         sercomm.rx.state = RX_ST_WAIT_START;
278
279                         /* do not add the control char */
280                         break;
281                 }
282                 /* default case: store the octet */
283                 ptr = msgb_put(sercomm.rx.msg, 1);
284                 *ptr = ch;
285                 break;
286         case RX_ST_ESCAPE:
287                 /* store bif-5-inverted octet in buffer */
288                 ch ^= (1 << 5);
289                 ptr = msgb_put(sercomm.rx.msg, 1);
290                 *ptr = ch;
291                 /* transition back to normal DATA state */
292                 sercomm.rx.state = RX_ST_DATA;
293                 break;
294         }
295
296         return 1;
297 }