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