Initial import of OsmocomBB into git repository
[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 #ifdef HOST_BUILD
28 #ifndef ARRAY_SIZE
29 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
30 #endif
31 #include <osmocom/msgb.h>
32 #include <sercomm.h>
33
34 #else
35 #include <debug.h>
36 #include <linuxlist.h>
37
38 #include <comm/msgb.h>
39 #include <comm/sercomm.h>
40 #include <calypso/uart.h>
41 #endif
42
43 #define SERCOMM_RX_MSG_SIZE     256
44
45 enum rx_state {
46         RX_ST_WAIT_START,
47         RX_ST_ADDR,
48         RX_ST_CTRL,
49         RX_ST_DATA,
50         RX_ST_ESCAPE,
51 };
52
53 static struct {
54         int initialized;
55
56         /* transmit side */
57         struct {
58                 struct llist_head dlci_queues[_SC_DLCI_MAX];
59                 struct msgb *msg;
60                 uint8_t *next_char;
61         } tx;
62
63         /* receive side */
64         struct {
65                 dlci_cb_t dlci_handler[_SC_DLCI_MAX];
66                 struct msgb *msg;
67                 enum rx_state state;
68                 uint8_t dlci;
69                 uint8_t ctrl;
70         } rx;
71         
72 } sercomm;
73
74 void sercomm_init(void)
75 {
76         unsigned int i;
77         for (i = 0; i < ARRAY_SIZE(sercomm.tx.dlci_queues); i++)
78                 INIT_LLIST_HEAD(&sercomm.tx.dlci_queues[i]);
79
80         sercomm.rx.msg = NULL;
81         sercomm.initialized = 1;
82 }
83
84 int sercomm_initialized(void)
85 {
86         return sercomm.initialized;
87 }
88
89 /* user interface for transmitting messages for a given DLCI */
90 void sercomm_sendmsg(uint8_t dlci, struct msgb *msg)
91 {
92         uint8_t *hdr;
93
94         /* prepend address + control octet */
95         hdr = msgb_push(msg, 2);
96         hdr[0] = dlci;
97         hdr[1] = HDLC_C_UI;
98         msgb_enqueue(&sercomm.tx.dlci_queues[dlci], msg);
99
100 #ifndef HOST_BUILD
101         /* tell UART that we have something to send */
102         uart_irq_enable(SERCOMM_UART_NR, UART_IRQ_TX_EMPTY, 1);
103 #endif
104 }
105
106 /* how deep is the Tx queue for a given DLCI */
107 unsigned int sercomm_tx_queue_depth(uint8_t dlci)
108 {
109         struct llist_head *le;
110         unsigned int num = 0;
111
112         llist_for_each(le, &sercomm.tx.dlci_queues[dlci]) {
113                 num++;
114         }
115
116         return num;
117 }
118
119 /* fetch one octet of to-be-transmitted serial data */
120 int sercomm_drv_pull(uint8_t *ch)
121 {
122         if (!sercomm.tx.msg) {
123                 unsigned int i;
124                 /* dequeue a new message from the queues */
125                 for (i = 0; i < ARRAY_SIZE(sercomm.tx.dlci_queues); i++) {
126                         sercomm.tx.msg = msgb_dequeue(&sercomm.tx.dlci_queues[i]);
127                         if (sercomm.tx.msg)
128                                 break;
129                 }
130                 if (sercomm.tx.msg) {
131                         /* start of a new message, send start flag octet */
132                         *ch = HDLC_FLAG;
133                         sercomm.tx.next_char = sercomm.tx.msg->data;
134                         return 1;
135                 } else {
136                         /* no more data avilable */
137                         return 0;
138                 }
139         }
140
141         /* escaping for the two control octets */
142         if (*sercomm.tx.next_char == HDLC_FLAG ||
143             *sercomm.tx.next_char == HDLC_ESCAPE) {
144                 /* send an escape octet */
145                 *ch = HDLC_ESCAPE;
146                 /* invert bit 5 of the next octet to be sent */
147                 *sercomm.tx.next_char ^= (1 << 5);
148         } else if (sercomm.tx.next_char == sercomm.tx.msg->tail) {
149                 /* last character has already been transmitted,
150                  * send end-of-message octet */
151                 *ch = HDLC_FLAG;
152                 /* we've reached the end of the message buffer */
153                 msgb_free(sercomm.tx.msg);
154                 sercomm.tx.msg = NULL;
155                 sercomm.tx.next_char = NULL;
156         } else {
157                 /* standard case, simply send next octet */
158                 *ch = *sercomm.tx.next_char++;
159         }
160         return 1;
161 }
162
163 /* register a handler for a given DLCI */
164 int sercomm_register_rx_cb(uint8_t dlci, dlci_cb_t cb)
165 {
166         if (dlci >= ARRAY_SIZE(sercomm.rx.dlci_handler))
167                 return -EINVAL;
168
169         if (sercomm.rx.dlci_handler[dlci])
170                 return -EBUSY;
171
172         sercomm.rx.dlci_handler[dlci] = cb;
173         return 0;
174 }
175
176 /* dispatch an incomnig message once it is completely received */
177 static void dispatch_rx_msg(uint8_t dlci, struct msgb *msg)
178 {
179         if (sercomm.rx.dlci_handler[dlci])
180                 sercomm.rx.dlci_handler[dlci](dlci, msg);
181         else
182                 msgb_free(msg);
183 }
184
185 /* the driver has received one byte, pass it into sercomm layer */
186 int sercomm_drv_rx_char(uint8_t ch)
187 {
188         uint8_t *ptr;
189
190         if (!sercomm.rx.msg)
191                 sercomm.rx.msg = sercomm_alloc_msgb(SERCOMM_RX_MSG_SIZE);
192
193         if (msgb_tailroom(sercomm.rx.msg) == 0) {
194                 msgb_free(sercomm.rx.msg);
195                 sercomm.rx.msg = sercomm_alloc_msgb(SERCOMM_RX_MSG_SIZE);
196                 sercomm.rx.state = RX_ST_WAIT_START;
197                 return 0;
198         }
199
200         switch (sercomm.rx.state) {
201         case RX_ST_WAIT_START:
202                 if (ch != HDLC_FLAG)
203                         return 0;
204                 sercomm.rx.state = RX_ST_ADDR;
205                 break;
206         case RX_ST_ADDR:
207                 sercomm.rx.dlci = ch;
208                 sercomm.rx.state = RX_ST_CTRL;
209                 break;
210         case RX_ST_CTRL:
211                 sercomm.rx.ctrl = ch;
212                 sercomm.rx.state = RX_ST_DATA;
213                 break;
214         case RX_ST_DATA:
215                 if (ch == HDLC_ESCAPE) {
216                         /* drop the escape octet, but change state */
217                         sercomm.rx.state = RX_ST_ESCAPE;
218                         break;
219                 } else if (ch == HDLC_FLAG) {
220                         /* message is finished */
221                         dispatch_rx_msg(sercomm.rx.dlci, sercomm.rx.msg);
222                         /* allocate new buffer */
223                         sercomm.rx.msg = NULL;
224                         /* start all over again */
225                         sercomm.rx.state = RX_ST_WAIT_START;
226
227                         /* do not add the control char */
228                         break;
229                 }
230                 /* default case: store the octet */
231                 ptr = msgb_put(sercomm.rx.msg, 1);
232                 *ptr = ch;
233                 break;
234         case RX_ST_ESCAPE:
235                 /* store bif-5-inverted octet in buffer */
236                 ch ^= (1 << 5);
237                 ptr = msgb_put(sercomm.rx.msg, 1);
238                 *ptr = ch;
239                 /* transition back to nromal DATA state */
240                 sercomm.rx.state = RX_ST_DATA;
241                 break;
242         }
243         return 1;
244 }