1 /*
2  * Copyright 2018 Oticon A/S
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 /**
7  * Functions to setup and utilize "back channels" (for test purposes) in
8  * between devices.
9  *
10  * Back channels are "cheat" communication channels, tests running in device
11  * can use to exchange information.
12  * They are fully reliable, and do not try to emulate any network or protocol.
13  * This channels behave just like unix pipes.
14  *
15  * A device may open one or several channels to any other device
16  * (Note that both devices need to open the channels or the other side will be
17  * blocked)
18  *
19  * Each channel is bidirectional
20  * Each channel is assigned a channel_id on creation, all subsequent operations
21  * towards that channel will use that channel_id
22  *
23  * What is carried in the channel is fully up to the user. But note:
24  *  * The channel is *non* *blocking*
25  *  * Before any read you shall check if there is anything to be read (you will
26  *    get an error if you try to read from an empty back channel).
27  *  * Writes are also non blocking, if there is no space in the channel the
28  *    write will fail:
29  *    * You cannot have more pending data in any given channel than 64KB in
30  *      Linux
31  *    * To avoid problems you should not send messages bigger than PIPE_BUF
32  *      ( 4K in Linux, to be POSIX portable 512B ) - 4 bytes
33  */
34 
35 #include <stdbool.h>
36 #include <stdint.h>
37 #include <stddef.h>
38 #include <fcntl.h>
39 #include <errno.h>
40 #include <unistd.h>
41 #include <string.h>
42 #include "bs_pc_base_fifo_user.h"
43 #include "bs_tracing.h"
44 #include "bs_oswrap.h"
45 
46 static bool channel_ever_opened = false;
47 
48 typedef enum {In=0, Out} direction_t;
49 
50 typedef struct {
51   char *ff_path[2];
52   int ff[2];
53   int pending_read_bytes; //-1 == channel is closed
54   int dev_nbr;
55   int channel_nbr;
56 } channels_status_t;
57 
58 static channels_status_t *channels_status;
59 static int number_back_channels = -1;
60 
61 static uint *channel_id_table = NULL;
62 
63 /**
64  * Close and cleanup the back channel communication
65  */
bs_clean_back_channels()66 void bs_clean_back_channels(){
67   if ( channel_ever_opened ){
68     if ( channels_status != NULL ) {
69       for (int i = 0; i < number_back_channels ; i ++) {
70         for (direction_t dir = In ; dir <= Out; dir++) {
71           if ( channels_status[i].ff_path[dir] ) {
72             close(channels_status[i].ff[dir]); //Close FIFO
73             remove(channels_status[i].ff_path[dir]); //Attempt to delete FIFO
74             free(channels_status[i].ff_path[dir]);
75           }
76         }
77       }
78 
79       free(channels_status);
80       channels_status = NULL;
81     }
82   }
83   if ( pb_com_path != NULL ) {
84     rmdir(pb_com_path);
85   }
86   if ( channel_id_table != NULL ) {
87     free(channel_id_table);
88     channel_id_table = NULL;
89   }
90   number_back_channels = 0;
91 }
92 
93 /**
94  * Open <nbr_of_channels> back channels to other devices,
95  * where <global_dev_nbr> is this device global number.
96  * <dev_nbrs> are the devices to which to open the channels
97  * <channel_nbrs> are the channel numbers to each device (you can have several channels to each device)
98  * e.g. to open 2 channels to device 1 and 1 channel to device 5 call like:
99  *   device_nbrs[3] = {1,1,5};
100  *   channel_numbers[3] = {0,1,0};
101  *   number_of_channels = 3;
102  *
103  * Note that this function can only be called *once*
104  *
105  * This function is blocking until the other side devices open the corresponding back channels
106  * This function returns NULL on failure or
107  * an array of channel identifiers to be used in subsequent back channel operations
108  * (DO NOT free that pointer)
109  *
110  */
bs_open_back_channel(uint global_dev_nbr,uint * dev_nbrs,uint * channel_nbrs,uint nbr_of_channels)111 uint *bs_open_back_channel(uint global_dev_nbr, uint* dev_nbrs, uint* channel_nbrs, uint nbr_of_channels){
112   if ( channel_ever_opened )
113     bs_trace_error_line("To prevent deadlocks you have to open all channels in one call to %s\n", __func__);
114 
115   extern bool is_base_com_initialized;
116   if ( ! is_base_com_initialized ){
117     bs_trace_error_line("You canNOT call %s before this device has connected to its phy(s)\n", __func__);
118   }
119 
120   channels_status = bs_calloc(nbr_of_channels, sizeof(channels_status_t));
121   channel_ever_opened = true;
122   number_back_channels = nbr_of_channels;
123   channel_id_table = bs_malloc(nbr_of_channels*sizeof(uint));
124 
125   for (direction_t dir = In ; dir <= Out; dir++){
126     for (int i = 0 ; i < nbr_of_channels; i ++){
127 
128       channels_status[i].ff_path[dir] = (char*)bs_calloc( pb_com_path_length + 50 , sizeof(char));
129       if ( dir == In ){
130         sprintf(channels_status[i].ff_path[dir], "%s/Device%u_from%u_%u.bc",
131                 pb_com_path, global_dev_nbr, dev_nbrs[i], channel_nbrs[i]);
132       } else {
133         sprintf(channels_status[i].ff_path[dir], "%s/Device%u_from%u_%u.bc",
134                 pb_com_path, dev_nbrs[i], global_dev_nbr, channel_nbrs[i]);
135       }
136 
137       if ( pb_create_fifo_if_not_there(channels_status[i].ff_path[dir]) != 0 ){
138         bs_clean_back_channels();
139         free(channel_id_table);
140         return NULL;
141       }
142 
143       if ( dir == In ){
144         //Open FIFO not locking for In side
145         if ( ( channels_status[i].ff[dir] = open(channels_status[i].ff_path[dir],O_RDONLY | O_NONBLOCK) ) == -1 ) {
146           bs_clean_back_channels();
147           free(channel_id_table);
148           return NULL;
149         }
150         channels_status[i].pending_read_bytes = 0;
151         channel_id_table[i] = i;
152         channels_status[i].dev_nbr = dev_nbrs[i];
153         channels_status[i].channel_nbr = channel_nbrs[i];
154       } else {
155         //Open FIFO locking for out side (this will block until the other device opens for reading)
156         if ( ( channels_status[i].ff[dir] = open(channels_status[i].ff_path[dir],O_WRONLY ) ) == -1 ) {
157           bs_clean_back_channels();
158           free(channel_id_table);
159           return NULL;
160         }
161         //Change write side permissions to non locking
162         int flags = fcntl(channels_status[i].ff[dir], F_GETFL);
163         flags |= O_NONBLOCK;
164         fcntl(channels_status[i].ff[dir], F_SETFL, flags);
165       }
166 
167     } //for i
168   } //for dir
169 
170   return channel_id_table;
171 }
172 
173 /**
174  * Send a message to the other device thru the channel
175  * Note that if the other device has closed the channel (pipe) == disconnected
176  * we will get a SIGPIPE here and will terminate abruptly
177  */
bs_bc_send_msg(uint channel_id,uint8_t * ptr,size_t size)178 void bs_bc_send_msg(uint channel_id, uint8_t *ptr, size_t size){
179   if ( channel_id >= number_back_channels )
180     bs_trace_error_line("you are trying to send a message thru a non existent back channel (%u)\n", channel_id);
181 
182   char message[size+4];
183   *(uint32_t*)message = size;
184   memcpy(&message[4], ptr, size);
185   //To avoid problems we move all data in one write() call.
186   //(otherwise the context maybe switched out between writes and the read may fail on the other side)
187 
188   int bytes_written = write(channels_status[channel_id].ff[Out], message, size+4);
189   if ( bytes_written != size+4 ) {
190     bs_trace_error_line("back channel %u filled up (%i != %z+4, errno=%i)\n",
191                         channel_id, bytes_written, size, errno);
192   }
193 }
194 
195 /**
196  * check if there is any pending message in the queue
197  * Returns -1 if the channel is closed
198  * Returns 0 if nothing is available yet
199  * the size of the next message if there is something
200  */
bs_bc_is_msg_received(uint channel_id)201 int bs_bc_is_msg_received(uint channel_id){
202   if ( channel_id >= number_back_channels )
203     bs_trace_error_line("you are trying to check for a message in a non existent back channel (%u)\n", channel_id);
204 
205   while ( channels_status[channel_id].pending_read_bytes == 0 ){ //otherwise the user is calling this function twice (and we'd break the protocol)
206     uint32_t size32;
207     int read_size = read(channels_status[channel_id].ff[In],&size32,sizeof(uint32_t)); //non blocking read
208     if ( read_size == sizeof(uint32_t) ) {
209       channels_status[channel_id].pending_read_bytes = size32;
210     } else if ( ( ( read_size == -1 ) && (errno == EAGAIN) ) || (read_size == 0) ) { //Nothing yet there
211       break;
212     } else if ( ( read_size == -1 ) && (errno == EINTR) ) {
213       //A signal interrupted the read before anything was read => retry needed
214       bs_trace_warning_line("Read to back channel %u interrupted by signal => Retrying\n",
215                             channel_id);
216     } else if ( read_size == EOF ) { //The FIFO was closed by the other side
217       channels_status[channel_id].pending_read_bytes = -1;
218       bs_trace_raw_time(3,"The back channel %u was closed by the other side\n",channel_id);
219       break;
220     } else {
221       bs_trace_error_line("Unexpected error in channel %u (%i read, errno=%i: %s)\n",
222                           channel_id, read_size, errno, strerror(errno));
223     }
224   }
225   return channels_status[channel_id].pending_read_bytes;
226 }
227 
228 /**
229  * Receive a message into <ptr> of <size> bytes from the other side
230  *
231  * Always call bs_bc_is_msg_received() before to check that there is
232  * indeed a message,
233  * and always ask for a message of not more than the number of bytes
234  * bs_bc_is_msg_received() returned
235  */
bs_bc_receive_msg(int channel_id,uint8_t * ptr,size_t size)236 void bs_bc_receive_msg(int channel_id , uint8_t *ptr, size_t size){
237   if ( channel_id >= number_back_channels )
238     bs_trace_error_line("You are trying to receive a message in a non existent back channel (%u)\n", channel_id);
239 
240   if ( size > channels_status[channel_id].pending_read_bytes )
241     bs_trace_error_line("Last time you checked bs_bc_is_msg_received() told there was %u bytes in channel %u, but now you try to read %u??\n",
242                          channels_status[channel_id].pending_read_bytes, channel_id, size);
243 
244   if ( size == 0 )
245     return;
246 
247   int read_size = read(channels_status[channel_id].ff[In],ptr,size); //Non-blocking read
248   if ( read_size != size ) {
249     bs_trace_error_line("Back channel %u broken (%i != %z bytes, errno=%i, pending=%i) "
250                         "(probably the other side crashed in the middle of a message == nasty)\n",
251                         channel_id, read_size, size, errno, channels_status[channel_id].pending_read_bytes);
252   }
253   channels_status[channel_id].pending_read_bytes -= size;
254 }
255