1 /*
2  * Copyright (c) 2015 Carlos Pizano-Uribe  cpu@chromium.org
3  *
4  * Use of this source code is governed by a MIT-style
5  * license that can be found in the LICENSE file or at
6  * https://opensource.org/licenses/MIT
7  */
8 
9 #include <lk/debug.h>
10 #include <lk/err.h>
11 #include <rand.h>
12 #include <string.h>
13 #include <lk/trace.h>
14 
15 #include <kernel/event.h>
16 #include <kernel/port.h>
17 #include <kernel/thread.h>
18 
19 #include <platform.h>
20 
21 #include <app/tests.h>
22 
23 #define LOCAL_TRACE 0
24 
25 void *context1 = (void *) 0x53;
26 
dump_port_result(const port_result_t * result)27 static void dump_port_result(const port_result_t *result) {
28     const port_packet_t *p = &result->packet;
29     LTRACEF("[%02x %02x %02x %02x %02x %02x %02x %02x]\n",
30             p->value[0], p->value[1], p->value[2], p->value[3],
31             p->value[4], p->value[5], p->value[6], p->value[7]);
32 }
33 
single_thread_basic(void)34 static int single_thread_basic(void) {
35     port_t w_port;
36     status_t st = port_create("sh_prt1", PORT_MODE_UNICAST, &w_port);
37     if (st < 0) {
38         printf("could not create port, status = %d\n", st);
39         return __LINE__;
40     }
41 
42     port_t r_port;
43     st = port_open("sh_prt0", context1, &r_port);
44     if (st != ERR_NOT_FOUND) {
45         printf("expected not to find port, status = %d\n", st);
46         return __LINE__;
47     }
48 
49     st = port_open("sh_prt1", context1, &r_port);
50     if (st < 0) {
51         printf("could not open port, status = %d\n", st);
52         return __LINE__;
53     }
54 
55     port_packet_t packet[3] = {
56         {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}},
57         {{0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11}},
58         {{0x33, 0x66, 0x99, 0xcc, 0x33, 0x66, 0x99, 0xcc}},
59     };
60 
61     st = port_write(w_port, &packet[0], 1);
62     if (st < 0) {
63         printf("could not write port, status = %d\n", st);
64         return __LINE__;
65     }
66 
67     printf("reading from port:\n");
68 
69     port_result_t res = {0};
70 
71     st = port_read(r_port, 0, &res);
72     if (st < 0) {
73         printf("could not read port, status = %d\n", st);
74         return __LINE__;
75     }
76     if (res.ctx != context1) {
77         printf("bad context! = %p\n", res.ctx);
78         return __LINE__;
79     }
80 
81     st = port_read(r_port, 0, &res);
82     if (st != ERR_TIMED_OUT) {
83         printf("expected timeout, status = %d\n", st);
84         return __LINE__;
85     }
86 
87     st = port_write(w_port, &packet[1], 1);
88     if (st < 0) {
89         printf("could not write port, status = %d\n", st);
90         return __LINE__;
91     }
92 
93     st = port_write(w_port, &packet[0], 1);
94     if (st < 0) {
95         printf("could not write port, status = %d\n", st);
96         return __LINE__;
97     }
98 
99     st = port_write(w_port, &packet[2], 1);
100     if (st < 0) {
101         printf("could not write port, status = %d\n", st);
102         return __LINE__;
103     }
104 
105     int expected_count = 3;
106     while (true) {
107         st = port_read(r_port, 0, &res);
108         if (st < 0)
109             break;
110         dump_port_result(&res);
111         --expected_count;
112     }
113 
114     if (expected_count != 0) {
115         printf("invalid read count = %d\n", expected_count);
116         return __LINE__;
117     }
118 
119     printf("\n");
120 
121     // port should be empty. should be able to write 8 packets.
122     expected_count = 8;
123     while (true) {
124         st = port_write(w_port, &packet[1], 1);
125         if (st < 0)
126             break;
127         --expected_count;
128         st = port_write(w_port, &packet[2], 1);
129         if (st < 0)
130             break;
131         --expected_count;
132     }
133 
134     if (expected_count != 0) {
135         printf("invalid write count = %d\n", expected_count);
136         return __LINE__;
137     }
138 
139     // tod(cpu) fix this possibly wrong error.
140     if (st != ERR_PARTIAL_WRITE) {
141         printf("expected buffer error, status =%d\n", st);
142         return __LINE__;
143     }
144 
145     // read 3 packets.
146     for (int ix = 0; ix != 3; ++ix) {
147         st = port_read(r_port, 0, &res);
148         if (st < 0) {
149             printf("could not read port, status = %d\n", st);
150             return __LINE__;
151         }
152     }
153 
154     // there are 5 packets, now we add another 3.
155     st = port_write(w_port, packet, 3);
156     if (st < 0) {
157         printf("could not write port, status = %d\n", st);
158         return __LINE__;
159     }
160 
161     expected_count = 8;
162     while (true) {
163         st = port_read(r_port, 0, &res);
164         if (st < 0)
165             break;
166         dump_port_result(&res);
167         --expected_count;
168     }
169 
170     if (expected_count != 0) {
171         printf("invalid read count = %d\n", expected_count);
172         return __LINE__;
173     }
174 
175     // attempt to use the wrong port.
176     st = port_write(r_port, &packet[1], 1);
177     if (st !=  ERR_BAD_HANDLE) {
178         printf("expected bad handle error, status = %d\n", st);
179         return __LINE__;
180     }
181 
182     st = port_read(w_port, 0, &res);
183     if (st !=  ERR_BAD_HANDLE) {
184         printf("expected bad handle error, status = %d\n", st);
185         return __LINE__;
186     }
187 
188     st = port_close(r_port);
189     if (st < 0) {
190         printf("could not close read port, status = %d\n", st);
191         return __LINE__;
192     }
193 
194     st = port_close(w_port);
195     if (st < 0) {
196         printf("could not close write port, status = %d\n", st);
197         return __LINE__;
198     }
199 
200     st = port_close(r_port);
201     if (st != ERR_BAD_HANDLE) {
202         printf("expected bad handle error, status = %d\n", st);
203         return __LINE__;
204     }
205 
206     st = port_close(w_port);
207     if (st != ERR_BAD_HANDLE) {
208         printf("expected bad handle error, status = %d\n", st);
209         return __LINE__;
210     }
211 
212     st = port_destroy(w_port);
213     if (st < 0) {
214         printf("could not destroy port, status = %d\n", st);
215         return __LINE__;
216     }
217 
218     printf("single_thread_basic : ok\n");
219     return 0;
220 }
221 
ping_pong_thread(void * arg)222 static int ping_pong_thread(void *arg) {
223     port_t r_port;
224     status_t st = port_open("ping_port", NULL, &r_port);
225     if (st < 0) {
226         printf("thread: could not open port, status = %d\n", st);
227         return __LINE__;
228     }
229 
230     bool should_dispose_pong_port = true;
231     port_t w_port;
232     st = port_create("pong_port", PORT_MODE_UNICAST, &w_port);
233     if (st == ERR_ALREADY_EXISTS) {
234         // won the race to create the port.
235         should_dispose_pong_port = false;
236     } else if (st < 0) {
237         printf("thread: could not open port, status = %d\n", st);
238         return __LINE__;
239     }
240 
241     port_result_t pr;
242 
243     // the loop is read-mutate-write until the write port
244     // is closed by the master thread.
245     while (true) {
246         st = port_read(r_port, INFINITE_TIME, &pr);
247 
248         if (st == ERR_CANCELLED) {
249             break;
250         } else if (st < 0) {
251             printf("thread: could not read port, status = %d\n", st);
252             return __LINE__;
253         }
254 
255         pr.packet.value[0]++;
256         pr.packet.value[5]--;
257 
258         st = port_write(w_port, &pr.packet, 1);
259         if (st < 0) {
260             printf("thread: could not write port, status = %d\n", st);
261             return __LINE__;
262         }
263     }
264 
265     port_close(r_port);
266 
267     if (should_dispose_pong_port) {
268         port_close(w_port);
269         port_destroy(w_port);
270     }
271 
272     return 0;
273 
274 bail:
275     return __LINE__;
276 }
277 
278 
two_threads_basic(void)279 static int two_threads_basic(void) {
280     port_t w_port;
281     status_t st = port_create("ping_port", PORT_MODE_BROADCAST, &w_port);
282     if (st < 0) {
283         printf("could not create port, status = %d\n", st);
284         return __LINE__;
285     }
286 
287     thread_t *t1 = thread_create(
288                        "worker1", &ping_pong_thread, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
289     thread_t *t2 = thread_create(
290                        "worker2", &ping_pong_thread, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
291     thread_resume(t1);
292     thread_resume(t2);
293 
294     // wait for the pong port to be created, the two threads race to do it.
295     port_t r_port;
296     while (true) {
297         st = port_open("pong_port", NULL, &r_port);
298         if (st == NO_ERROR) {
299             break;
300         } else if (st == ERR_NOT_FOUND) {
301             thread_sleep(100);
302         } else {
303             printf("could not open port, status = %d\n", st);
304             return __LINE__;
305         }
306     }
307 
308     // We have two threads listening to the ping port. Which both reply
309     // on the pong port, so we get two packets in per packet out.
310     const int passes = 256;
311     printf("two_threads_basic test, %d passes\n", passes);
312 
313     port_packet_t packet_out = {{0xaf, 0x77, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05}};
314 
315     port_result_t pr;
316     for (int ix = 0; ix != passes; ++ix) {
317         const size_t count = 1 + ((unsigned int)rand() % 3);
318 
319         for (size_t jx = 0; jx != count; ++jx) {
320             st = port_write(w_port, &packet_out, 1);
321             if (st < 0) {
322                 printf("could not write port, status = %d\n", st);
323                 return __LINE__;
324             }
325         }
326 
327         packet_out.value[0]++;
328         packet_out.value[5]--;
329 
330         for (size_t jx = 0; jx != count * 2; ++jx) {
331             st = port_read(r_port, INFINITE_TIME, &pr);
332             if (st < 0) {
333                 printf("could not read port, status = %d\n", st);
334                 return __LINE__;
335             }
336 
337             if ((pr.packet.value[0] != packet_out.value[0]) ||
338                     (pr.packet.value[5] != packet_out.value[5])) {
339                 printf("unexpected data in packet, loop %d", ix);
340                 return __LINE__;
341             }
342         }
343     }
344 
345     thread_sleep(100);
346 
347     // there should be no more packets to read.
348     st = port_read(r_port, 0, &pr);
349     if (st != ERR_TIMED_OUT) {
350         printf("unexpected packet, status = %d\n", st);
351         return __LINE__;
352     }
353 
354     printf("two_threads_basic master shutdown\n");
355 
356     st = port_close(r_port);
357     if (st < 0) {
358         printf("could not close port, status = %d\n", st);
359         return __LINE__;
360     }
361 
362     st = port_close(w_port);
363     if (st < 0) {
364         printf("could not close port, status = %d\n", st);
365         return __LINE__;
366     }
367 
368     st = port_destroy(w_port);
369     if (st < 0) {
370         printf("could not destroy port, status = %d\n", st);
371         return __LINE__;
372     }
373 
374     int retcode = -1;
375     thread_join(t1, &retcode, INFINITE_TIME);
376     if (retcode)
377         goto fail;
378 
379     thread_join(t2,  &retcode, INFINITE_TIME);
380     if (retcode)
381         goto fail;
382 
383     return 0;
384 
385 fail:
386     printf("child thread exited with %d\n", retcode);
387     return __LINE__;
388 }
389 
390 #define CMD_PORT_CTX ((void*) 0x77)
391 #define TS1_PORT_CTX ((void*) 0x11)
392 #define TS2_PORT_CTX ((void*) 0x12)
393 
394 typedef enum {
395     ADD_PORT,
396     QUIT
397 } action_t;
398 
399 typedef struct {
400     action_t what;
401     port_t port;
402 } watcher_cmd;
403 
send_watcher_cmd(port_t cmd_port,action_t action,port_t port)404 static status_t send_watcher_cmd(port_t cmd_port, action_t action, port_t port) {
405     watcher_cmd _cmd  = {action, port};
406     return port_write(cmd_port, ((port_packet_t *) &_cmd), 1);;
407 }
408 
group_watcher_thread(void * arg)409 static int group_watcher_thread(void *arg) {
410     port_t watched[8] = {0};
411     status_t st = port_open("grp_ctrl", CMD_PORT_CTX, &watched[0]);
412     if (st < 0) {
413         printf("could not open port, status = %d\n", st);
414         return __LINE__;
415     }
416 
417     size_t count = 1;
418     port_t group;
419     int ctx_count = -1;
420 
421     while (true) {
422         st = port_group(watched, count, &group);
423         if (st < 0) {
424             printf("could not make group, status = %d\n", st);
425             return __LINE__;
426         }
427 
428         port_result_t pr;
429         while (true) {
430             st = port_read(group, INFINITE_TIME, &pr);
431             if (st < 0) {
432                 printf("could not read port, status = %d\n", st);
433                 return __LINE__;
434             }
435 
436             if (pr.ctx == CMD_PORT_CTX) {
437                 break;
438             } else if (pr.ctx == TS1_PORT_CTX) {
439                 ctx_count += 1;
440             } else if (pr.ctx == TS2_PORT_CTX) {
441                 ctx_count += 2;
442             } else {
443                 printf("unknown context %p\n", pr.ctx);
444                 return __LINE__;
445             }
446         }
447 
448         // Either adding a port or exiting; either way close the
449         // existing group port and create a new one if needed
450         // at the top of the loop.
451 
452         port_close(group);
453         watcher_cmd *wc = (watcher_cmd *) &pr.packet;
454 
455         if (wc->what == ADD_PORT) {
456             watched[count++] = wc->port;
457         }  else if (wc->what == QUIT) {
458             break;
459         } else {
460             printf("unknown command %d\n", wc->what);
461             return __LINE__;
462         }
463     }
464 
465     if (ctx_count !=  2) {
466         printf("unexpected context count %d", ctx_count);
467         return __LINE__;
468     }
469 
470     printf("group watcher shutdown\n");
471 
472     for (size_t ix = 0; ix != count; ++ix) {
473         st = port_close(watched[ix]);
474         if (st < 0) {
475             printf("failed to close read port, status = %d\n", st);
476             return __LINE__;
477         }
478     }
479 
480     return 0;
481 }
482 
make_port_pair(const char * name,void * ctx,port_t * write,port_t * read)483 static status_t make_port_pair(const char *name, void *ctx, port_t *write, port_t *read) {
484     status_t st = port_create(name, PORT_MODE_UNICAST, write);
485     if (st < 0)
486         return st;
487     return port_open(name,ctx, read);
488 }
489 
group_basic(void)490 static int group_basic(void) {
491     // we spin a thread that connects to a well known port, then we
492     // send two ports that it will add to a group port.
493     port_t cmd_port;
494     status_t st = port_create("grp_ctrl", PORT_MODE_UNICAST, &cmd_port);
495     if (st < 0 ) {
496         printf("could not create port, status = %d\n", st);
497         return __LINE__;
498     }
499 
500     thread_t *wt = thread_create(
501                        "g_watcher", &group_watcher_thread, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
502     thread_resume(wt);
503 
504     port_t w_test_port1, r_test_port1;
505     st = make_port_pair("tst_port1", TS1_PORT_CTX, &w_test_port1, &r_test_port1);
506     if (st < 0)
507         return __LINE__;
508 
509     port_t w_test_port2, r_test_port2;
510     st = make_port_pair("tst_port2", TS2_PORT_CTX, &w_test_port2, &r_test_port2);
511     if (st < 0)
512         return __LINE__;
513 
514     st = send_watcher_cmd(cmd_port, ADD_PORT, r_test_port1);
515     if (st < 0)
516         return __LINE__;
517 
518     st = send_watcher_cmd(cmd_port, ADD_PORT, r_test_port2);
519     if (st < 0)
520         return __LINE__;
521 
522     thread_sleep(50);
523 
524     port_packet_t pp = {{0}};
525     st = port_write(w_test_port1, &pp, 1);
526     if (st < 0)
527         return __LINE__;
528 
529     st = port_write(w_test_port2, &pp, 1);
530     if (st < 0)
531         return __LINE__;
532 
533     st = send_watcher_cmd(cmd_port, QUIT, 0);
534     if (st < 0)
535         return __LINE__;
536 
537     int retcode = -1;
538     thread_join(wt, &retcode, INFINITE_TIME);
539     if (retcode) {
540         printf("child thread exited with %d\n", retcode);
541         return __LINE__;
542     }
543 
544     st = port_close(w_test_port1);
545     if (st < 0)
546         return __LINE__;
547     st = port_close(w_test_port2);
548     if (st < 0)
549         return __LINE__;
550     st = port_close(cmd_port);
551     if (st < 0)
552         return __LINE__;
553     st = port_destroy(w_test_port1);
554     if (st < 0)
555         return __LINE__;
556     st = port_destroy(w_test_port2);
557     if (st < 0)
558         return __LINE__;
559     st = port_destroy(cmd_port);
560     if (st < 0)
561         return __LINE__;
562 
563     return 0;
564 }
565 
group_dynamic(void)566 static int group_dynamic(void) {
567     status_t st;
568 
569     port_t w_test_port1, r_test_port1;
570     st = make_port_pair("tst_port1", TS1_PORT_CTX, &w_test_port1, &r_test_port1);
571     if (st < 0)
572         return __LINE__;
573 
574     port_t w_test_port2, r_test_port2;
575     st = make_port_pair("tst_port2", TS2_PORT_CTX, &w_test_port2, &r_test_port2);
576     if (st < 0)
577         return __LINE__;
578 
579     port_t pg;
580     st = port_group(&r_test_port1, 1, &pg);
581     if (st < 0)
582         return __LINE__;
583 
584     port_packet_t pkt = { { 0 } };
585     st = port_write(w_test_port2, &pkt, 1);
586     if (st < 0)
587         return __LINE__;
588 
589     port_result_t rslt;
590     st = port_read(pg, 0, &rslt);
591     if (st != ERR_TIMED_OUT)
592         return __LINE__;
593 
594     // Attach the port that has been written to to the port group and ensure
595     // that we can read from it.
596     st = port_group_add(pg, r_test_port2);
597     if (st < 0)
598         return __LINE__;
599 
600     st = port_read(pg, 0, &rslt);
601     if (st < 0)
602         return __LINE__;
603 
604     // Write some data to a port then remove it from the port group and ensure
605     // that we can't read from it.
606     st = port_write(w_test_port1, &pkt, 1);
607     if (st < 0)
608         return __LINE__;
609 
610     st = port_group_remove(pg, r_test_port1);
611     if (st < 0)
612         return __LINE__;
613 
614     st = port_read(pg, 0, &rslt);
615     if (st != ERR_TIMED_OUT)
616         return __LINE__;
617 
618     st = port_close(w_test_port1);
619     if (st < 0)
620         return __LINE__;
621     st = port_close(w_test_port2);
622     if (st < 0)
623         return __LINE__;
624     st = port_destroy(w_test_port1);
625     if (st < 0)
626         return __LINE__;
627     st = port_destroy(w_test_port2);
628     if (st < 0)
629         return __LINE__;
630 
631     return 0;
632 }
633 
634 static event_t group_waiting_sync_evt;
635 
receive_thread(void * arg)636 static int receive_thread(void *arg) {
637     port_t pg = (port_t)arg;
638 
639     // Try to read from an empty port group. When the other thread adds a port
640     // to this port group, we should wake up and
641     port_result_t rslt;
642     status_t st = port_read(pg, 500, &rslt);
643     if (st == ERR_TIMED_OUT)
644         return __LINE__;
645 
646     event_signal(&group_waiting_sync_evt, true);
647 
648     return 0;
649 }
650 
651 /* Test the edge case where a read port with data available is added to a port
652  * group that has a read-blocked receiver.
653  */
group_waiting(void)654 static int group_waiting(void) {
655     status_t st;
656 
657     event_init(&group_waiting_sync_evt, false, EVENT_FLAG_AUTOUNSIGNAL);
658 
659     port_t w_test_port1, r_test_port1;
660     st = make_port_pair("tst_port1", TS1_PORT_CTX, &w_test_port1, &r_test_port1);
661     if (st < 0)
662         return __LINE__;
663 
664     // Write something to this port group that currently has no receivers.
665     port_packet_t pkt = { { 0 } };
666     st = port_write(w_test_port1, &pkt, 1);
667     if (st < 0)
668         return __LINE__;
669 
670     // Create an empty port group.
671     port_t pg;
672     st = port_group(NULL, 0, &pg);
673     if (st < 0)
674         return __LINE__;
675 
676 
677     thread_t *t1 = thread_create(
678                        "receiver",
679                        &receive_thread,
680                        (void *)pg,
681                        DEFAULT_PRIORITY,
682                        DEFAULT_STACK_SIZE
683                    );
684 
685     thread_resume(t1);
686 
687     // Wait for the other thread to block on the read.
688     thread_sleep(20);
689 
690     // Adding a port that has data available to the port group should wake any
691     // threads waiting on that port group.
692     port_group_add(pg, r_test_port1);
693 
694     if (event_wait_timeout(&group_waiting_sync_evt, 500) != NO_ERROR)
695         return __LINE__;
696 
697     st = port_close(w_test_port1);
698     if (st < 0)
699         return __LINE__;
700 
701     st = port_destroy(w_test_port1);
702     if (st < 0)
703         return __LINE__;
704 
705     return 0;
706 }
707 
708 #define RUN_TEST(t)  result = t(); if (result) goto fail
709 
port_tests(int argc,const console_cmd_args * argv)710 int port_tests(int argc, const console_cmd_args *argv) {
711     int result;
712     int count = 3;
713     while (count--) {
714         RUN_TEST(single_thread_basic);
715         RUN_TEST(two_threads_basic);
716         RUN_TEST(group_basic);
717         RUN_TEST(group_dynamic);
718     }
719 
720     printf("all tests passed\n");
721     return 0;
722 fail:
723     printf("test failed at line %d\n", result);
724     return 1;
725 }
726 
727 #undef RUN_TEST
728