diff --git a/radio/vrtsim/vrtsim.c b/radio/vrtsim/vrtsim.c index 97c820a859..9e678a5305 100644 --- a/radio/vrtsim/vrtsim.c +++ b/radio/vrtsim/vrtsim.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -62,15 +63,20 @@ typedef enum { ROLE_SERVER = 1, ROLE_CLIENT } role; #define TIME_SCALE_HLP \ "sample time scale. 1.0 means realtime. Values > 1 mean faster than realtime. Values < 1 mean slower than realtime\n" #define TAPS_SOCKET_HLP "Socket to connect to the channel emulation server\n" +#define CLIENT_NUM_RX_HLP "Number of RX antennas of the client, specified on the server\n" +#define CONNECTION_DESCRIPTOR_HLP "Path to the file written by the server that the client can use to connect." +#define DEFAULT_CHANNEL_NAME "vrtsim_channel" +#define DEFAULT_DESCRIPTOR "/tmp/vrtsim_connection" // clang-format off #define VRTSIM_PARAMS_DESC \ { \ - {"channel_name", "shared memory channel name\n", 0, .strptr = &channel_name, .defstrval = "vrtsim_channel", TYPE_STRING, 0}, \ - {"role", "either client or server\n", 0, .strptr = &role, .defstrval = ROLE_CLIENT_STRING, TYPE_STRING, 0}, \ - {"timescale", TIME_SCALE_HLP, 0, .dblptr = &vrtsim_state->timescale, .defdblval = 1.0, TYPE_DOUBLE, 0}, \ - {"chanmod", "Enable channel modelling", 0, .iptr = &vrtsim_state->chanmod, .defintval = 0, TYPE_INT, 0}, \ - {"taps-socket", TAPS_SOCKET_HLP, 0, .strptr = &vrtsim_state->taps_socket, .defstrval = NULL, TYPE_STRING, 0}, \ + {"connection_descriptor", CONNECTION_DESCRIPTOR_HLP, 0, .strptr = &vrtsim_state->connection_descriptor, .defstrval = DEFAULT_DESCRIPTOR, TYPE_STRING, 0}, \ + {"role", "either client or server\n", 0, .strptr = &role, .defstrval = ROLE_CLIENT_STRING, TYPE_STRING, 0}, \ + {"timescale", TIME_SCALE_HLP, 0, .dblptr = &vrtsim_state->timescale, .defdblval = 1.0, TYPE_DOUBLE, 0}, \ + {"chanmod", "Enable channel modelling", 0, .iptr = &vrtsim_state->chanmod, .defintval = 0, TYPE_INT, 0}, \ + {"taps-socket", TAPS_SOCKET_HLP, 0, .strptr = &vrtsim_state->taps_socket, .defstrval = NULL, TYPE_STRING, 0}, \ + {"client-num-rx-antennas", CLIENT_NUM_RX_HLP, 0, .iptr = &vrtsim_state->client_num_rx_antennas, .defintval = 1, TYPE_INT, 0}, \ }; // clang-format on @@ -96,7 +102,7 @@ typedef struct tx_timing_s { typedef struct { int role; - char channel_name[40]; + char *connection_descriptor; ShmTDIQChannel *channel; uint64_t last_received_sample; pthread_t timing_thread; @@ -116,6 +122,7 @@ typedef struct { channel_desc_t *channel_desc; Actor_t *channel_modelling_actors; char *taps_socket; + int client_num_rx_antennas; } vrtsim_state_t; // Sample history for channel impulse response @@ -174,12 +181,10 @@ static void load_channel_model(vrtsim_state_t *vrtsim_state) static void vrtsim_readconfig(vrtsim_state_t *vrtsim_state) { - char *channel_name = NULL; char *role = NULL; paramdef_t vrtsim_params[] = VRTSIM_PARAMS_DESC; int ret = config_get(config_get_if(), vrtsim_params, sizeofArray(vrtsim_params), VRTSIM_SECTION); AssertFatal(ret >= 0, "configuration couldn't be performed\n"); - strncpy(vrtsim_state->channel_name, channel_name, sizeof(vrtsim_state->channel_name) - 1); if (strncmp(role, ROLE_CLIENT_STRING, strlen(ROLE_CLIENT_STRING)) == 0) { vrtsim_state->role = ROLE_CLIENT; } else if (strncmp(role, ROLE_SERVER_STRING, strlen(ROLE_SERVER_STRING)) == 0) { @@ -230,112 +235,80 @@ static void *vrtsim_timing_job(void *arg) return 0; } +typedef struct client_info_s { + int server_num_rx_antennas; + int client_num_rx_antennas; +} client_info_t; + /** - * @brief Exchanges peer information between the server and the client. + * @brief Publishes the client information information to a file for the client to read. * - * This function sets up a Unix domain socket server, waits for a client to connect, - * and exchanges peer information (number of RX antennas) with the client. + * The server writes its client_info (number of RX antennas) to a file, which the client reads. + * The server does not wait for the client to write back; the client can connect at any point. * - * @param peer_info The peer information to send to the client. - * @return The peer information received from the client. - * - * @note This function will block until a client connects and the information is exchanged. + * @param client_info The client information to publish. + * @return The peer information (same as input, server is authoritative). */ -static peer_info_t server_exchange_peer_info(peer_info_t peer_info) +static void server_publish_client_info(client_info_t client_info, char *descriptor_file) { - int server_fd, client_fd; - - // Create a Unix domain socket - server_fd = socket(AF_UNIX, SOCK_STREAM, 0); - AssertFatal(server_fd >= 0, "socket() failed: errno: %d, %s\n", errno, strerror(errno)); - - // Bind the socket to a file path - struct sockaddr_un addr = {.sun_family = AF_UNIX}; - strncpy(addr.sun_path, "/tmp/vrtsim_socket", sizeof(addr.sun_path) - 1); - unlink(addr.sun_path); // Ensure the path does not already exist - int ret = bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)); - AssertFatal(ret == 0, "bind() failed: errno: %d, %s\n", errno, strerror(errno)); - - // Listen for incoming connections - ret = listen(server_fd, 1); - AssertFatal(ret == 0, "listen() failed: errno: %d, %s\n", errno, strerror(errno)); - - // Accept a connection from a client - socklen_t addr_len = sizeof(addr); - client_fd = accept(server_fd, (struct sockaddr *)&addr, &addr_len); - AssertFatal(client_fd >= 0, "accept() failed: errno: %d, %s\n", errno, strerror(errno)); - - // Send the antenna info to the server - ssize_t bytes_sent = send(client_fd, &peer_info, sizeof(peer_info), 0); - AssertFatal(bytes_sent == sizeof(peer_info), "send() failed: errno: %d, %s\n", errno, strerror(errno)); - - ssize_t bytes_received = recv(client_fd, &peer_info, sizeof(peer_info), 0); - AssertFatal(bytes_received == sizeof(peer_info), "recv() failed: errno: %d, %s\n", errno, strerror(errno)); - - LOG_I(HW, "Received client info: RX %d\n", peer_info.num_rx_antennas); - - // Close the client and server sockets - close(client_fd); - close(server_fd); - return peer_info; + FILE *fp = fopen(descriptor_file, "wb"); + AssertFatal(fp != NULL, "Failed to open client info file for writing: %s\n", strerror(errno)); + size_t written = fwrite(&client_info, sizeof(client_info), 1, fp); + AssertFatal(written == 1, "Failed to write client info to file\n"); + fclose(fp); } -/** - * @brief Exchanges peer information between the client and the server. - * - * This function connects to a Unix domain socket server, exchanges peer information - * (number of RX antennas) with the server, and returns the peer information received - * from the server. - * - * @param peer_info The peer information to send to the server. - * @return The peer information received from the server. - * - * @note This function will block until the information is exchanged. - */ -static peer_info_t client_exchange_peer_info(peer_info_t peer_info) +static client_info_t client_read_info(char *descriptor_file) { - int client_fd; - - // Create a Unix domain socket - client_fd = socket(AF_UNIX, SOCK_STREAM, 0); - AssertFatal(client_fd >= 0, "socket() failed: errno: %d, %s\n", errno, strerror(errno)); - - // Connect to the server - struct sockaddr_un addr = {.sun_family = AF_UNIX}; - strncpy(addr.sun_path, "/tmp/vrtsim_socket", sizeof(addr.sun_path) - 1); - int ret = -1; + client_info_t client_info; int tries = 0; - while (ret != 0 && tries < 10) { - ret = connect(client_fd, (struct sockaddr *)&addr, sizeof(addr)); - tries++; + while (tries < 10) { + FILE *fp = fopen(descriptor_file, "rb"); + if (fp) { + size_t read = fread(&client_info, sizeof(client_info), 1, fp); + fclose(fp); + if (read == 1) { + return client_info; + } + } sleep(1); + tries++; } - AssertFatal(ret == 0, "connect() failed: errno: %d, %s\n", errno, strerror(errno)); - - peer_info_t peer_info_received; - ssize_t bytes_received = recv(client_fd, &peer_info_received, sizeof(peer_info_received), 0); - AssertFatal(bytes_received == sizeof(peer_info_received), "recv() failed: errno: %d, %s\n", errno, strerror(errno)); - - // Send the antenna info to the server - ssize_t bytes_sent = send(client_fd, &peer_info, sizeof(peer_info), 0); - AssertFatal(bytes_sent == sizeof(peer_info), "send() failed: errno: %d, %s\n", errno, strerror(errno)); - - LOG_I(HW, "Received peer info: RX %d\n", peer_info_received.num_rx_antennas); - - // Close the client socket - close(client_fd); - return peer_info_received; + AssertFatal(0, "Timeout waiting for client info\n"); + return client_info; } static int vrtsim_connect(openair0_device *device) { vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv; - // Exchange peer info - peer_info_t peer_info = {.num_rx_antennas = device->openair0_cfg[0].rx_num_channels}; + + // Setup a shared memory channel if (vrtsim_state->role == ROLE_SERVER) { - vrtsim_state->peer_info = server_exchange_peer_info(peer_info); + vrtsim_state->peer_info.num_rx_antennas = vrtsim_state->client_num_rx_antennas; + vrtsim_state->channel = shm_td_iq_channel_create(DEFAULT_CHANNEL_NAME, + vrtsim_state->peer_info.num_rx_antennas, + device->openair0_cfg[0].rx_num_channels); + // Exchange peer info + client_info_t client_info = { + .server_num_rx_antennas = device->openair0_cfg[0].rx_num_channels, + .client_num_rx_antennas = vrtsim_state->client_num_rx_antennas, + }; + server_publish_client_info(client_info, vrtsim_state->connection_descriptor); + + vrtsim_state->run_timing_thread = true; + int ret = pthread_create(&vrtsim_state->timing_thread, NULL, vrtsim_timing_job, vrtsim_state); + AssertFatal(ret == 0, "pthread_create() failed: errno: %d, %s\n", errno, strerror(errno)); } else { - vrtsim_state->peer_info = client_exchange_peer_info(peer_info); + client_info_t client_info = client_read_info(vrtsim_state->connection_descriptor); + AssertFatal(client_info.server_num_rx_antennas > 0, "Server did not publish valid client info, aborting client connection\n"); + AssertFatal( + client_info.client_num_rx_antennas == device->openair0_cfg[0].rx_num_channels, + "Server expects different number of RX antennas. %d != %d. Use server command line option --client-num-rx-antennas\n", + client_info.client_num_rx_antennas, + device->openair0_cfg[0].rx_num_channels); + vrtsim_state->channel = shm_td_iq_channel_connect(DEFAULT_CHANNEL_NAME, 10); + vrtsim_state->peer_info.num_rx_antennas = client_info.server_num_rx_antennas; + vrtsim_state->last_received_sample = shm_td_iq_channel_get_current_sample(vrtsim_state->channel); } // Handle channel modelling after number of RX antennas are known @@ -363,18 +336,6 @@ static int vrtsim_connect(openair0_device *device) vrtsim_state->tx_timing[i].tx_histogram.range = 3000.0; } - // Setup a shared memory channel - if (vrtsim_state->role == ROLE_SERVER) { - vrtsim_state->channel = shm_td_iq_channel_create(vrtsim_state->channel_name, - vrtsim_state->peer_info.num_rx_antennas, - device->openair0_cfg[0].rx_num_channels); - vrtsim_state->run_timing_thread = true; - int ret = pthread_create(&vrtsim_state->timing_thread, NULL, vrtsim_timing_job, vrtsim_state); - AssertFatal(ret == 0, "pthread_create() failed: errno: %d, %s\n", errno, strerror(errno)); - } else { - vrtsim_state->channel = shm_td_iq_channel_connect(vrtsim_state->channel_name, 10); - } - return 0; } @@ -640,6 +601,15 @@ static void vrtsim_end(openair0_device *device) LOG_I(HW, "VRTSIM: Average TX budget %.3lf uS (more is better)\n", tx_timing->average_tx_budget); histogram_print(&tx_timing->tx_histogram); free(vrtsim_state->tx_timing); + + if (vrtsim_state->role == ROLE_SERVER) { + int ret = remove(vrtsim_state->connection_descriptor); + if (ret != 0) { + LOG_E(HW, "Failed to remove connection descriptor file %s: %s\n", vrtsim_state->connection_descriptor, strerror(errno)); + } else { + LOG_A(HW, "Removed connection descriptor file %s\n", vrtsim_state->connection_descriptor); + } + } } static int vrtsim_stub(openair0_device *device)