]> rtime.felk.cvut.cz Git - pes-rpp/rpp-test-sw.git/blob - rpp-test-sw/commands/cmd_echoserver.c
Fix warnings
[pes-rpp/rpp-test-sw.git] / rpp-test-sw / commands / cmd_echoserver.c
1 /*
2  * cmd_echoserver.c -- Simple echoserver
3  *
4  * Copyright (C) 2014, 2015 Czech Technical University in Prague
5  *
6  * This echoserver is capable of running multiple instances each running
7  * in the background thread.
8  * Each instance listens on a predefined port, when a connection is
9  * established and some data are received, the same data are then sent
10  * back to the client.
11  * There is support for basic thread management -- it is possible to
12  * either list all the background threads or kill them all.
13  *
14  * The code uses LwIP netconn API.
15  *
16  * Nomenclature:
17  *     es_ prefix is used as the "echoserver" specific identifier
18  */
19
20 #include "cmd_echoserver.h"
21
22 #ifndef DOCGEN
23
24 #include <string.h>
25 #include <ctype.h>
26
27 #include "rpp/rpp.h"
28 #include "lwip/udp.h"
29 #include "lwip/api.h" /* netconn */
30
31 #include "os/FreeRTOS.h"
32 #include "os/task.h"
33
34 /* Ethernet interface instance nr. */
35 #define INTERFACE_INSTANCE_NUMBER       0
36
37 /* Number of statically allocated structs es_thread_context.
38  * This limits the number of concurrently running threads.
39  */
40 #define MAX_THREADS_CNT                 12
41
42 /* Run in the foreground, in the context of the command-processor thread */
43 #define ES_COMMAND_RUN_FOREGROUND       1
44 /* Create new background thread */
45 #define ES_COMMAND_NEW_THREAD           2
46 /* List all the background ES threads */
47 #define ES_COMMAND_LIST_THREADS         3
48 /* Kill all the background ES threads */
49 #define ES_COMMAND_KILL_ALL_THREADS     4
50
51 /* Forward declarations */
52 void es_print_help(void);
53
54 struct es_thread_context {
55         /* Thread handle; Useful when killing all the threads */
56         xTaskHandle handle;
57
58         /* listening connection */
59         struct netconn *conn_listen;
60         /* used for the netconn_accept() */
61         struct netconn *conn;
62
63 #define ES_THREAD_TERMINATED            0
64 #define ES_THREAD_RUNNING               1
65         /* Status of the thread represented with this
66          * particular struct es_thread_context
67          */
68         int status;
69
70         /*
71          * Options passed when executed from the command processor
72          */
73
74         /* Number of the port used to listen to */
75         unsigned int port_no;
76
77         /* Return code */
78         int err;
79 } es_threads[MAX_THREADS_CNT];
80
81 static int es_thread_handle_find_first_free(void)
82 {
83         int i;
84
85         for (i = 0; i < MAX_THREADS_CNT; i++) {
86                 if (es_threads[i].status == ES_THREAD_TERMINATED)
87                         return i;
88         }
89
90         return -1;
91 }
92
93 static void es_thread_terminate(struct es_thread_context *thread_context)
94 {
95         int err = thread_context->err;
96
97         /* FIXME maybe not necessary at all */
98         thread_context->port_no = 0;
99
100         thread_context->err = 0;
101         thread_context->status = ES_THREAD_TERMINATED;
102
103         if (thread_context->conn_listen) {
104                 netconn_close(thread_context->conn_listen);
105                 netconn_delete(thread_context->conn_listen);
106                 thread_context->conn_listen = NULL;
107         }
108         if (thread_context->conn) {
109                 netconn_close(thread_context->conn);
110                 netconn_delete(thread_context->conn);
111                 thread_context->conn = NULL;
112         }
113
114         rpp_sci_printf("Terminating thread %s retcode: %d\n",
115                         pcTaskGetTaskName(thread_context->handle), err);
116         vTaskDelete(thread_context->handle);
117 }
118
119
120 static void es_logic(cmd_io_t *cmd_io, void *data)
121 {
122         struct es_thread_context *thread_context = (struct es_thread_context*)data;
123         err_t err = ERR_OK;
124
125         thread_context->conn_listen = netconn_new(NETCONN_TCP);
126         if (thread_context->conn_listen == NULL) {
127                 rpp_sci_printf("netconn_new() failed\n");
128                 thread_context->err = ERR_MEM;
129                 return;
130         }
131         err = netconn_bind(thread_context->conn_listen, NULL, thread_context->port_no);
132         if (err != ERR_OK) {
133                 thread_context->err = err;
134                 goto leave_clean;
135         }
136
137         err = netconn_listen(thread_context->conn_listen);
138         if (err != ERR_OK) {
139                 thread_context->err = err;
140                 goto leave_clean;
141         }
142
143         while (cmd_io == NULL || cmd_io->getc(cmd_io) < 0) { /* keep-alive */
144                 err = netconn_accept(thread_context->conn_listen, &thread_context->conn);
145                 if (err == ERR_OK) {
146                         struct netbuf *netbuf;
147                         void *data;
148                         u16_t len;
149
150                         rpp_sci_printf("accepted new connection %p\n",
151                                 thread_context->conn);
152                         while ((err = netconn_recv(thread_context->conn, &netbuf)) == ERR_OK) {
153                                 /*printf("Recved\n");*/
154                                 do {
155                                         netbuf_data(netbuf, &data, &len);
156                                         //rpp_sci_printf("netbuf len: %d\n", (unsigned int)len);
157                                         err = netconn_write(thread_context->conn,
158                                                             data, len, NETCONN_COPY);
159                                         /* FIXME NOCOPY or COPY? What if we free the netbuf before its
160                                            payload is transmitted? */
161                                         if (err != ERR_OK) {
162                                                 rpp_sci_printf(
163                                                         "tcpecho: netconn_write: error \"%s\" (%d)\n",
164                                                         lwip_strerr(err), err);
165                                                         break; /* FIXME is this necessary?
166                                                                 Is it possible to continue? */
167                                         }
168                                 } while (netbuf_next(netbuf) >= 0);
169                                 netbuf_delete(netbuf);
170                         }
171                         rpp_sci_printf("Got EOF, looping\n");
172                         netconn_close(thread_context->conn);
173                         netconn_delete(thread_context->conn);
174                         /* Necessary for generic resource freeing */
175                         thread_context->conn = NULL;
176                 } else {
177                         thread_context->err = err;
178                         return;
179                 }
180         }
181
182 leave_clean:
183         netconn_close(thread_context->conn_listen);
184         netconn_delete(thread_context->conn_listen);
185         /* Necessary for generic resource freeing */
186         thread_context->conn_listen = NULL;
187         return;
188 }
189
190 static void es_thread(void *data)
191 {
192         struct es_thread_context *thread_context = (struct es_thread_context*)data;
193
194         /* Run the echoserver logic */
195         es_logic(NULL, data);
196
197         /* It is mandatory to destroy the thread when leaving;
198          * Be aware of the fact that we are terminating ourselves --
199          * in our thread context */
200         es_thread_terminate(thread_context);
201
202         /* We should never ever get here */
203 }
204
205 int cmd_do_init_es(cmd_io_t *cmd_io, const struct cmd_des *des, char *param[])
206 {
207         struct netif *netif = rpp_eth_get_netif(INTERFACE_INSTANCE_NUMBER);
208         struct es_thread_context *thread_context;
209         char thread_name[5] = "es"; /* thread name, used for debugging only */
210         err_t err = ERR_OK;
211         uint8_t pindex;
212         int thread_nr;
213         int command = ES_COMMAND_RUN_FOREGROUND;
214         unsigned int port_no = 0;
215         int i;
216
217         if (!isPostInitialized()) {
218                 rpp_sci_printf("Eth not initialized run 'ethinit' command first.\n");
219                 return FAILURE;
220         }
221
222         /* Parse the "command line" arguments */
223         for (pindex = 1; param[pindex] != 0; pindex++) {
224                 if (strncmp(param[pindex], "-c", 3) == 0) {
225                         if (command != ES_COMMAND_RUN_FOREGROUND) {
226                                 rpp_sci_printf("More than single command are used\n");
227                                 return FAILURE;
228                         }
229                         command = ES_COMMAND_KILL_ALL_THREADS;
230                 } else if (strncmp(param[pindex], "-l", 3) == 0) {
231                         if (command != ES_COMMAND_RUN_FOREGROUND) {
232                                 rpp_sci_printf("More than single command are used\n");
233                                 return FAILURE;
234                         }
235                         command = ES_COMMAND_LIST_THREADS;
236                 } else if (strncmp(param[pindex], "-t", 3) == 0) {
237                         if (command != ES_COMMAND_RUN_FOREGROUND) {
238                                 rpp_sci_printf("More than single command are used\n");
239                                 return FAILURE;
240                         }
241                         command = ES_COMMAND_NEW_THREAD;
242                 } else if (strncmp(param[pindex], "-p", 3) == 0) {
243                         /* The validity (!= 0) check is done later */
244                         port_no = rpp_eth_portStrToInt((uint8_t *)param[++pindex]);
245                 } else {
246                         es_print_help();
247                         return FAILURE;
248                 }
249         }
250
251         /* Port number is mandatory for interactive or background-thread mode */
252         if (command == ES_COMMAND_RUN_FOREGROUND || command == ES_COMMAND_NEW_THREAD)
253         {
254                 if (!port_no)
255                 {
256                         rpp_sci_printf("Port number not set\n");
257                         return FAILURE;
258                 }
259
260                 /* Is there any free preallocated thread handle? */
261                 thread_nr = es_thread_handle_find_first_free();
262                 if (thread_nr < 0) {
263                         rpp_sci_printf("Unable to create new thread."
264                                 "Maximum number of simultaneously running threads is reached\n");
265                         return FAILURE;
266                 }
267                 thread_context = &es_threads[thread_nr];
268                 thread_context->port_no = port_no;
269         }
270
271         /* Execute the particular command */
272         if (command == ES_COMMAND_NEW_THREAD) {
273                 unsigned int thread_nr_tmp;
274                 thread_nr_tmp = (thread_nr % 100);
275                 thread_name[2] = (thread_nr_tmp/10) + '0';
276                 thread_name[3] = (thread_nr_tmp%10) + '0';
277                 thread_name[4] = '\0';
278                 rpp_sci_printf("Starting thread: %s\n", thread_name);
279                 err = xTaskCreate(es_thread,
280                                   thread_name,
281                                   esTaskStackSize,
282                                   thread_context,
283                                   esTaskPriority,
284                                   &thread_context->handle);
285                 if (err != pdPASS) {
286                         rpp_sci_printf("xTaskCreate() failed\n");
287                         return FAILURE;
288                 } else {
289                         thread_context->status = ES_THREAD_RUNNING;
290                 }
291
292         } else if (command == ES_COMMAND_RUN_FOREGROUND) {
293                 /* Run in the foreground */
294                 es_logic(cmd_io, (void *)thread_context);
295         } else if (command == ES_COMMAND_LIST_THREADS) {
296                 for (i = 0; i < MAX_THREADS_CNT; i++) {
297                         if (es_threads[i].status == ES_THREAD_RUNNING) {
298                                 rpp_sci_printf("Thread %s exists state: %s\n",
299                                         pcTaskGetTaskName(es_threads[i].handle),
300                                         "unknown");
301                         }
302                 }
303         } else if (command == ES_COMMAND_KILL_ALL_THREADS) {
304                 for (i = 0; i < MAX_THREADS_CNT; i++) {
305                         if (es_threads[i].status == ES_THREAD_RUNNING) {
306                                 es_thread_terminate(&es_threads[i]);
307                         }
308                 }
309         }
310         return ERR_OK;
311 }
312
313 #endif  /* DOCGEN */
314
315 #define ES_COMMAND_NAME                         "ethes"
316 cmd_des_t const cmd_des_es = {
317         0, CDESM_SPACE_SEP,
318         ES_COMMAND_NAME, "Start very simple TCP echoserver",
319         "Syntax:\n"
320         ES_COMMAND_NAME " -p NUMBER [-l|-c|-t]\n\n"
321         "    -p\tdefine port number to listen to\n"
322         "Optional command:\n"
323         "    -l\tlist all running threads\n"
324         "    -c\tkill all running threads\n"
325         "    -t\tcreate new background thread\n"
326         "If no command is given, the application is run in interactive mode"
327         " (in foreground)\n",
328         CMD_HANDLER(cmd_do_init_es), (void *)&cmd_list_es
329 };
330
331 #ifndef DOCGEN
332 void es_print_help(void)
333 {
334         rpp_sci_printf("%s\n", cmd_des_es.long_help);
335 }
336 #endif  /* DOCGEN */
337
338 /** List of commands for lwip, defined as external */
339 cmd_des_t const *cmd_list_es[] = {
340         &cmd_des_es,
341         NULL
342 };