1 /*
2 * Copyright 2018 Oticon A/S
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6 #include "bs_pc_base_types.h"
7 #include "bs_pc_base.h"
8 #include "bs_tracing.h"
9 #include "bs_oswrap.h"
10 #include "bs_string.h"
11 #include <signal.h>
12 #include <string.h>
13 #include <dirent.h>
14 #include <sys/stat.h>
15 #include <sys/types.h>
16 #include <errno.h>
17 #include <unistd.h>
18 #include <fcntl.h>
19 #include <pwd.h>
20 #include <errno.h>
21
22
23 bool is_base_com_initialized = false; //Used by the BackChannel to avoid deadlocks
24 char *pb_com_path = NULL;
25 int pb_com_path_length = 0;
26
27 /**
28 * Create a FIFO if it doesn't exist
29 *
30 * Note that it may already exist, and/or that some other program
31 * may be racing to create at the same time
32 */
pb_create_fifo_if_not_there(const char * fifo_path)33 int pb_create_fifo_if_not_there(const char *fifo_path) {
34 if ((access(fifo_path, F_OK) == -1) && (mkfifo(fifo_path, S_IRWXG | S_IRWXU) != 0) && (access(fifo_path, F_OK) == -1)) {
35 bs_trace_warning_line("Can not create %s\n", fifo_path);
36 return -1;
37 }
38 return 0;
39 }
40
41 /*
42 * Do a quick sanity check of the simulation id. To warn users if it is likely going to create
43 * issues.
44 */
pb_check_sim_id(const char * s)45 bool pb_check_sim_id(const char *s) {
46 int i = 0;
47 while (s[i] != 0) {
48 if ((s[i] == '/')) {
49 bs_trace_warning_line("The sim_id ('%s') has '%c' in it. Please note the sim_id is used to "
50 "create filesystem paths. Using special characters will likely result "
51 "in undesired behavior\n", s, s[i]);
52 return true;
53 }
54 i++;
55 }
56 return false;
57 }
58
59 /**
60 * Create the comm folder if it doesn't exist
61 * And sets its name in cb_com_path
62 * Returns
63 * the length of the cb_com_path string if it succeeds
64 * -1 otherwise
65 */
pb_create_com_folder(const char * s)66 int pb_create_com_folder(const char *s) {
67 char *UserName = NULL;
68 int UserNameLength = 0;
69 bool free_UserName = false;
70 struct passwd *pw;
71
72 uid_t euid = geteuid();
73 errno = 0;
74 pw = getpwuid(euid);
75 if (pw != NULL) {
76 UserName = pw->pw_name;
77 } else if (!((errno == 0) || (errno == ENOENT) || (errno== ESRCH) || (errno== EBADF) || (errno == EPERM))) {
78 bs_trace_warning_line("Warning, completely unexpected error trying to get the user name will try to recover but will likely fail"
79 "(euid=%i, errno=%i)\n", euid, errno);
80 } else {
81 //Otherwise (pw == NULL AND any of those errors), it seems the user is not a real user, or the connection to the NIS server is down or..
82 bs_trace_info_line(2, "You seem to be running this program as a not real user(?). Will try to recover"
83 "(euid=%i, errno=%i)\n", euid, errno);
84 }
85
86 if (UserName == NULL) {
87 //Let's hope the POSIX required LOGNAME environment variable is set
88 UserName = getenv("LOGNAME");
89 }
90 if (UserName == NULL) {
91 //It was not.. let's hope for USER..
92 UserName = getenv("USER");
93 }
94 if (UserName == NULL) {
95 bs_trace_info_line(2, "Couldn't get the user name to build the tmp path for the interprocess comm (euid=%i)"
96 "(this shouldn't have happened, could not find the environment variables LOGNAME or USER either), errno=%i."
97 "Falling back to using the numeric eid\n", euid, errno);
98 UserName = bs_calloc(sizeof(char), 32); /* plenty of margin even for 64bits ints */
99 free_UserName = true;
100 sprintf(UserName, "%i", euid);
101 }
102
103 UserNameLength = strlen(UserName);
104
105 pb_com_path = (char*)bs_calloc(10 + strlen(s) + UserNameLength, sizeof(char));
106
107 sprintf(pb_com_path, "/tmp/bs_%s", UserName);
108
109 if (free_UserName) {
110 free(UserName);
111 }
112
113 if (bs_createfolder(pb_com_path) != 0) {
114 free(pb_com_path);
115 pb_com_path = NULL;
116 return -1;
117 }
118 sprintf(&pb_com_path[8+UserNameLength], "/%s", s);
119 if (bs_createfolder(pb_com_path) != 0) {
120 free(pb_com_path);
121 pb_com_path = NULL;
122 return -1;
123 }
124 return 9 + UserNameLength + strlen(s);
125 }
126
pb_send_payload(int ff,void * buf,size_t size)127 void pb_send_payload(int ff, void *buf, size_t size) {
128 if (size) {
129 if (buf==NULL) {
130 bs_trace_error_line("Null pointer!!\n");
131 }
132 write(ff, buf, size);
133 }
134 }
135
136 //#define NO_LOCK_FILE
137
138 #if !defined(NO_LOCK_FILE)
lock_file_fill(const char * filename,long int my_pid)139 static void lock_file_fill(const char *filename, long int my_pid){
140 FILE *file;
141
142 file = bs_fopen(filename, "w");
143 fprintf(file, "%li\n",(long) my_pid);
144 #if defined(__linux)
145 long long unsigned int starttime = bs_get_process_start_time(my_pid);
146 fprintf(file,"%llu\n",starttime);
147 #endif
148 fclose(file);
149 }
150 #endif
151
152 /**
153 * Check if the lock file exists
154 * * If it doesn't create one for us
155 * * If the lock file exists:
156 * * If the process who created it is still running, we stop.
157 * * If the process who created it is not running, we print a warning and risk it
158 * * If we cannot determine who created it, we stop.
159 *
160 * Returns 0 if we consider it safe enough to continue
161 * Something else otherwise
162 */
pb_test_and_create_lock_file(const char * filename)163 int pb_test_and_create_lock_file(const char *filename) {
164 #if !defined(NO_LOCK_FILE)
165 pid_t my_pid = getpid();
166
167 if (access(filename, F_OK) == -1) {
168 // The file does not exist. So unless somebody is racing us, we are safe to go
169 lock_file_fill(filename, my_pid);
170 return 0;
171 }
172
173 bs_trace_warning_line("Previous lock file found %s\n", filename);
174
175 FILE *file;
176 bool corrupt_file = false;
177 int other_dead = 1;
178 long int his_pid;
179 #if defined(__linux)
180 long long unsigned int his_starttime;
181 #endif
182
183 file = bs_fopen(filename, "r");
184 if ( fscanf(file, "%li\n", &his_pid) != 1 ) {
185 corrupt_file = true;
186 }
187 #if defined(__linux)
188 if ((corrupt_file == false) && (fscanf(file,"%llu\n", &his_starttime) != 1)) {
189 corrupt_file = true;
190 }
191 #endif
192
193 fclose(file);
194
195 if (corrupt_file) { //We are provably racing the other process, we stop
196 bs_trace_warning_line("Found previous lock owned by unknown process, we may be racing each other => aborting\n");
197 return 1;
198 }
199
200 other_dead = kill(his_pid, 0);
201
202 if (other_dead == 0) { //a process with the pid exists
203 #if defined(__linux)
204 /* To be sure the pid was not reused, let's check that the process start time matches */
205
206 uint64_t other_start_time = bs_get_process_start_time(his_pid);
207 if (his_starttime == other_start_time) { //it is the same process
208 bs_trace_warning_line("Found a previous, still RUNNING process w pid %li with the same sim_id and device port which would interfere with this one, aborting\n", his_pid);
209 } else {
210 other_dead = 1;
211 }
212 #else
213 bs_trace_warning_line("Found a previous, still RUNNING process w pid %li with the same sim_id and device port which would interfere with this one, aborting\n", his_pid);
214 #endif
215 }
216
217 if (other_dead){
218 bs_trace_warning_line("Found previous lock owned by DEAD process (pid was %li), will attempt to take over\n", his_pid);
219 lock_file_fill(filename, my_pid);
220 return 0;
221 } else {
222 return 1;
223 }
224
225 #else
226 return 0;
227 #endif
228 }
229
pb_remove_lock_file(char ** file_path)230 void pb_remove_lock_file(char** file_path){
231 if (*file_path) {
232 remove(*file_path);
233 free(*file_path);
234 *file_path = NULL;
235 }
236 }
237
phy_test_and_create_lock_file(pb_phy_state_t * this,const char * phy_id)238 static int phy_test_and_create_lock_file(pb_phy_state_t *this, const char *phy_id){
239 int flen = pb_com_path_length + 20 + strlen(phy_id);
240 this->lock_path = (char*) bs_calloc(flen, sizeof(char));
241 sprintf(this->lock_path, "%s/%s.phy.lock", pb_com_path, phy_id);
242
243 int ret = pb_test_and_create_lock_file(this->lock_path);
244 if (ret) {
245 free(this->lock_path);
246 this->lock_path = NULL;
247 }
248 return ret;
249 }
250
pb_device_test_and_create_lock_file(pb_dev_state_t * this,const char * phy_id,unsigned int dev_nbr)251 int pb_device_test_and_create_lock_file(pb_dev_state_t *this, const char *phy_id, unsigned int dev_nbr){
252 int flen = pb_com_path_length + 20 + strlen(phy_id) + bs_number_strlen(dev_nbr);
253
254 this->lock_path = (char*) bs_calloc(flen, sizeof(char));
255
256 sprintf(this->lock_path, "%s/%s.d%i.lock", pb_com_path, phy_id, dev_nbr);
257 int ret = pb_test_and_create_lock_file(this->lock_path);
258 if (ret) {
259 free(this->lock_path);
260 this->lock_path = NULL;
261 }
262 return ret;
263 }
264
265
266 /**
267 * Initialize the communication with the devices:
268 *
269 * inputs:
270 * this Pointer to structure where the connection status will be kept.
271 * MUST be initialized with zeroes.
272 * s String identifying the simulation
273 * p String identifying this phy in this simulation
274 * n How many devices we expect during the simulation
275 *
276 * returns:
277 * 0 if ok. Any other number on error
278 */
pb_phy_initcom(pb_phy_state_t * this,const char * s,const char * p,uint n)279 int pb_phy_initcom(pb_phy_state_t *this, const char* s, const char *p, uint n) {
280
281 signal(SIGPIPE, SIG_IGN);
282
283 if (this->device_connected) {
284 bs_trace_warning_line("%s called twice in a simulation\n", __func__);
285 return -1;
286 }
287
288 (void)pb_check_sim_id(s);
289
290 pb_com_path_length = pb_create_com_folder(s);
291
292 this->device_connected = NULL;
293 this->lock_path = NULL;
294
295 if ( phy_test_and_create_lock_file(this, p) ) {
296 return -1;
297 }
298
299 this->n_devices = n;
300 this->device_connected = (bool *) bs_calloc(n, sizeof(bool));
301 this->ff_path_dtp = (char **) bs_calloc(n, sizeof(char *));
302 this->ff_path_ptd = (char **) bs_calloc(n, sizeof(char *));
303 this->ff_dtp = (int *) bs_calloc(n, sizeof(int *));
304 this->ff_ptd = (int *) bs_calloc(n, sizeof(int *));
305
306 for (int d = 0; d < this->n_devices; d++) {
307 int flen = pb_com_path_length + 30 + strlen(p) + bs_number_strlen(d);
308 this->ff_path_dtp[d] = (char *)bs_calloc(flen, sizeof(char));
309 this->ff_path_ptd[d] = (char *)bs_calloc(flen, sizeof(char));
310 sprintf(this->ff_path_dtp[d], "%s/%s.d%i.dtp", pb_com_path, p, d);
311 sprintf(this->ff_path_ptd[d], "%s/%s.d%i.ptd", pb_com_path, p, d);
312
313 if ((pb_create_fifo_if_not_there(this->ff_path_dtp[d]) != 0)
314 || (pb_create_fifo_if_not_there(this->ff_path_ptd[d]) != 0)) {
315 pb_phy_disconnect_devices(this);
316 bs_trace_error_line("Could not create FIFOs to device %i\n", d);
317 }
318
319 if ((this->ff_ptd[d] = open(this->ff_path_ptd[d], O_WRONLY)) == -1) {
320 this->ff_ptd[d] = 0;
321 pb_phy_disconnect_devices(this);
322 bs_trace_error_line("Opening FIFO from phy to device %i failed\n", d);
323 }
324 if ((this->ff_dtp[d] = open(this->ff_path_dtp[d], O_RDONLY)) == -1) {
325 this->ff_dtp[d] = 0;
326 pb_phy_disconnect_devices(this);
327 bs_trace_error_line("Opening FIFO from device %i to phy failed\n", d);
328 }
329
330 this->device_connected[d] = true;
331 bs_trace_raw(9,"Connected to device %i\n", d);
332 }
333
334 return 0;
335 }
336
pb_phy_free_one_device(pb_phy_state_t * this,int d)337 void pb_phy_free_one_device(pb_phy_state_t *this, int d) {
338 if (this->ff_dtp[d]) {
339 close(this->ff_dtp[d]);
340 this->ff_dtp[d] = 0;
341 }
342 if (this->ff_path_dtp[d]) {
343 remove(this->ff_path_dtp[d]);
344 free(this->ff_path_dtp[d]);
345 this->ff_path_dtp[d] = NULL;
346 }
347 if (this->ff_ptd[d]) {
348 close(this->ff_ptd[d]);
349 this->ff_ptd[d] = 0;
350 }
351 if (this->ff_path_ptd[d]) {
352 remove(this->ff_path_ptd[d]);
353 free(this->ff_path_ptd[d]);
354 this->ff_path_ptd[d] = NULL;
355 }
356 this->device_connected[d] = false;
357 }
358
359 /**
360 * Disconnect all devices we are still connected to,
361 * free all the memory we used and delete the FIFOs
362 *
363 * It is safe to call this function multiple times
364 */
pb_phy_disconnect_devices(pb_phy_state_t * this)365 void pb_phy_disconnect_devices(pb_phy_state_t *this) {
366
367 if (this->device_connected != NULL) {
368 pc_header_t header = PB_MSG_DISCONNECT;
369 for (int d = 0; d < this->n_devices; d++) {
370 if (this->ff_ptd[d]) {
371 write(this->ff_ptd[d], &header, sizeof(header));
372 }
373 pb_phy_free_one_device(this, d);
374 }
375
376 if (pb_com_path) {
377 rmdir(pb_com_path);
378 free(pb_com_path);
379 pb_com_path = NULL;
380 }
381
382 if (this->device_connected) {
383 free(this->device_connected);
384 this->device_connected = NULL;
385 }
386 if (this->ff_path_dtp) {
387 free(this->ff_path_dtp);
388 this->ff_path_dtp = NULL;
389 }
390 if (this->ff_dtp) {
391 free(this->ff_dtp);
392 this->ff_dtp = NULL;
393 }
394 if (this->ff_path_ptd) {
395 free(this->ff_path_ptd);
396 this->ff_path_ptd = NULL;
397 }
398 if (this->ff_ptd) {
399 free(this->ff_ptd);
400 this->ff_ptd = NULL;
401 }
402 }
403 pb_remove_lock_file(&this->lock_path);
404 }
405
406 /**
407 * Check if we are connected to this device (or any device)
408 * Return 1 if we are
409 */
pb_phy_is_connected_to_device(pb_phy_state_t * this,uint d)410 int pb_phy_is_connected_to_device(pb_phy_state_t *this, uint d){
411 if ((this->device_connected == NULL) || (!this->device_connected[d])) {
412 bs_trace_error_line("Programming error while trying to talk to device %i\n", d);
413 return 0;
414 }
415 return 1;
416 }
417
418 /**
419 * Respond to the device at the end of wait
420 */
pb_phy_resp_wait(pb_phy_state_t * this,uint d)421 void pb_phy_resp_wait(pb_phy_state_t *this, uint d) {
422 if ( pb_phy_is_connected_to_device(this, d) ) {
423 pc_header_t header = PB_MSG_WAIT_END;
424 write(this->ff_ptd[d], &header, sizeof(header));
425 }
426 }
427
428 /**
429 * Get (and return) the next request from this device
430 */
pb_phy_get_next_request(pb_phy_state_t * this,uint d)431 pc_header_t pb_phy_get_next_request(pb_phy_state_t *this, uint d) {
432 pc_header_t header = PB_MSG_DISCONNECT;
433
434 if ( pb_phy_is_connected_to_device(this, d) ) {
435 int n = read(this->ff_dtp[d], &header, sizeof(header));
436 if (n < sizeof(header)) {
437 bs_trace_warning_line("Device %u left the party unsuspectingly.. I treat it as if it disconnected\n", d);
438 }
439
440 if ((header == PB_MSG_DISCONNECT) || (header == PB_MSG_TERMINATE)) {
441 //if the read failed or the device really wants to disconnect
442 pb_phy_free_one_device(this, d);
443 }
444 }
445 return header;
446 }
447
pb_phy_get_wait_s(pb_phy_state_t * this,uint d,pb_wait_t * wait_s)448 void pb_phy_get_wait_s(pb_phy_state_t *this, uint d, pb_wait_t *wait_s) {
449 if ( pb_phy_is_connected_to_device(this, d) ) {
450 read(this->ff_dtp[d], wait_s, sizeof(pb_wait_t));
451 }
452 }
453
454 /**
455 * Initialize the communication interface with the phy
456 *
457 * inputs:
458 * this Pointer to structure where the connection status will be keps.
459 * MUST be initialized with zeroes.
460 * d The device number this device will have in this phy
461 * s String identifying the simulation
462 * p String identifying this phy in this simulation
463 *
464 * returns:
465 * 0 if ok. Any other number on error
466 */
pb_dev_init_com(pb_dev_state_t * this,uint d,const char * s,const char * p)467 int pb_dev_init_com(pb_dev_state_t *this, uint d, const char* s, const char *p) {
468
469 if (this->connected) {
470 bs_trace_warning_line("%s called twice in a simulation\n", __func__);
471 return -1;
472 }
473
474 (void)pb_check_sim_id(s);
475
476 /* In case we fail, we initialize them to "invalid" content*/
477 this->ff_path_dtp = NULL;
478 this->ff_path_ptd = NULL;
479
480 this->ff_ptd = 0; /*0 == stdin == not one we would have used */
481 this->ff_dtp = 0;
482
483 if ((s == NULL) || (p == NULL)) {
484 bs_trace_error_line("The simulation and phy identification strings need to be provided\n");
485 }
486
487 this->this_dev_nbr = d;
488 pb_com_path_length = pb_create_com_folder(s);
489
490 if ( pb_device_test_and_create_lock_file(this, p, d) ) {
491 bs_trace_error_line("Failed to get lock\n");
492 }
493
494 int flen = pb_com_path_length + strlen(p) + bs_number_strlen(d) + 30;
495 this->ff_path_dtp = (char *) bs_calloc(flen, sizeof(char));
496 this->ff_path_ptd = (char *) bs_calloc(flen, sizeof(char));
497 sprintf(this->ff_path_dtp, "%s/%s.d%i.dtp", pb_com_path, p, d);
498 sprintf(this->ff_path_ptd, "%s/%s.d%i.ptd", pb_com_path, p, d);
499
500 if ((pb_create_fifo_if_not_there(this->ff_path_dtp) != 0)
501 || (pb_create_fifo_if_not_there(this->ff_path_ptd) != 0)) {
502 pb_dev_clean_up(this);
503 bs_trace_error_line("Could not create FIFOs");
504 }
505
506 if (((this->ff_ptd = open(this->ff_path_ptd, O_RDONLY )) == -1)) {
507 this->ff_ptd = 0;
508 pb_dev_clean_up(this);
509 bs_trace_error_line("Opening FIFO from phy to device failed\n");
510 }
511 if (((this->ff_dtp = open(this->ff_path_dtp, O_WRONLY )) == -1)) {
512 this->ff_dtp = 0;
513 pb_dev_clean_up(this);
514 bs_trace_error_line("Opening FIFO from device to phy failed\n");
515 }
516
517 this->connected = true;
518 is_base_com_initialized = true;
519 return 0;
520 }
521
522 /**
523 * Attempt to terminate the simulation and disconnect
524 */
pb_dev_terminate(pb_dev_state_t * this)525 void pb_dev_terminate(pb_dev_state_t *this) {
526 if (this->connected) {
527 pc_header_t header = PB_MSG_TERMINATE;
528
529 write(this->ff_dtp, &header, sizeof(header));
530 pb_dev_clean_up(this);
531 }
532 }
533
534 /**
535 * Disconnect from the phy
536 */
pb_dev_disconnect(pb_dev_state_t * this)537 void pb_dev_disconnect(pb_dev_state_t *this) {
538 if (this->connected) {
539 pc_header_t header = PB_MSG_DISCONNECT;
540
541 write(this->ff_dtp, &header, sizeof(header));
542 pb_dev_clean_up(this);
543 }
544 }
545
546 /*
547 * Try to delete the FIFOs, clear memory and try to delete the directory
548 *
549 * It is safe to call this function (unnecessarily) several times
550 */
pb_dev_clean_up(pb_dev_state_t * this)551 void pb_dev_clean_up(pb_dev_state_t *this) {
552
553 pb_remove_lock_file(&this->lock_path);
554
555 this->connected = false; //we don't want any possible future call to libphycom to attempt to talk with the phy
556
557 if (this->ff_path_dtp) {
558 if (this->ff_dtp) {
559 close(this->ff_dtp);
560 this->ff_dtp = 0;
561 }
562
563 remove(this->ff_path_dtp);
564 free(this->ff_path_dtp);
565 this->ff_path_dtp = NULL;
566 }
567
568 if (this->ff_path_ptd) {
569 if (this->ff_ptd) {
570 close(this->ff_ptd);
571 this->ff_ptd = 0;
572 }
573 remove(this->ff_path_ptd);
574 free(this->ff_path_ptd);
575 this->ff_path_ptd = NULL;
576 }
577
578 if (pb_com_path != NULL) {
579 rmdir(pb_com_path);
580 free(pb_com_path);
581 pb_com_path = NULL;
582 }
583 }
584
585 /**
586 * Read from a FIFO n_bytes
587 * returns -1 on failure (it can't read n_bytes, and cleans up),
588 * otherwise returns n_bytes
589 */
pb_dev_read(pb_dev_state_t * this,void * buf,size_t n_bytes)590 int pb_dev_read(pb_dev_state_t *this, void *buf, size_t n_bytes) {
591 int read_b;
592
593 read_b = read(this->ff_ptd, buf, n_bytes);
594
595 if (n_bytes == read_b) {
596 return read_b;
597 }
598
599 bs_trace_warning_line(COM_FAILED_ERROR " (tried to get %i got %i bytes)\n", n_bytes, read_b);
600 pb_dev_clean_up(this);
601 return -1;
602 }
603
604 /**
605 * Request a non blocking wait to the phy
606 * Note that eventually the caller needs to pick the wait response
607 * from the phy with cb_dev_pick_wait_resp()
608 */
pb_dev_request_wait_nonblock(pb_dev_state_t * this,pb_wait_t * wait_s)609 int pb_dev_request_wait_nonblock(pb_dev_state_t *this, pb_wait_t *wait_s) {
610 CHECK_CONNECTED(this->connected);
611 pb_send_msg(this->ff_dtp, PB_MSG_WAIT, (void *)wait_s, sizeof(pb_wait_t));
612 return 0;
613 }
614
615 /**
616 * Block until getting a wait response from the phy
617 * If everything goes ok, the phy has just reached the
618 * requested end time and 0 is returned
619 * Otherwise, we should disconnect (-1 will be returned)
620 */
pb_dev_pick_wait_resp(pb_dev_state_t * this)621 int pb_dev_pick_wait_resp(pb_dev_state_t *this) {
622 CHECK_CONNECTED(this->connected);
623
624 pc_header_t header = PB_MSG_DISCONNECT;
625
626 if (pb_dev_read(this, &header, sizeof(header)) == -1) {
627 return -1;
628 }
629
630 if (header == PB_MSG_DISCONNECT) {
631 pb_dev_clean_up(this);
632 return -1;
633 } else if (header == PB_MSG_WAIT_END) {
634 return 0;
635 } else {
636 INVALID_RESP(header);
637 return -1;
638 }
639 }
640
641 /**
642 * Request a wait to the phy and block until receiving the response
643 * If everything goes ok 0 is returned
644 *
645 * Otherwise, we should disconnect (-1 will be returned)
646 */
pb_dev_request_wait_block(pb_dev_state_t * this,pb_wait_t * wait_s)647 int pb_dev_request_wait_block(pb_dev_state_t *this, pb_wait_t *wait_s) {
648 CHECK_CONNECTED(this->connected);
649 int ret;
650 ret = pb_dev_request_wait_nonblock(this, wait_s);
651 if (ret)
652 return ret;
653 return pb_dev_pick_wait_resp(this);
654 }
655