Commit 2883853e authored by Joanne Hugé's avatar Joanne Hugé

Update ptp / phc related code

Add GPIO support to software-pps
Update synchronize-phc
Add run-software-pps script
Add sanity check for server
Sleep at beginning of the loop in client
Add sanity check in client too
Add mode to synchronize-phc
Fix mistake in get-ptp-time
Add kill ptp and phc scripts
Add test-ptp
Add option to print time on each line for ptp-get-time
Add nowait option to gettime
Add logging option in test-ptp
Add error correction in test-ptp
Fix encode decode mistake
Don't use strcpy
Fix packet data transfer, add error correction
Add 68us jitter 20mn A20-shuttle signal measure
Update phc script
Change default parameters for test-board-synchro
Update kill scripts to work when called outside of script dir
Update parse-saleae-measure script to include hex exports
parent 39fc5e7a
...@@ -8,6 +8,8 @@ packet-exchange/build/server ...@@ -8,6 +8,8 @@ packet-exchange/build/server
packet-exchange/build/client packet-exchange/build/client
gettime/build/gettime gettime/build/gettime
software-pps/build/software-pps software-pps/build/software-pps
test-ptp/build/server
test-ptp/build/client
scripts/packet-histogram_stop-options* scripts/packet-histogram_stop-options*
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include <unistd.h> #include <unistd.h>
#define NSEC_PER_SEC UINT64_C(1000000000) #define NSEC_PER_SEC UINT64_C(1000000000)
#define MAX_BUFFER_SIZE 10000000
// Structs // Structs
...@@ -42,8 +43,13 @@ static uint64_t ts_to_uint(struct timespec t); ...@@ -42,8 +43,13 @@ static uint64_t ts_to_uint(struct timespec t);
static thread_param_t thread_params; static thread_param_t thread_params;
static uint64_t ptptime; static uint64_t ptptime;
static int quit; static int quit;
static int nowait;
static int print_newline;
static int print_period;
static clockid_t clock_id; static clockid_t clock_id;
static uint64_t ts_buffer[MAX_BUFFER_SIZE];
static void help(char *argv[]) { static void help(char *argv[]) {
printf( printf(
"Usage: %s [-h] [-i USEC]\n" "Usage: %s [-h] [-i USEC]\n"
...@@ -52,6 +58,9 @@ static void help(char *argv[]) { ...@@ -52,6 +58,9 @@ static void help(char *argv[]) {
" -r Get time from CLOCK_REALTIME (default)\n" " -r Get time from CLOCK_REALTIME (default)\n"
" -t Get time from CLOCK_TAI\n" " -t Get time from CLOCK_TAI\n"
" -q Get time then quit\n" " -q Get time then quit\n"
" -L Print new line for each value\n"
" -R USEC Print period\n"
" -F Read clock and store in buffer as fast as possible and dump the buffer at the end\n"
" -i USEC RT thread wake-up interval (default: 10ms)\n" " -i USEC RT thread wake-up interval (default: 10ms)\n"
"\n", "\n",
argv[0]); argv[0]);
...@@ -74,14 +83,26 @@ static void *rt_thread(void *p) { ...@@ -74,14 +83,26 @@ static void *rt_thread(void *p) {
clock_gettime(CLOCK_MONOTONIC, &next); clock_gettime(CLOCK_MONOTONIC, &next);
// Time lookup loop // Time lookup loop
for (;;) { for (int i = 0;; i++) {
clock_gettime(clock_id, &ptptime_ts); clock_gettime(clock_id, &ptptime_ts);
ptptime = ts_to_uint(ptptime_ts); ptptime = ts_to_uint(ptptime_ts);
if(!nowait) {
add_ns(&next, thread_params.interval); add_ns(&next, thread_params.interval);
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL); clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL);
} }
else {
ts_buffer[i] = ptptime;
if((i+1) >= MAX_BUFFER_SIZE) {
for(int j = 0; j < MAX_BUFFER_SIZE; j++)
printf("%" PRIu64 "\n", ts_buffer[j]);
exit(EXIT_SUCCESS);
}
}
}
return NULL; return NULL;
} }
...@@ -99,6 +120,8 @@ int main(int argc, char *argv[]) { ...@@ -99,6 +120,8 @@ int main(int argc, char *argv[]) {
thread_params.interval = 100000 * 1000; thread_params.interval = 100000 * 1000;
thread_params.priority = 98; thread_params.priority = 98;
clock_id = CLOCK_REALTIME; clock_id = CLOCK_REALTIME;
print_newline = 0;
print_period = 100000;
/* Lock all current and future pages from preventing of being paged to /* Lock all current and future pages from preventing of being paged to
* swap */ * swap */
...@@ -149,10 +172,13 @@ int main(int argc, char *argv[]) { ...@@ -149,10 +172,13 @@ int main(int argc, char *argv[]) {
// Verbose loop // Verbose loop
for (;;) { for (;;) {
usleep(100000); usleep(print_period);
printf("%9" PRIu64 ": \n", ptptime); if(!nowait) {
printf("%9" PRIu64 "\n", ptptime);
if(!print_newline)
printf("\033[%dA", 1); printf("\033[%dA", 1);
} }
}
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
} }
...@@ -161,7 +187,7 @@ int main(int argc, char *argv[]) { ...@@ -161,7 +187,7 @@ int main(int argc, char *argv[]) {
static void process_options(int argc, char *argv[]) { static void process_options(int argc, char *argv[]) {
for (;;) { for (;;) {
int c = getopt(argc, argv, "hi:qmrt"); int c = getopt(argc, argv, "hi:qmrtLR:F");
if (c == -1) break; if (c == -1) break;
...@@ -185,6 +211,15 @@ static void process_options(int argc, char *argv[]) { ...@@ -185,6 +211,15 @@ static void process_options(int argc, char *argv[]) {
case 'q': case 'q':
quit = 1; quit = 1;
break; break;
case 'L':
print_newline = 1;
break;
case 'R':
print_period = atoi(optarg);
break;
case 'F':
nowait = 1;
break;
} }
} }
} }
......
...@@ -64,6 +64,8 @@ no, 0h0, 1000us (10949) | 0.0000us | 239.0000us | 140.4619us ...@@ -64,6 +64,8 @@ no, 0h0, 1000us (10949) | 0.0000us | 239.0000us | 140.4619us
no, 15h1, 1000us (54075815) | 0.0000us | 550.0000us | 299.5232us | 7.1155us | 0 no, 15h1, 1000us (54075815) | 0.0000us | 550.0000us | 299.5232us | 7.1155us | 0
no, 15h0, 500us (108100303) | 0.0000us | 457.0000us | 246.4996us | 7.5832us | 0 no, 15h0, 500us (108100303) | 0.0000us | 457.0000us | 246.4996us | 7.5832us | 0
no, 62h51, 1000us (226318412) | 0.0000us | 586.0000us | 268.5267us | 4.6224us | 0 no, 62h51, 1000us (226318412) | 0.0000us | 586.0000us | 268.5267us | 4.6224us | 0
no, 14h2, 1000us (50560753) | 0.0000us | 418.0000us | 192.4910us | 11.2624us | 0
no, 14h2, 1000us (50562481) | 0.0000us | 399.0000us | 169.4949us | 5.6921us | 0
![alt text](measures/graphs/packet_jitterPacket jitter.png "packet_jitter Graph") ![alt text](measures/graphs/packet_jitterPacket jitter.png "packet_jitter Graph")
...@@ -85,12 +87,12 @@ Minimum | Maximum | Average | Standard deviation | Lost packets ...@@ -85,12 +87,12 @@ Minimum | Maximum | Average | Standard deviation | Lost packets
Two A20 boards are connected end to end with an ethernet cable to a shuttle. All devices are synchronized with PTP. The shuttle sends packets containing timestamps to the boards, and the boards emit a signal at the given timestamp. A logic analyzer measures the variation difference between the signals of the two boards. Two A20 boards are connected end to end with an ethernet cable to a shuttle. All devices are synchronized with PTP. The shuttle sends packets containing timestamps to the boards, and the boards emit a signal at the given timestamp. A logic analyzer measures the variation difference between the signals of the two boards.
**Common test metadata:** Device: A20, Linux kernel version: 5.6, Task priority: 98, Interval: 1000us, Boot Parameters: isolcpus,rcu_nocbs,irqaffinity, ETF qdisc delta: None, Device and processor load: None, Test duration: 0h20, Speed (Mb/s): 1000, ETF offset: 500us, Packet route: E2E, qdisc: pfifo_fast, Client device: A20, XDP: no **Common test metadata:** Device: A20, Linux kernel version: 5.6, Task priority: 98, Interval: 1000us, Boot Parameters: isolcpus,rcu_nocbs,irqaffinity, ETF qdisc delta: None, Device and processor load: None, Test duration: 0h20, Speed (Mb/s): 1000, ETF offset: 500us, Packet route: E2E, qdisc: pfifo_fast, Client device: Shuttle, XDP: no
Minimum | Maximum | Average | Standard deviation | Lost packets Minimum | Maximum | Average | Standard deviation | Lost packets
------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- | ------------ ------------------------------- | ------------------------------- | ------------------------------- | ------------------------------- | ------------
**edge jitter - period jitter** | **edge jitter - period jitter** | **edge jitter - period jitter** | **edge jitter - period jitter** | **edge jitter - period jitter** | **edge jitter - period jitter** | **edge jitter - period jitter** | **edge jitter - period jitter** |
0.0000us - 595.0000us | 216.0000us - 1432.0000us | 6.8536us - 999.5032us | 8.4292us - 45.7510us | 0 0.0000us - 974.0000us | 68.0000us - 1038.0000us | 6.2311us - 999.5382us | 7.9965us - 1.5777us | 0
![alt text](measures/graphs/shuttle_a20_signal_jitteredge jitter.png "shuttle_a20_signal_jitter Graph") ![alt text](measures/graphs/shuttle_a20_signal_jitteredge jitter.png "shuttle_a20_signal_jitter Graph")
......
{"cyclictest_wake-up_latency": {"ids": [3, 5], "next_id": 6}, "shuttle_signal_jitter": {"ids": [0], "next_id": 1}, "packet_jitter": {"ids": [0, 1, 2, 3, 4], "next_id": 5}, "ping_interval": {"ids": [0], "next_id": 1}, "shuttle_a20_signal_jitter": {"ids": [0], "next_id": 1}} {"cyclictest_wake-up_latency": {"ids": [3, 5], "next_id": 6}, "shuttle_signal_jitter": {"ids": [0], "next_id": 1}, "packet_jitter": {"ids": [0, 1, 2, 3, 4, 5, 6], "next_id": 7}, "ping_interval": {"ids": [0], "next_id": 1}, "shuttle_a20_signal_jitter": {"ids": [1], "next_id": 2}}
\ No newline at end of file \ No newline at end of file
{"measure_sets": [{"measure_type": "packet_jitter", "props_names": ["Packet jitter"], "units": ["us"], "middle": 0, "props": [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 2, 0, 2, 0, 0, 1, 2, 0, 2, 0, 1, 1, 2, 1, 2, 3, 6, 1, 4, 13, 9, 11, 21, 20, 29, 34, 46, 46, 57, 71, 109, 126, 131, 169, 189, 220, 268, 275, 299, 299, 317, 354, 411, 390, 415, 457, 452, 427, 441, 485, 503, 522, 466, 493, 520, 449, 515, 507, 527, 486, 544, 531, 480, 532, 543, 521, 592, 568, 600, 658, 713, 759, 792, 855, 856, 918, 1000, 1063, 1102, 1204, 1201, 1283, 1391, 1531, 1655, 1789, 1911, 2160, 2348, 2411, 2729, 2899, 3181, 3463, 3627, 3720, 3899, 3967, 3941, 4022, 4023, 3832, 3767, 3727, 3633, 3608, 3527, 3505, 3585, 3635, 3699, 3735, 3691, 3916, 4054, 4273, 4340, 4413, 4747, 5021, 5544, 6220, 7754, 9955, 15719, 27759, 45877, 53157, 38255, 21822, 15232, 14887, 16356, 18708, 22048, 26228, 31697, 40703, 56029, 81460, 111903, 129296, 133030, 158793, 217361, 258231, 252337, 242746, 257345, 297944, 360375, 458603, 588432, 763775, 1070415, 1624853, 2153920, 1955967, 1170896, 681061, 799046, 1917572, 5349987, 8482733, 5417178, 1517889, 387397, 163570, 147649, 411512, 1161623, 1966590, 1952419, 1369798, 916862, 698339, 560000, 440380, 350904, 290575, 252642, 242449, 259948, 264937, 218348, 163559, 145777, 142029, 118578, 85917, 62291, 49001, 41989, 36521, 32550, 28637, 25665, 22898, 21358, 24453, 39077, 56025, 52835, 34440, 20930, 13957, 10607, 8294, 7153, 6280, 5637, 5525, 5140, 4943, 4737, 4462, 4274, 4059, 3853, 3615, 3419, 3311, 3026, 2842, 2691, 2593, 2419, 2259, 2214, 2061, 2007, 1915, 1940, 1828, 1809, 1777, 1844, 1763, 1809, 1699, 1735, 1766, 1690, 1557, 1539, 1588, 1552, 1647, 1529, 1582, 1705, 1681, 1716, 1734, 1838, 1876, 1938, 1955, 2000, 2020, 1998, 2092, 2036, 2001, 1986, 1994, 1879, 1837, 1660, 1608, 1549, 1376, 1259, 1172, 1165, 1039, 943, 961, 800, 772, 712, 603, 571, 494, 498, 407, 397, 359, 304, 342, 359, 297, 279, 309, 257, 287, 285, 261, 260, 217, 211, 218, 210, 198, 195, 185, 164, 180, 182, 185, 178, 176, 169, 170, 173, 182, 163, 187, 152, 151, 130, 150, 148, 118, 112, 124, 89, 83, 47, 55, 51, 23, 22, 17, 10, 10, 10, 3, 1, 2, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], "props_type": "histogram", "metadata": {"dev": "A20", "ker": "5.6", "prio": "98", "i": "1000us", "boot_p": "isolcpus,rcu_nocbs,irqaffinity", "delta": "None", "load": "None", "duration": "14h2", "speed": "1000", "etf_offset": "500us", "route": "E2E", "qdisc": "pfifo_fast", "client": "Shuttle", "XDP": "no", "lost_packets": "0"}}]}
\ No newline at end of file
{"measure_sets": [{"measure_type": "packet_jitter", "props_names": ["Packet jitter"], "units": ["us"], "middle": 0, "props": [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 2, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 2, 2, 0, 3, 2, 5, 11, 12, 6, 11, 14, 26, 49, 62, 82, 116, 150, 166, 240, 287, 318, 333, 438, 482, 560, 545, 625, 631, 652, 784, 756, 891, 951, 949, 1019, 1052, 1174, 1158, 1263, 1345, 1314, 1460, 1495, 1539, 1609, 1667, 1735, 1766, 1761, 1903, 1869, 1894, 1813, 1873, 1861, 1927, 1917, 1932, 1947, 1896, 1939, 1952, 2004, 2031, 2063, 2181, 2125, 2113, 2141, 2151, 2277, 2366, 2461, 2644, 2639, 3030, 3180, 3544, 3941, 4225, 4740, 5278, 5804, 6491, 7126, 7873, 8760, 9745, 10643, 11338, 12214, 12865, 13979, 15097, 16756, 18636, 20813, 23639, 26388, 30614, 36174, 42745, 52804, 66042, 84070, 108778, 145761, 209566, 328782, 592280, 1323125, 3558947, 8368140, 12154871, 10217858, 6205413, 3236053, 1397915, 580786, 293172, 183589, 133625, 104761, 84672, 70795, 59837, 51392, 45220, 38994, 33418, 28457, 23831, 19982, 16473, 14339, 12128, 10386, 9083, 7988, 7397, 7096, 6658, 6550, 6955, 7476, 8167, 8966, 9863, 10326, 10319, 9970, 9328, 8137, 6836, 5735, 4886, 4077, 3355, 3080, 2736, 2622, 2333, 2341, 2236, 2184, 2037, 1946, 1867, 1724, 1539, 1408, 1382, 1258, 1113, 1077, 979, 950, 905, 880, 890, 869, 879, 839, 877, 858, 837, 848, 857, 817, 853, 862, 832, 906, 864, 911, 833, 880, 910, 998, 996, 1099, 1109, 1200, 1315, 1357, 1394, 1464, 1543, 1592, 1546, 1535, 1456, 1487, 1449, 1430, 1304, 1304, 1206, 1156, 1168, 1060, 1058, 1026, 1099, 1089, 991, 956, 872, 896, 905, 755, 619, 548, 450, 355, 308, 224, 158, 123, 93, 59, 39, 29, 28, 23, 10, 13, 10, 2, 2, 3, 0, 2, 1, 1, 0, 0, 1, 2, 1, 1, 1, 1, 0, 1, 2, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 2, 0, 1, 2, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]], "props_type": "histogram", "metadata": {"dev": "A20", "ker": "5.6", "prio": "98", "i": "1000us", "boot_p": "isolcpus,rcu_nocbs,irqaffinity", "delta": "None", "load": "None", "duration": "14h2", "speed": "1000", "etf_offset": "500us", "route": "E2E", "qdisc": "pfifo_fast", "client": "Shuttle", "XDP": "no", "lost_packets": "0"}}]}
\ No newline at end of file
{"measure_sets": [{"measure_type": "shuttle_a20_signal_jitter", "props_names": ["edge jitter", "period jitter"], "units": ["us", "us"], "middle": 0, "props": [[185988, 159565, 131040, 105630, 84946, 69601, 58997, 47575, 38698, 31479, 25823, 22278, 19474, 17443, 15489, 14020, 13090, 12852, 13110, 12467, 11645, 11115, 10499, 10170, 9818, 9379, 8578, 7377, 6659, 5987, 5672, 5183, 3615, 2170, 1690, 1447, 1348, 1127, 1032, 793, 675, 673, 787, 851, 466, 215, 178, 140, 132, 108, 100, 74, 84, 63, 43, 46, 37, 40, 32, 18, 24, 18, 11, 18, 19, 15, 11, 8, 8, 10, 7, 7, 8, 3, 2, 3, 6, 5, 5, 1, 4, 4, 4, 3, 3, 2, 0, 0, 1, 1, 1, 0, 3, 3, 1, 2, 2, 1, 1, 2, 0, 0, 2, 0, 0, 2, 0, 3, 0, 0, 1, 1, 1, 0, 1, 3, 2, 0, 3, 0, 1, 1, 1, 1, 2, 1, 1, 0, 2, 3, 5, 0, 2, 0, 1, 1, 0, 2, 4, 4, 1, 0, 2, 3, 4, 1, 2, 2, 4, 3, 2, 6, 4, 1, 1, 1, 1, 2, 5, 0, 0, 2, 1, 5, 2, 1, 3, 3, 2, 0, 4, 4, 2, 6, 2, 0, 5, 6, 2, 2, 6, 1, 2, 3, 0, 2, 4, 1, 2, 4, 2, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 0, 2, 0, 1, 2, 0, 2, 1, 0, 1, 1, 1, 0, 0, 0, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 2, 2, 11, 13, 14, 16, 20, 30, 53, 45, 60, 53, 67, 69, 92, 123, 110, 116, 111, 123, 122, 109, 105, 110, 135, 151, 135, 140, 153, 172, 185, 208, 294, 381, 436, 529, 674, 810, 985, 1161, 1380, 1378, 1287, 1101, 892, 689, 548, 375, 286, 219, 185, 148, 130, 149, 154, 214, 238, 252, 258, 201, 171, 114, 82, 56, 30, 27, 12, 10, 15, 12, 16, 10, 11, 8, 10, 9, 7, 4, 4, 1, 5, 6, 5, 3, 3, 3, 4, 2, 5, 1, 1, 4, 1, 1, 3, 0, 1, 4, 3, 3, 3, 2, 3, 6, 0, 3, 3, 0, 0, 1, 0, 1, 1, 2, 3, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 2, 2, 0, 0, 0, 0, 1, 1, 3, 1, 1, 0, 2, 0, 2, 2, 1, 0, 3, 1, 1, 0, 2, 3, 1, 1, 4, 0, 1, 1, 4, 2, 3, 0, 4, 2, 1, 1, 2, 1, 1, 2, 1, 1, 0, 0, 1, 0, 2, 1, 2, 3, 1, 2, 1, 2, 0, 0, 0, 2, 2, 1, 1, 2, 0, 4, 4, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 2, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 2, 0, 0, 1, 1, 1, 1, 0, 0, 1, 2, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 2, 3, 0, 2, 1, 1, 1, 1, 0, 2, 0, 2, 3, 6, 12, 15, 17, 20, 33, 23, 36, 43, 40, 46, 59, 64, 71, 48, 77, 82, 114, 131, 206, 235, 266, 302, 334, 396, 426, 507, 655, 867, 1177, 1507, 1944, 2616, 3813, 6001, 15433, 47814, 65637, 54264, 69074, 35775, 32209, 194152, 730676, 627419, 135556, 19622, 21715, 66558, 48547, 52999, 66239, 26121, 7598, 4081, 3350, 2662, 2026, 1665, 1427, 1220, 972, 782, 645, 537, 451, 384, 340, 313, 247, 219, 179, 133, 129, 105, 79, 53, 52, 27, 14, 25, 18, 25, 29, 22, 35, 23, 17, 21, 11, 13, 11, 2, 9, 1, 2, 3, 2, 0, 3, 0, 0, 1, 0, 4, 0, 0, 1, 0, 0, 4, 2, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 0, 1, 2, 2, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 1, 1, 0, 0, 2, 2, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 2, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 4, 3, 3, 3, 1, 0, 0, 1, 0, 2, 3, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 2, 0, 0, 0, 1, 0, 0, 2, 2, 1, 1, 0, 2, 2, 1, 1, 4, 4, 1, 1, 3, 2, 1, 2, 1, 1, 3, 0, 0, 3, 0, 0, 2, 1, 0, 0, 2, 3, 1, 0, 1, 0, 0, 0, 0, 0, 2, 1, 1, 1, 1, 4, 0, 0, 1, 0, 0, 1, 0, 2, 1, 1, 0, 2, 0, 1, 2, 2, 1, 0, 0, 0, 0, 1, 1, 0, 1, 3, 1, 1, 1, 2, 0, 1, 1, 3, 5, 2, 2, 3, 6, 3, 2, 5, 0, 3, 3, 2, 4, 3, 2, 3, 3, 5, 5, 5, 4, 3, 4, 4, 5, 5, 4, 8, 8, 6, 14, 7, 12, 14, 21, 17, 16, 24, 26, 34, 48, 75, 106, 149, 195, 238, 271, 261, 227, 194, 166, 141, 163, 234, 247, 306, 409, 602, 783, 927, 1100, 1229, 1142, 1122, 988, 767, 649, 649, 564, 511, 481, 375, 323, 250, 220, 206, 186, 142, 152, 120, 112, 117, 121, 133, 110, 125, 109, 117, 106, 86, 91, 61, 55, 73, 34, 46, 42, 26, 25, 20, 19, 7, 14, 7, 7, 2, 1, 0, 2, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]], "props_type": "histogram", "metadata": {"dev": "A20", "ker": "5.6", "prio": "98", "i": "1000us", "boot_p": "isolcpus,rcu_nocbs,irqaffinity", "delta": "None", "load": "None", "duration": "0h20", "speed": "1000", "etf_offset": "500us", "route": "E2E", "qdisc": "pfifo_fast", "client": "A20", "XDP": "no", "lost_packets": "0"}}]} {"measure_sets": [{"measure_type": "shuttle_a20_signal_jitter", "props_names": ["edge jitter", "period jitter"], "units": ["us", "us"], "middle": 0, "props": [[171084, 184732, 162375, 120948, 91327, 73851, 59157, 45345, 34688, 25423, 19038, 15231, 14122, 12180, 10568, 10099, 9914, 9329, 9470, 9119, 8270, 7038, 6713, 6956, 7400, 8418, 9540, 10295, 9049, 6943, 5871, 4899, 3984, 3007, 2557, 2615, 2635, 2117, 1282, 1130, 552, 184, 73, 55, 35, 49, 44, 31, 24, 22, 22, 23, 28, 24, 23, 15, 13, 18, 13, 8, 4, 4, 11, 6, 2, 2, 6, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 8, 33, 57, 80, 125, 172, 238, 372, 415, 503, 606, 596, 697, 789, 997, 1161, 1648, 2508, 4248, 9259, 36669, 245749, 884299, 877124, 257492, 45024, 11471, 5230, 2758, 1677, 1168, 849, 572, 394, 369, 349, 376, 377, 388, 329, 337, 256, 217, 204, 235, 242, 244, 234, 227, 195, 141, 138, 77, 46, 30, 9, 4, 1, 2, 0, 1, 1]], "props_type": "histogram", "metadata": {"dev": "A20", "ker": "5.6", "prio": "98", "i": "1000us", "boot_p": "isolcpus,rcu_nocbs,irqaffinity", "delta": "None", "load": "None", "duration": "0h20", "speed": "1000", "etf_offset": "500us", "route": "E2E", "qdisc": "pfifo_fast", "client": "Shuttle", "XDP": "no", "lost_packets": "0"}}]}
\ No newline at end of file \ No newline at end of file
...@@ -150,6 +150,7 @@ static void *packet_sending_thread(void *p) { ...@@ -150,6 +150,7 @@ static void *packet_sending_thread(void *p) {
} }
next = uint_to_ts(thread_params.start_ts); next = uint_to_ts(thread_params.start_ts);
} }
add_ns(&next, thread_params.interval);
// Set txtime and TX data timestamp if required // Set txtime and TX data timestamp if required
if (enable_etf || thread_params.send_tx_delay) { if (enable_etf || thread_params.send_tx_delay) {
...@@ -170,9 +171,21 @@ static void *packet_sending_thread(void *p) { ...@@ -170,9 +171,21 @@ static void *packet_sending_thread(void *p) {
nb_cycles >= ((unsigned int)thread_params.max_cycles)) nb_cycles >= ((unsigned int)thread_params.max_cycles))
break; break;
clock_gettime(CLOCK_REALTIME, &current);
// Sanity check
if(current.tv_sec > next.tv_sec)
goto invalid_ts;
if(current.tv_sec == next.tv_sec && current.tv_nsec > next.tv_nsec)
goto invalid_ts;
ret = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &next, NULL);
if (ret) {
fprintf(stderr, "clock_nanosleep returned error: %d, aborting...\n", ret);
exit(EXIT_FAILURE);
}
// TX data // TX data
*((uint64_t *)send_data) = encode(thread_params.send_tx_delay ? tx_data : 0xdeadbeef, send_data);
thread_params.send_tx_delay ? tx_data : 0xdeadbeef;
// Get timestamp before TSN task for stats // Get timestamp before TSN task for stats
clock_gettime(CLOCK_REALTIME, &current); clock_gettime(CLOCK_REALTIME, &current);
...@@ -181,7 +194,10 @@ static void *packet_sending_thread(void *p) { ...@@ -181,7 +194,10 @@ static void *packet_sending_thread(void *p) {
// Update txtime and TX data timestamp if required // Update txtime and TX data timestamp if required
if (enable_etf) next_txtime += thread_params.interval; if (enable_etf) next_txtime += thread_params.interval;
if (thread_params.send_tx_delay) tx_data += thread_params.interval; if (thread_params.send_tx_delay) {
tx_data = next.tv_sec * NSEC_PER_SEC + next.tv_nsec;
tx_data += thread_params.send_tx_delay;
}
// Update statistics // Update statistics
if (nb_cycles) { if (nb_cycles) {
...@@ -206,15 +222,15 @@ static void *packet_sending_thread(void *p) { ...@@ -206,15 +222,15 @@ static void *packet_sending_thread(void *p) {
} }
previous = current; previous = current;
ret = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &next, NULL);
if (ret) {
fprintf(stderr, "clock_nanosleep returned error: %d, aborting...\n", ret);
exit(EXIT_FAILURE);
}
} }
pthread_exit(NULL); pthread_exit(NULL);
invalid_ts:
fprintf(stderr, "Invalid timestamp : %" PRIu64 "\n", ts_to_uint(next));
fprintf(stderr, "Current time is : %" PRIu64 "\n", ts_to_uint(current));
fprintf(stderr, "Difference with timestamp : %" PRIi64 "\n\n\n", ((int64_t)ts_to_uint(next)) - ((int64_t)ts_to_uint(current)));
exit(EXIT_FAILURE);
} }
/* /*
......
...@@ -128,3 +128,21 @@ void init_signals(void (*_sighand)(int)) { ...@@ -128,3 +128,21 @@ void init_signals(void (*_sighand)(int)) {
for (int i = 0; i < NSIG; i++) signal(i, sighand_wrapper); for (int i = 0; i < NSIG; i++) signal(i, sighand_wrapper);
} }
static int get_bit(uint64_t t, int i) {
return (t & (((uint64_t) 1) << i)) > 0;
}
void encode(uint64_t t, char * b) {
for(int i = 0; i < 64; i++)
b[i] = get_bit(t, i) * 0xff;
}
uint64_t decode(char * b) {
uint64_t ret = 0;
for(int i = 0; i < 64; i++) {
int sum_bits = 0;
for(int j = 0; j < 8; j++)
sum_bits += (b[i] & (1 << j)) > 0;
//printf("Sum: %d (%x)\n", sum_bits, b[i]);
ret |= (sum_bits >= 4) * (((uint64_t) 1) << i);
}
return ret;
}
...@@ -54,4 +54,7 @@ int histogram_max(uint64_t *histogram, int max_value); ...@@ -54,4 +54,7 @@ int histogram_max(uint64_t *histogram, int max_value);
extern void (*previous_handlers[NSIG])(int); extern void (*previous_handlers[NSIG])(int);
void encode(uint64_t t, char * b);
uint64_t decode(char * b);
#endif #endif
...@@ -201,7 +201,8 @@ void recv_udp_packet() { ...@@ -201,7 +201,8 @@ void recv_udp_packet() {
} }
} }
strcpy(stats->data, rx_buffer); for(int i = 0; i < MAX_BUFFER_SIZE; i++)
stats->data[i] = rx_buffer[i];
} }
#ifdef WITH_XDP #ifdef WITH_XDP
......
...@@ -134,7 +134,8 @@ void send_udp_packet(char *data, uint64_t txtime) { ...@@ -134,7 +134,8 @@ void send_udp_packet(char *data, uint64_t txtime) {
packets_sent++; packets_sent++;
strcpy(tx_buffer, data); for(int i = 0; i < (int)params->tx_buffer_len; i++)
tx_buffer[i] = data[i];
memset(&sin, 0, sizeof(sin)); memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET; sin.sin_family = AF_INET;
......
...@@ -102,6 +102,7 @@ static void help(char *argv[]) { ...@@ -102,6 +102,7 @@ static void help(char *argv[]) {
static void *emit_signal_thread(void *p) { static void *emit_signal_thread(void *p) {
(void)p; (void)p;
cpu_set_t mask; cpu_set_t mask;
struct timespec current;
// Set thread CPU affinity // Set thread CPU affinity
if (thread_params.affinity_cpu) { if (thread_params.affinity_cpu) {
...@@ -114,8 +115,18 @@ static void *emit_signal_thread(void *p) { ...@@ -114,8 +115,18 @@ static void *emit_signal_thread(void *p) {
pthread_mutex_lock(&emit_signal_mutex); pthread_mutex_lock(&emit_signal_mutex);
for (;;) { for (int i = 0;;i++) {
pthread_cond_wait(&emit_signal_ts_received, &emit_signal_mutex); pthread_cond_wait(&emit_signal_ts_received, &emit_signal_mutex);
clock_gettime(CLOCK_REALTIME, &current);
// Sanity check
if(current.tv_sec > emit_signal_next.tv_sec)
goto invalid_ts;
if(current.tv_sec == emit_signal_next.tv_sec && current.tv_nsec > emit_signal_next.tv_nsec)
goto invalid_ts;
if(current.tv_sec + 15 < emit_signal_next.tv_sec)
goto invalid_ts;
clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &emit_signal_next, NULL); clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &emit_signal_next, NULL);
toggle_gpio(); toggle_gpio();
...@@ -123,6 +134,12 @@ static void *emit_signal_thread(void *p) { ...@@ -123,6 +134,12 @@ static void *emit_signal_thread(void *p) {
pthread_mutex_unlock(&emit_signal_mutex); pthread_mutex_unlock(&emit_signal_mutex);
pthread_exit(NULL); pthread_exit(NULL);
invalid_ts:
fprintf(stderr, "Invalid timestamp : %" PRIu64 "\n", ts_to_uint(emit_signal_next));
fprintf(stderr, "Current time is : %" PRIu64 "\n", ts_to_uint(current));
fprintf(stderr, "Difference with timestamp : %" PRIi64 "\n\n\n", ((int64_t)ts_to_uint(emit_signal_next)) - ((int64_t)ts_to_uint(current)));
exit(EXIT_FAILURE);
} }
/* Real-time thread: /* Real-time thread:
...@@ -167,12 +184,20 @@ static void *tsn_thread(void *p) { ...@@ -167,12 +184,20 @@ static void *tsn_thread(void *p) {
// Emit signal // Emit signal
if (thread_params.emit_signal) { if (thread_params.emit_signal) {
uint64_t emit_signal_t = *((uint64_t *)ingress_stats.data); uint64_t emit_signal_t = decode(ingress_stats.data);
pthread_mutex_lock(&emit_signal_mutex); pthread_mutex_lock(&emit_signal_mutex);
emit_signal_next = uint_to_ts(emit_signal_t); emit_signal_next = uint_to_ts(emit_signal_t);
pthread_cond_signal(&emit_signal_ts_received); pthread_cond_signal(&emit_signal_ts_received);
pthread_mutex_unlock(&emit_signal_mutex); pthread_mutex_unlock(&emit_signal_mutex);
// Sanity check
if(current.tv_sec > emit_signal_next.tv_sec)
goto invalid_ts;
if(current.tv_sec == emit_signal_next.tv_sec && current.tv_nsec > emit_signal_next.tv_nsec)
goto invalid_ts;
if(current.tv_sec + 15 < emit_signal_next.tv_sec)
goto invalid_ts;
} }
// Update stats // Update stats
...@@ -217,6 +242,12 @@ static void *tsn_thread(void *p) { ...@@ -217,6 +242,12 @@ static void *tsn_thread(void *p) {
} }
return NULL; return NULL;
invalid_ts:
fprintf(stderr, "Invalid timestamp at reception : %" PRIu64 "\n", ts_to_uint(emit_signal_next));
fprintf(stderr, "Current time is : %" PRIu64 "\n", ts_to_uint(current));
fprintf(stderr, "Difference with timestamp : %" PRIi64 "\n\n\n", ((int64_t)ts_to_uint(emit_signal_next)) - ((int64_t)ts_to_uint(current)));
exit(EXIT_FAILURE);
} }
static void create_thread(void *(*thread_function)(void *)) { static void create_thread(void *(*thread_function)(void *)) {
......
...@@ -27,19 +27,19 @@ while getopts "hls:m:u:n:" opt; do ...@@ -27,19 +27,19 @@ while getopts "hls:m:u:n:" opt; do
;; ;;
s ) s )
new_offset=$((OPTARG * 1000000000)) new_offset=$((OPTARG * 1000000000))
offset+=$((offset + new_offset)) offset=$((offset + new_offset))
;; ;;
m ) m )
new_offset=$((OPTARG * 1000000)) new_offset=$((OPTARG * 1000000))
offset+=$((offset + new_offset)) offset=$((offset + new_offset))
;; ;;
u ) u )
new_offset=$((OPTARG * 1000)) new_offset=$((OPTARG * 1000))
offset+=$((offset + new_offset)) offset=$((offset + new_offset))
;; ;;
n ) n )
new_offset=${OPTARG} new_offset=${OPTARG}
offset+=$((offset + new_offset)) offset=$((offset + new_offset))
;; ;;
* ) * )
usage usage
......
#!/bin/bash
usage() {
cat << ENDUSAGE
Usage: $0 [-h]
Kills all processes started by test-board-synchro (clients and servers)
-h Show help
ENDUSAGE
1>&2;
exit 1;
}
while getopts "h" opt; do
case "${opt}" in
h )
usage
;;
* )
usage
;;
esac
done
shift $((OPTIND-1))
killall phc2sys;
#!/bin/bash
script_dir=$(dirname $(realpath $0))
usage() {
cat << ENDUSAGE
Usage: $0 [-h] BOARD1_HOSTNAME BOARD2_HOSTNAME
Kills all processes started by test-board-synchro (clients and servers)
-h Show help
BOARD_HOSTNAME Uses /etc/hosts to find the IP address associated to the hostname
ENDUSAGE
1>&2;
exit 1;
}
while getopts "h" opt; do
case "${opt}" in
h )
usage
;;
* )
usage
;;
esac
done
shift $((OPTIND-1))
if [ $# -ne 2 ]; then
usage
fi
board1=$1
board2=$2
killall ptp4l;
$script_dir/sudossh $board1 "killall ptp4l";
$script_dir/sudossh $board2 "killall ptp4l";
#!/bin/bash #!/bin/bash
script_dir=$(dirname $(realpath $0))
usage() { usage() {
cat << ENDUSAGE cat << ENDUSAGE
Usage: $0 [-h] BOARD1_HOSTNAME BOARD2_HOSTNAME Usage: $0 [-h] BOARD1_HOSTNAME BOARD2_HOSTNAME
Kills all processes started by test-board-synchro (clients and servers) Kills all processes started by test-board-synchro (clients, servers, software-pps)
-h Show help -h Show help
BOARD_HOSTNAME Uses /etc/hosts to find the IP address associated to the hostname
ENDUSAGE ENDUSAGE
1>&2; 1>&2;
exit 1; exit 1;
} }
while getopts "hc:i:o:rsT" opt; do while getopts "h" opt; do
case "${opt}" in case "${opt}" in
h ) h )
usage usage
...@@ -24,10 +27,14 @@ done ...@@ -24,10 +27,14 @@ done
shift $((OPTIND-1)) shift $((OPTIND-1))
if [ $# -ne 2 ]; then
usage
fi
board1=$1 board1=$1
board2=$2 board2=$2
killall client; killall client;
killall run-client; killall run-client;
./sudossh $board1 "killall server"; $script_dir/sudossh $board1 "killall server software-pps";
./sudossh $board2 "killall server"; $script_dir/sudossh $board2 "killall server software-pps";
...@@ -3,8 +3,13 @@ ...@@ -3,8 +3,13 @@
import sys import sys
import argparse import argparse
def parseLine(l): def parseLine(l, hex_export=False):
if hex_export:
(ts, ch, *x) = l.split(", ")
ch = int(ch)
ch0, ch1 = ch & 1, (ch & 2) >> 1
else:
(ts, ch0, ch1, *x) = l.split(", ") (ts, ch0, ch1, *x) = l.split(", ")
ts = int(float(ts) * 10**9) ts = int(float(ts) * 10**9)
...@@ -53,12 +58,12 @@ def parsePulses(filename): ...@@ -53,12 +58,12 @@ def parsePulses(filename):
print("IO error") print("IO error")
exit() exit()
def parseToggles(filename): def parseToggles(filename, hex_import=False):
with open(filename, "r") as f: with open(filename, "r") as f:
lines = [l for l in f] lines = [l for l in f]
lines = lines[3:] lines = lines[3:]
values = [ parseLine(l) for l in lines] values = [ parseLine(l, hex_import) for l in lines]
values_c1 = [] values_c1 = []
values_c2 = [] values_c2 = []
...@@ -128,12 +133,15 @@ def parse_args(): ...@@ -128,12 +133,15 @@ def parse_args():
parser = argparse.ArgumentParser(description='Measure analysis') parser = argparse.ArgumentParser(description='Measure analysis')
parser.add_argument('-t', nargs=1, required=False, help='import shuttle-a20 json measures') parser.add_argument('-t', nargs=1, required=False, help='import shuttle-a20 json measures')
parser.add_argument('-p', nargs=1, required=False, help='import shuttle json measures') parser.add_argument('-p', nargs=1, required=False, help='import shuttle json measures')
parser.add_argument('-H', action='store_true', required=False, help='Import measures exported with hex values')
args = parser.parse_args() args = parser.parse_args()
hex_export=bool(args.H)
if args.t is not None: if args.t is not None:
filename = args.t[0] filename = args.t[0]
(deltas, periods_c1, periods_c2) = parseToggles(filename) (deltas, periods_c1, periods_c2) = parseToggles(filename, hex_export)
print("# Shuttle-A20 synchronization signal jitter histogram") print("# Shuttle-A20 synchronization signal jitter histogram")
generateHistogram(deltas) generateHistogram(deltas)
generateHistogram(periods_c1 + periods_c2) generateHistogram(periods_c1 + periods_c2)
......
#!/bin/bash
script_dir=$(dirname $(realpath $0))
usage () {
cat << ENDUSAGE
Usage: $0 [-h] [-i USEC -g -s -t MSEC] BOARD1_HOSTNAME BOARD2_HOSTNAME
-h Show help
-i USEC Signal period
-g Use GPIO instead of serial
-t MSEC Set the start timestamp offset
BOARD_HOSTNAME Uses /etc/hosts to find the IP address associated to the hostname
ENDUSAGE
1>&2;
exit 1;
}
interval=1000
pp_opts=""
ts_offset=6000
while getopts "hi:gt:" opt; do
case "$opt" in
h)
usage
;;
i)
interval=${OPTARG}
;;
g)
pps_opts+=" -g "
;;
t )
ts_offset=${OPTARG}
;;
*)
usage
;;
esac
done
shift $((OPTIND-1))
if [ $# -ne 2 ]; then
usage
fi
board1=$1
board2=$2
ts=$($script_dir/get-ptp-time -m $ts_offset)
echo "Timestamp: $ts";
ssh $board1 "cd tsn-measures/software-pps/build;make";
$script_dir/exec-ssh-nohup $board1 "tsn-measures/software-pps/build/software-pps -a1 -p97 -i $interval $pps_opts -s $ts";
ssh $board2 "cd tsn-measures/software-pps/build;make";
$script_dir/exec-ssh-nohup $board2 "tsn-measures/software-pps/build/software-pps -a1 -p97 -i $interval $pps_opts -s $ts";
...@@ -6,17 +6,21 @@ usage() { ...@@ -6,17 +6,21 @@ usage() {
cat << ENDUSAGE cat << ENDUSAGE
Usage: $0 [-h] Usage: $0 [-h]
-h Show help -h Show help
-s use CLOCK_REALTIME as slave (might not work well)
ENDUSAGE ENDUSAGE
1>&2; 1>&2;
exit 1; exit 1;
} }
while getopts "h" opt; do while getopts "hs" opt; do
case "${opt}" in case "${opt}" in
h ) h )
usage usage
;; ;;
s )
rt_slave=1
;;
* ) * )
usage usage
;; ;;
...@@ -27,5 +31,14 @@ shift $((OPTIND-1)) ...@@ -27,5 +31,14 @@ shift $((OPTIND-1))
# Run phc2sys # Run phc2sys
chrt -f 95 phc2sys -m -c /dev/ptp0 -s CLOCK_REALTIME -w &> ~/phc2sys_sys_log &
chrt -f 95 phc2sys -m -c /dev/ptp1 -s CLOCK_REALTIME -w &> ~/phc2sys_sys_log & pmc -u -b 0 -i enp1s0 "SET GRANDMASTER_SETTINGS_NP clockClass 248 clockAccuracy 0xfe offsetScaledLogVariance 0xffff currentUtcOffset 37 leap61 0 leap59 0 currentUtcOffsetValid 1 ptpTimescale 1 timeTraceable 1 frequencyTraceable 0 timeSource 0xa0"
pmc -u -b 0 -i enp2s0 "SET GRANDMASTER_SETTINGS_NP clockClass 248 clockAccuracy 0xfe offsetScaledLogVariance 0xffff currentUtcOffset 37 leap61 0 leap59 0 currentUtcOffsetValid 1 ptpTimescale 1 timeTraceable 1 frequencyTraceable 0 timeSource 0xa0"
if [ -n "$rt_slave" ]; then
chrt -f 95 phc2sys -m -c /dev/ptp0 -s /dev/ptp1 --step_threshold=1 -w &> ~/phc2sys_ptps_log &
chrt -f 95 phc2sys -m -s /dev/ptp0 -c CLOCK_REALTIME --step_threshold=1 -w &> ~/phc2sys_rt_log &
else
chrt -f 95 phc2sys -m -c /dev/ptp0 -s CLOCK_REALTIME --step_threshold=1 -w &> ~/phc2sys_ptp0_log &
chrt -f 95 phc2sys -m -c /dev/ptp1 -s CLOCK_REALTIME --step_threshold=1 -w &> ~/phc2sys_ptp1_log &
fi
...@@ -17,9 +17,9 @@ ENDUSAGE ...@@ -17,9 +17,9 @@ ENDUSAGE
exit 1; exit 1;
} }
interval=500000 interval=1000
server_opts="" server_opts=""
ts_offset=6000 ts_offset=2000
while getopts "hc:i:o:rt:T" opt; do while getopts "hc:i:o:rt:T" opt; do
case "${opt}" in case "${opt}" in
...@@ -57,8 +57,8 @@ if [ -n "$enable_tracing" ]; then ...@@ -57,8 +57,8 @@ if [ -n "$enable_tracing" ]; then
server_opts+=" -T $((interval*2)) -E \"-e irq -e sched -e net -e napi -e gpio\" -B 100000" server_opts+=" -T $((interval*2)) -E \"-e irq -e sched -e net -e napi -e gpio\" -B 100000"
fi fi
./sudossh $board1 "killall server"; $script_dir/sudossh $board1 "killall server";
./sudossh $board2 "killall server"; $script_dir/sudossh $board2 "killall server";
killall client; killall client;
killall run-client; killall run-client;
......
...@@ -4,6 +4,7 @@ SRCDIR = ../src ...@@ -4,6 +4,7 @@ SRCDIR = ../src
SRCS = software-pps.c SRCS = software-pps.c
SRCS += common.c SRCS += common.c
SRCS += pulse.c SRCS += pulse.c
SRCS += gpio.c
OBJS = $(SRCS:%.c=%.o) OBJS = $(SRCS:%.c=%.o)
......
#include "gpio.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
static int gpio_state;
static char cmd[128];
static char * one = "1";
static char * zero = "0";
static int fd;
void enable_gpio(int gpio_index) {
sprintf(cmd, "echo %d > /sys/class/gpio/export", gpio_index);
system(cmd);
sprintf(cmd, "echo out > /sys/class/gpio/gpio%d/direction", gpio_index);
system(cmd);
sprintf(cmd, "/sys/class/gpio/gpio%d/value", gpio_index);
fd = open(cmd, O_WRONLY);
}
void toggle_gpio() {
if(gpio_state)
write(fd, one, 1);
else
write(fd, zero, 1);
gpio_state = !gpio_state;
}
#ifndef GPIO_H
#define GPIO_H
void enable_gpio(int gpio_index);
void toggle_gpio();
#endif
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include <unistd.h> #include <unistd.h>
#include "pulse.h" #include "pulse.h"
#include "gpio.h"
#include "common.h" #include "common.h"
// Structs // Structs
...@@ -48,6 +49,7 @@ static uint64_t nb_cycles; ...@@ -48,6 +49,7 @@ static uint64_t nb_cycles;
static main_param_t main_params; static main_param_t main_params;
static thread_param_t thread_params; static thread_param_t thread_params;
static int use_gpio;
static void help(char *argv[]) { static void help(char *argv[]) {
printf( printf(
...@@ -59,6 +61,7 @@ static void help(char *argv[]) { ...@@ -59,6 +61,7 @@ static void help(char *argv[]) {
" -r USEC non-RT main thread refresh interval\n" " -r USEC non-RT main thread refresh interval\n"
" -l N_CYCLES RT thread cycles amount (default: infinite)\n" " -l N_CYCLES RT thread cycles amount (default: infinite)\n"
" -s NS Common start time reference\n" " -s NS Common start time reference\n"
" -g Use GPIO instead of serial\n"
" -v Verbose\n" " -v Verbose\n"
"\n", "\n",
argv[0]); argv[0]);
...@@ -103,7 +106,11 @@ static void *pps_thread(void *p) { ...@@ -103,7 +106,11 @@ static void *pps_thread(void *p) {
break; break;
if(use_gpio)
toggle_gpio();
else
send_pulse(); send_pulse();
add_ns(&next, thread_params.interval); add_ns(&next, thread_params.interval);
ret = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &next, NULL); ret = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &next, NULL);
...@@ -134,6 +141,7 @@ int main(int argc, char *argv[]) { ...@@ -134,6 +141,7 @@ int main(int argc, char *argv[]) {
thread_params.start_ts = 0; thread_params.start_ts = 0;
main_params.refresh_rate = 50000; main_params.refresh_rate = 50000;
main_params.verbose = 0; main_params.verbose = 0;
use_gpio = 0;
// Lock all current and future pages from preventing of being paged to // Lock all current and future pages from preventing of being paged to
// swap // swap
...@@ -145,6 +153,9 @@ int main(int argc, char *argv[]) { ...@@ -145,6 +153,9 @@ int main(int argc, char *argv[]) {
// Process bash options // Process bash options
process_options(argc, argv); process_options(argc, argv);
if(use_gpio)
enable_gpio(86);
else
enable_pulse(); enable_pulse();
// Initialize pthread attributes (default values) // Initialize pthread attributes (default values)
...@@ -198,7 +209,7 @@ int main(int argc, char *argv[]) { ...@@ -198,7 +209,7 @@ int main(int argc, char *argv[]) {
*/ */
static void process_options(int argc, char *argv[]) { static void process_options(int argc, char *argv[]) {
for (;;) { for (;;) {
int c = getopt(argc, argv, "a:hi:l:p:r:s:v"); int c = getopt(argc, argv, "a:hgi:l:p:r:s:v");
if (c == -1) break; if (c == -1) break;
...@@ -210,6 +221,9 @@ static void process_options(int argc, char *argv[]) { ...@@ -210,6 +221,9 @@ static void process_options(int argc, char *argv[]) {
help(argv); help(argv);
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
break; break;
case 'g':
use_gpio = 1;
break;
case 'i': case 'i':
thread_params.interval = strtoull(optarg, NULL, 10) * 1000; thread_params.interval = strtoull(optarg, NULL, 10) * 1000;
break; break;
......
SERVER_PROG = server
CLIENT_PROG = client
SRCDIR = ../src
SERVER_SRCS = server.c
SERVER_SRCS += recv_packet.c
SERVER_SRCS += send_packet.c
SERVER_SRCS += common.c
SERVER_SRCS += tracer.c
SERVER_SRCS += gpio.c
CLIENT_SRCS = client.c
CLIENT_SRCS += recv_packet.c
CLIENT_SRCS += send_packet.c
CLIENT_SRCS += common.c
CLIENT_SRCS += tracer.c
SERVER_OBJS = $(SERVER_SRCS:%.c=%.o)
CLIENT_OBJS = $(CLIENT_SRCS:%.c=%.o)
ifeq ($(DEBUG),)
CFLAGS = -O2
else
CFLAGS = -Og -g -Wall -Wextra
endif
CFLAGS += -MD -MP
CFLAGS += -I $(SRCDIR)
CFLAGS += -std=gnu99
LLIBS = -pthread
ifneq ($(WITH_XDP),)
CFLAGS += -D WITH_XDP
LDFLAGS += -L/usr/lib -lbpf
else ifneq ($(WITH_GIT_XDP),)
IFLAGS += -I ${HOME}/libbpf/include
CFLAGS += -D WITH_XDP $(IFLAGS)
LDIRS += -L${HOME}/libbpf/src
LLIBS += -lelf -lz -l:libbpf.a
endif
vpath %.c $(SRCDIR)
xdp_kern.o: xdp_kern.c
clang $(IFLAGS) -isystem /usr/include/arm-linux-gnueabihf -S -target bpf -D __BPF_TRACING__ -Wall -O2 -emit-llvm -c -g -o xdp_kern.ll $^
llc -march=bpf -filetype=obj -o $@ xdp_kern.ll
ifneq ($(WITH_GIT_XDP),)
$(SERVER_PROG): $(SERVER_OBJS) xdp_kern.o
$(CC) $(LDFLAGS) $(LDIRS) $(SERVER_OBJS) $(LLIBS) -o $@
else
$(SERVER_PROG): $(SERVER_OBJS)
$(CC) $(LDFLAGS) $(LDIRS) $^ $(LLIBS) -o $@
endif
$(CLIENT_PROG): $(CLIENT_OBJS)
$(CC) $(LDFLAGS) $(LDIRS) $^ $(LLIBS) -o $@
-include $(subst .c,.d,$(SERVER_SRCS))
-include $(subst .c,.d,$(CLIENT_SRCS))
clean:
$(RM) $(SERVER_OBJS) $(SERVER_PROG) $(subst .c,.d,$(SERVER_SRCS))
$(RM) $(CLIENT_OBJS) $(CLIENT_PROG) $(subst .c,.d,$(CLIENT_SRCS))
.PHONY: clean
/* SPDX-License-Identifier: GPL-2.0 */
/* Copied from $(LINUX)/tools/testing/selftests/bpf/bpf_helpers.h */
/* Added to fix compilation on old Ubuntu systems - please preserve when
updating file! */
#ifndef __always_inline
# define __always_inline inline __attribute__((always_inline))
#endif
#ifndef __BPF_HELPERS_H
#define __BPF_HELPERS_H
/* helper macro to place programs, maps, license in
* different sections in elf_bpf file. Section names
* are interpreted by elf_bpf loader
*/
#define SEC(NAME) __attribute__((section(NAME), used))
/* helper functions called from eBPF programs written in C */
static void *(*bpf_map_lookup_elem)(void *map, void *key) =
(void *) BPF_FUNC_map_lookup_elem;
static int (*bpf_map_update_elem)(void *map, void *key, void *value,
unsigned long long flags) =
(void *) BPF_FUNC_map_update_elem;
static int (*bpf_map_delete_elem)(void *map, void *key) =
(void *) BPF_FUNC_map_delete_elem;
static int (*bpf_map_push_elem)(void *map, void *value,
unsigned long long flags) =
(void *) BPF_FUNC_map_push_elem;
static int (*bpf_map_pop_elem)(void *map, void *value) =
(void *) BPF_FUNC_map_pop_elem;
static int (*bpf_map_peek_elem)(void *map, void *value) =
(void *) BPF_FUNC_map_peek_elem;
static int (*bpf_probe_read)(void *dst, int size, void *unsafe_ptr) =
(void *) BPF_FUNC_probe_read;
static unsigned long long (*bpf_ktime_get_ns)(void) =
(void *) BPF_FUNC_ktime_get_ns;
static int (*bpf_trace_printk)(const char *fmt, int fmt_size, ...) =
(void *) BPF_FUNC_trace_printk;
static void (*bpf_tail_call)(void *ctx, void *map, int index) =
(void *) BPF_FUNC_tail_call;
static unsigned long long (*bpf_get_smp_processor_id)(void) =
(void *) BPF_FUNC_get_smp_processor_id;
static unsigned long long (*bpf_get_current_pid_tgid)(void) =
(void *) BPF_FUNC_get_current_pid_tgid;
static unsigned long long (*bpf_get_current_uid_gid)(void) =
(void *) BPF_FUNC_get_current_uid_gid;
static int (*bpf_get_current_comm)(void *buf, int buf_size) =
(void *) BPF_FUNC_get_current_comm;
static unsigned long long (*bpf_perf_event_read)(void *map,
unsigned long long flags) =
(void *) BPF_FUNC_perf_event_read;
static int (*bpf_clone_redirect)(void *ctx, int ifindex, int flags) =
(void *) BPF_FUNC_clone_redirect;
static int (*bpf_redirect)(int ifindex, int flags) =
(void *) BPF_FUNC_redirect;
static int (*bpf_redirect_map)(void *map, int key, int flags) =
(void *) BPF_FUNC_redirect_map;
static int (*bpf_perf_event_output)(void *ctx, void *map,
unsigned long long flags, void *data,
int size) =
(void *) BPF_FUNC_perf_event_output;
static int (*bpf_get_stackid)(void *ctx, void *map, int flags) =
(void *) BPF_FUNC_get_stackid;
static int (*bpf_probe_write_user)(void *dst, void *src, int size) =
(void *) BPF_FUNC_probe_write_user;
static int (*bpf_current_task_under_cgroup)(void *map, int index) =
(void *) BPF_FUNC_current_task_under_cgroup;
static int (*bpf_skb_get_tunnel_key)(void *ctx, void *key, int size, int flags) =
(void *) BPF_FUNC_skb_get_tunnel_key;
static int (*bpf_skb_set_tunnel_key)(void *ctx, void *key, int size, int flags) =
(void *) BPF_FUNC_skb_set_tunnel_key;
static int (*bpf_skb_get_tunnel_opt)(void *ctx, void *md, int size) =
(void *) BPF_FUNC_skb_get_tunnel_opt;
static int (*bpf_skb_set_tunnel_opt)(void *ctx, void *md, int size) =
(void *) BPF_FUNC_skb_set_tunnel_opt;
static unsigned long long (*bpf_get_prandom_u32)(void) =
(void *) BPF_FUNC_get_prandom_u32;
static int (*bpf_xdp_adjust_head)(void *ctx, int offset) =
(void *) BPF_FUNC_xdp_adjust_head;
static int (*bpf_xdp_adjust_meta)(void *ctx, int offset) =
(void *) BPF_FUNC_xdp_adjust_meta;
static int (*bpf_get_socket_cookie)(void *ctx) =
(void *) BPF_FUNC_get_socket_cookie;
static int (*bpf_setsockopt)(void *ctx, int level, int optname, void *optval,
int optlen) =
(void *) BPF_FUNC_setsockopt;
static int (*bpf_getsockopt)(void *ctx, int level, int optname, void *optval,
int optlen) =
(void *) BPF_FUNC_getsockopt;
static int (*bpf_sock_ops_cb_flags_set)(void *ctx, int flags) =
(void *) BPF_FUNC_sock_ops_cb_flags_set;
static int (*bpf_sk_redirect_map)(void *ctx, void *map, int key, int flags) =
(void *) BPF_FUNC_sk_redirect_map;
static int (*bpf_sk_redirect_hash)(void *ctx, void *map, void *key, int flags) =
(void *) BPF_FUNC_sk_redirect_hash;
static int (*bpf_sock_map_update)(void *map, void *key, void *value,
unsigned long long flags) =
(void *) BPF_FUNC_sock_map_update;
static int (*bpf_sock_hash_update)(void *map, void *key, void *value,
unsigned long long flags) =
(void *) BPF_FUNC_sock_hash_update;
static int (*bpf_perf_event_read_value)(void *map, unsigned long long flags,
void *buf, unsigned int buf_size) =
(void *) BPF_FUNC_perf_event_read_value;
static int (*bpf_perf_prog_read_value)(void *ctx, void *buf,
unsigned int buf_size) =
(void *) BPF_FUNC_perf_prog_read_value;
static int (*bpf_override_return)(void *ctx, unsigned long rc) =
(void *) BPF_FUNC_override_return;
static int (*bpf_msg_redirect_map)(void *ctx, void *map, int key, int flags) =
(void *) BPF_FUNC_msg_redirect_map;
static int (*bpf_msg_redirect_hash)(void *ctx,
void *map, void *key, int flags) =
(void *) BPF_FUNC_msg_redirect_hash;
static int (*bpf_msg_apply_bytes)(void *ctx, int len) =
(void *) BPF_FUNC_msg_apply_bytes;
static int (*bpf_msg_cork_bytes)(void *ctx, int len) =
(void *) BPF_FUNC_msg_cork_bytes;
static int (*bpf_msg_pull_data)(void *ctx, int start, int end, int flags) =
(void *) BPF_FUNC_msg_pull_data;
static int (*bpf_msg_push_data)(void *ctx, int start, int end, int flags) =
(void *) BPF_FUNC_msg_push_data;
static int (*bpf_msg_pop_data)(void *ctx, int start, int cut, int flags) =
(void *) BPF_FUNC_msg_pop_data;
static int (*bpf_bind)(void *ctx, void *addr, int addr_len) =
(void *) BPF_FUNC_bind;
static int (*bpf_xdp_adjust_tail)(void *ctx, int offset) =
(void *) BPF_FUNC_xdp_adjust_tail;
static int (*bpf_skb_get_xfrm_state)(void *ctx, int index, void *state,
int size, int flags) =
(void *) BPF_FUNC_skb_get_xfrm_state;
static int (*bpf_sk_select_reuseport)(void *ctx, void *map, void *key, __u32 flags) =
(void *) BPF_FUNC_sk_select_reuseport;
static int (*bpf_get_stack)(void *ctx, void *buf, int size, int flags) =
(void *) BPF_FUNC_get_stack;
static int (*bpf_fib_lookup)(void *ctx, struct bpf_fib_lookup *params,
int plen, __u32 flags) =
(void *) BPF_FUNC_fib_lookup;
static int (*bpf_lwt_push_encap)(void *ctx, unsigned int type, void *hdr,
unsigned int len) =
(void *) BPF_FUNC_lwt_push_encap;
static int (*bpf_lwt_seg6_store_bytes)(void *ctx, unsigned int offset,
void *from, unsigned int len) =
(void *) BPF_FUNC_lwt_seg6_store_bytes;
static int (*bpf_lwt_seg6_action)(void *ctx, unsigned int action, void *param,
unsigned int param_len) =
(void *) BPF_FUNC_lwt_seg6_action;
static int (*bpf_lwt_seg6_adjust_srh)(void *ctx, unsigned int offset,
unsigned int len) =
(void *) BPF_FUNC_lwt_seg6_adjust_srh;
static int (*bpf_rc_repeat)(void *ctx) =
(void *) BPF_FUNC_rc_repeat;
static int (*bpf_rc_keydown)(void *ctx, unsigned int protocol,
unsigned long long scancode, unsigned int toggle) =
(void *) BPF_FUNC_rc_keydown;
static unsigned long long (*bpf_get_current_cgroup_id)(void) =
(void *) BPF_FUNC_get_current_cgroup_id;
static void *(*bpf_get_local_storage)(void *map, unsigned long long flags) =
(void *) BPF_FUNC_get_local_storage;
static unsigned long long (*bpf_skb_cgroup_id)(void *ctx) =
(void *) BPF_FUNC_skb_cgroup_id;
static unsigned long long (*bpf_skb_ancestor_cgroup_id)(void *ctx, int level) =
(void *) BPF_FUNC_skb_ancestor_cgroup_id;
static struct bpf_sock *(*bpf_sk_lookup_tcp)(void *ctx,
struct bpf_sock_tuple *tuple,
int size, unsigned long long netns_id,
unsigned long long flags) =
(void *) BPF_FUNC_sk_lookup_tcp;
static struct bpf_sock *(*bpf_sk_lookup_udp)(void *ctx,
struct bpf_sock_tuple *tuple,
int size, unsigned long long netns_id,
unsigned long long flags) =
(void *) BPF_FUNC_sk_lookup_udp;
static int (*bpf_sk_release)(struct bpf_sock *sk) =
(void *) BPF_FUNC_sk_release;
static int (*bpf_skb_vlan_push)(void *ctx, __be16 vlan_proto, __u16 vlan_tci) =
(void *) BPF_FUNC_skb_vlan_push;
static int (*bpf_skb_vlan_pop)(void *ctx) =
(void *) BPF_FUNC_skb_vlan_pop;
static int (*bpf_rc_pointer_rel)(void *ctx, int rel_x, int rel_y) =
(void *) BPF_FUNC_rc_pointer_rel;
static void (*bpf_spin_lock)(struct bpf_spin_lock *lock) =
(void *) BPF_FUNC_spin_lock;
static void (*bpf_spin_unlock)(struct bpf_spin_lock *lock) =
(void *) BPF_FUNC_spin_unlock;
static struct bpf_sock *(*bpf_sk_fullsock)(struct bpf_sock *sk) =
(void *) BPF_FUNC_sk_fullsock;
static struct bpf_tcp_sock *(*bpf_tcp_sock)(struct bpf_sock *sk) =
(void *) BPF_FUNC_tcp_sock;
static struct bpf_sock *(*bpf_get_listener_sock)(struct bpf_sock *sk) =
(void *) BPF_FUNC_get_listener_sock;
static int (*bpf_skb_ecn_set_ce)(void *ctx) =
(void *) BPF_FUNC_skb_ecn_set_ce;
/* llvm builtin functions that eBPF C program may use to
* emit BPF_LD_ABS and BPF_LD_IND instructions
*/
struct sk_buff;
unsigned long long load_byte(void *skb,
unsigned long long off) asm("llvm.bpf.load.byte");
unsigned long long load_half(void *skb,
unsigned long long off) asm("llvm.bpf.load.half");
unsigned long long load_word(void *skb,
unsigned long long off) asm("llvm.bpf.load.word");
/* a helper structure used by eBPF C program
* to describe map attributes to elf_bpf loader
*/
struct bpf_map_def {
unsigned int type;
unsigned int key_size;
unsigned int value_size;
unsigned int max_entries;
unsigned int map_flags;
unsigned int inner_map_idx;
unsigned int numa_node;
};
#define BPF_ANNOTATE_KV_PAIR(name, type_key, type_val) \
struct ____btf_map_##name { \
type_key key; \
type_val value; \
}; \
struct ____btf_map_##name \
__attribute__ ((section(".maps." #name), used)) \
____btf_map_##name = { }
static int (*bpf_skb_load_bytes)(void *ctx, int off, void *to, int len) =
(void *) BPF_FUNC_skb_load_bytes;
static int (*bpf_skb_load_bytes_relative)(void *ctx, int off, void *to, int len, __u32 start_header) =
(void *) BPF_FUNC_skb_load_bytes_relative;
static int (*bpf_skb_store_bytes)(void *ctx, int off, void *from, int len, int flags) =
(void *) BPF_FUNC_skb_store_bytes;
static int (*bpf_l3_csum_replace)(void *ctx, int off, int from, int to, int flags) =
(void *) BPF_FUNC_l3_csum_replace;
static int (*bpf_l4_csum_replace)(void *ctx, int off, int from, int to, int flags) =
(void *) BPF_FUNC_l4_csum_replace;
static int (*bpf_csum_diff)(void *from, int from_size, void *to, int to_size, int seed) =
(void *) BPF_FUNC_csum_diff;
static int (*bpf_skb_under_cgroup)(void *ctx, void *map, int index) =
(void *) BPF_FUNC_skb_under_cgroup;
static int (*bpf_skb_change_head)(void *, int len, int flags) =
(void *) BPF_FUNC_skb_change_head;
static int (*bpf_skb_pull_data)(void *, int len) =
(void *) BPF_FUNC_skb_pull_data;
static unsigned int (*bpf_get_cgroup_classid)(void *ctx) =
(void *) BPF_FUNC_get_cgroup_classid;
static unsigned int (*bpf_get_route_realm)(void *ctx) =
(void *) BPF_FUNC_get_route_realm;
static int (*bpf_skb_change_proto)(void *ctx, __be16 proto, __u64 flags) =
(void *) BPF_FUNC_skb_change_proto;
static int (*bpf_skb_change_type)(void *ctx, __u32 type) =
(void *) BPF_FUNC_skb_change_type;
static unsigned int (*bpf_get_hash_recalc)(void *ctx) =
(void *) BPF_FUNC_get_hash_recalc;
static unsigned long long (*bpf_get_current_task)(void *ctx) =
(void *) BPF_FUNC_get_current_task;
static int (*bpf_skb_change_tail)(void *ctx, __u32 len, __u64 flags) =
(void *) BPF_FUNC_skb_change_tail;
static long long (*bpf_csum_update)(void *ctx, __u32 csum) =
(void *) BPF_FUNC_csum_update;
static void (*bpf_set_hash_invalid)(void *ctx) =
(void *) BPF_FUNC_set_hash_invalid;
static int (*bpf_get_numa_node_id)(void) =
(void *) BPF_FUNC_get_numa_node_id;
static int (*bpf_probe_read_str)(void *ctx, __u32 size,
const void *unsafe_ptr) =
(void *) BPF_FUNC_probe_read_str;
static unsigned int (*bpf_get_socket_uid)(void *ctx) =
(void *) BPF_FUNC_get_socket_uid;
static unsigned int (*bpf_set_hash)(void *ctx, __u32 hash) =
(void *) BPF_FUNC_set_hash;
static int (*bpf_skb_adjust_room)(void *ctx, __s32 len_diff, __u32 mode,
unsigned long long flags) =
(void *) BPF_FUNC_skb_adjust_room;
/* Scan the ARCH passed in from ARCH env variable (see Makefile) */
#if defined(__TARGET_ARCH_x86)
#define bpf_target_x86
#define bpf_target_defined
#elif defined(__TARGET_ARCH_s930x)
#define bpf_target_s930x
#define bpf_target_defined
#elif defined(__TARGET_ARCH_arm64)
#define bpf_target_arm64
#define bpf_target_defined
#elif defined(__TARGET_ARCH_mips)
#define bpf_target_mips
#define bpf_target_defined
#elif defined(__TARGET_ARCH_powerpc)
#define bpf_target_powerpc
#define bpf_target_defined
#elif defined(__TARGET_ARCH_sparc)
#define bpf_target_sparc
#define bpf_target_defined
#else
#undef bpf_target_defined
#endif
/* Fall back to what the compiler says */
#ifndef bpf_target_defined
#if defined(__x86_64__)
#define bpf_target_x86
#elif defined(__s390x__)
#define bpf_target_s930x
#elif defined(__aarch64__)
#define bpf_target_arm64
#elif defined(__mips__)
#define bpf_target_mips
#elif defined(__powerpc__)
#define bpf_target_powerpc
#elif defined(__sparc__)
#define bpf_target_sparc
#endif
#endif
#if defined(bpf_target_x86)
#define PT_REGS_PARM1(x) ((x)->di)
#define PT_REGS_PARM2(x) ((x)->si)
#define PT_REGS_PARM3(x) ((x)->dx)
#define PT_REGS_PARM4(x) ((x)->cx)
#define PT_REGS_PARM5(x) ((x)->r8)
#define PT_REGS_RET(x) ((x)->sp)
#define PT_REGS_FP(x) ((x)->bp)
#define PT_REGS_RC(x) ((x)->ax)
#define PT_REGS_SP(x) ((x)->sp)
#define PT_REGS_IP(x) ((x)->ip)
#elif defined(bpf_target_s390x)
#define PT_REGS_PARM1(x) ((x)->gprs[2])
#define PT_REGS_PARM2(x) ((x)->gprs[3])
#define PT_REGS_PARM3(x) ((x)->gprs[4])
#define PT_REGS_PARM4(x) ((x)->gprs[5])
#define PT_REGS_PARM5(x) ((x)->gprs[6])
#define PT_REGS_RET(x) ((x)->gprs[14])
#define PT_REGS_FP(x) ((x)->gprs[11]) /* Works only with CONFIG_FRAME_POINTER */
#define PT_REGS_RC(x) ((x)->gprs[2])
#define PT_REGS_SP(x) ((x)->gprs[15])
#define PT_REGS_IP(x) ((x)->psw.addr)
#elif defined(bpf_target_arm64)
#define PT_REGS_PARM1(x) ((x)->regs[0])
#define PT_REGS_PARM2(x) ((x)->regs[1])
#define PT_REGS_PARM3(x) ((x)->regs[2])
#define PT_REGS_PARM4(x) ((x)->regs[3])
#define PT_REGS_PARM5(x) ((x)->regs[4])
#define PT_REGS_RET(x) ((x)->regs[30])
#define PT_REGS_FP(x) ((x)->regs[29]) /* Works only with CONFIG_FRAME_POINTER */
#define PT_REGS_RC(x) ((x)->regs[0])
#define PT_REGS_SP(x) ((x)->sp)
#define PT_REGS_IP(x) ((x)->pc)
#elif defined(bpf_target_mips)
#define PT_REGS_PARM1(x) ((x)->regs[4])
#define PT_REGS_PARM2(x) ((x)->regs[5])
#define PT_REGS_PARM3(x) ((x)->regs[6])
#define PT_REGS_PARM4(x) ((x)->regs[7])
#define PT_REGS_PARM5(x) ((x)->regs[8])
#define PT_REGS_RET(x) ((x)->regs[31])
#define PT_REGS_FP(x) ((x)->regs[30]) /* Works only with CONFIG_FRAME_POINTER */
#define PT_REGS_RC(x) ((x)->regs[1])
#define PT_REGS_SP(x) ((x)->regs[29])
#define PT_REGS_IP(x) ((x)->cp0_epc)
#elif defined(bpf_target_powerpc)
#define PT_REGS_PARM1(x) ((x)->gpr[3])
#define PT_REGS_PARM2(x) ((x)->gpr[4])
#define PT_REGS_PARM3(x) ((x)->gpr[5])
#define PT_REGS_PARM4(x) ((x)->gpr[6])
#define PT_REGS_PARM5(x) ((x)->gpr[7])
#define PT_REGS_RC(x) ((x)->gpr[3])
#define PT_REGS_SP(x) ((x)->sp)
#define PT_REGS_IP(x) ((x)->nip)
#elif defined(bpf_target_sparc)
#define PT_REGS_PARM1(x) ((x)->u_regs[UREG_I0])
#define PT_REGS_PARM2(x) ((x)->u_regs[UREG_I1])
#define PT_REGS_PARM3(x) ((x)->u_regs[UREG_I2])
#define PT_REGS_PARM4(x) ((x)->u_regs[UREG_I3])
#define PT_REGS_PARM5(x) ((x)->u_regs[UREG_I4])
#define PT_REGS_RET(x) ((x)->u_regs[UREG_I7])
#define PT_REGS_RC(x) ((x)->u_regs[UREG_I0])
#define PT_REGS_SP(x) ((x)->u_regs[UREG_FP])
/* Should this also be a bpf_target check for the sparc case? */
#if defined(__arch64__)
#define PT_REGS_IP(x) ((x)->tpc)
#else
#define PT_REGS_IP(x) ((x)->pc)
#endif
#endif
#ifdef bpf_target_powerpc
#define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ (ip) = (ctx)->link; })
#define BPF_KRETPROBE_READ_RET_IP BPF_KPROBE_READ_RET_IP
#elif bpf_target_sparc
#define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ (ip) = PT_REGS_RET(ctx); })
#define BPF_KRETPROBE_READ_RET_IP BPF_KPROBE_READ_RET_IP
#else
#define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ \
bpf_probe_read(&(ip), sizeof(ip), (void *)PT_REGS_RET(ctx)); })
#define BPF_KRETPROBE_READ_RET_IP(ip, ctx) ({ \
bpf_probe_read(&(ip), sizeof(ip), \
(void *)(PT_REGS_FP(ctx) + sizeof(ip))); })
#endif
#endif
/*
* Real time packet sending client
*
* Large portions taken from cyclictest
*
*/
#define _GNU_SOURCE
#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <limits.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "common.h"
#include "recv_packet.h"
#include "send_packet.h"
#include "tracer.h"
// Structs
enum TSNTask { SEND_PACKET_TASK, RTT_TASK };
typedef struct rtt_stat {
int min_rtt;
int avg_rtt;
int max_rtt;
} rtt_stat_t;
typedef struct thread_param {
uint64_t interval;
unsigned int max_cycles;
int priority;
int etf_offset;
uint64_t send_tx_delay;
int affinity_cpu;
uint64_t start_ts;
int enable_etf_tracing;
int enable_threshold_tracing;
int threshold;
} thread_param_t;
typedef struct main_param {
int refresh_rate;
int verbose;
int enable_tracing;
int enable_graph;
} main_param_t;
// Static functions
static void process_options(int argc, char *argv[]);
static void do_tsn_task(char *data, uint64_t next_txtime);
static void print_histograms();
static void sighand(int sig_num);
// Static variables
static uint64_t kernel_latency_hist[MAX_KERNEL_LATENCY];
static uint64_t rtt_hist[MAX_RTT];
static uint64_t nb_cycles;
static main_param_t main_params;
static thread_param_t thread_params;
static egress_param_t egress_params;
static ingress_param_t ingress_params;
static rtt_stat_t rtt_stats = {.min_rtt = INT_MAX};
static egress_stat_t egress_stats = {.min_kernel_latency = INT_MAX,
.min_interval = INT_MAX};
static ingress_stat_t ingress_stats = {.min_kernel_latency = INT_MAX,
.min_interval = INT_MAX};
static int enable_histograms;
static int enable_etf;
static int enable_timestamps;
static enum TSNTask tsn_task;
static struct timespec measures_start;
static struct timespec measures_end;
static char send_data[MAX_BUFFER_SIZE];
static int log_timestamps = 0;
static void help(char *argv[]) {
printf(
"Usage: %s [-a CPU -p PRIO -i USEC -r USEC -l N] [-d BUF_LEN | -c "
"DELAY -s NS] [-b -e OFFSET -q PK_PRIO -gtvT] IF IP\n\n"
" -h Show help\n"
" -f IF Set the network interface to be used\n"
" -a CPU Pin the real time thread to CPU\n"
" -p PRIO RT thread priority\n"
" -i USEC RT thread wake-up interval (default: 10ms)\n"
" -r USEC non-RT main thread refresh interval\n"
" -l N_CYCLES RT thread cycles amount (default: infinite)\n"
" -d BUF_LEN TX buffer len\n"
" -c USEC Send timestamp with the specified delay\n"
" -s NS Common start time reference\n"
" -b Measure RTT\n"
" -e USEC txtime userspace offset (for ETF qdisc)\n"
" -q PK_PRIO Packet priority \n"
" -g Print histograms to sdtout on exit\n"
" -t Enable TX timestamps\n"
" -v Verbose\n"
" -T Enable tracing until deadline is missed\n"
" -S Enable tracing until threshold is reached\n"
" -L Log timestamps in a file called timestamps_log\n"
"\n",
argv[0]);
}
/*
* Real-time thread:
* - Sends packets at a regular intervall
*/
static void *packet_sending_thread(void *p) {
(void)p;
struct timespec next, current, previous;
uint64_t next_txtime = 0, tx_data = 0;
int ret;
cpu_set_t mask;
// Set thread CPU affinity
if (thread_params.affinity_cpu != -1) {
CPU_ZERO(&mask);
CPU_SET(thread_params.affinity_cpu, &mask);
if (sched_setaffinity(0, sizeof(mask), &mask))
error(EXIT_FAILURE, errno, "Could not set CPU affinity to CPU %d\n",
thread_params.affinity_cpu);
}
clock_gettime(CLOCK_MONOTONIC, &measures_start);
clock_gettime(CLOCK_REALTIME, &next);
if (thread_params.start_ts) {
if (thread_params.start_ts < ts_to_uint(next)) {
fprintf(stderr, "start timestamp is in the past, aborting...\n");
exit(EXIT_FAILURE);
}
if (thread_params.start_ts > (ts_to_uint(next) + UINT64_C(3600000000000))) {
fprintf(stderr, "start timestamp is too high, aborting...\n");
exit(EXIT_FAILURE);
}
next = uint_to_ts(thread_params.start_ts);
}
// Set txtime and TX data timestamp if required
if (enable_etf || thread_params.send_tx_delay) {
if (enable_etf) {
next_txtime = next.tv_sec * NSEC_PER_SEC + next.tv_nsec;
next_txtime += thread_params.etf_offset;
}
if (thread_params.send_tx_delay) {
tx_data = next.tv_sec * NSEC_PER_SEC + next.tv_nsec;
tx_data += thread_params.send_tx_delay;
}
}
// Packet sending loop
for (nb_cycles = 0;; nb_cycles++) {
if (thread_params.max_cycles &&
nb_cycles >= ((unsigned int)thread_params.max_cycles))
break;
clock_gettime(CLOCK_REALTIME, &current);
ret = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &next, NULL);
if (ret) {
fprintf(stderr, "clock_nanosleep returned error: %d, aborting...\n", ret);
exit(EXIT_FAILURE);
}
// TX data
*((uint64_t *)send_data) =
thread_params.send_tx_delay ? tx_data : 0xdeadbeef;
// Get timestamp before TSN task for stats
clock_gettime(CLOCK_REALTIME, &current);
do_tsn_task(send_data, next_txtime);
add_ns(&next, thread_params.interval);
// Update txtime and TX data timestamp if required
if (enable_etf) next_txtime += thread_params.interval;
if (thread_params.send_tx_delay) {
tx_data = next.tv_sec * NSEC_PER_SEC + next.tv_nsec;
tx_data += thread_params.send_tx_delay;
}
// Update statistics
if (nb_cycles) {
int interval_us = calcdiff_ns(current, previous) / 1000;
egress_stats.min_interval = _min_(interval_us, egress_stats.min_interval);
egress_stats.max_interval = _max_(interval_us, egress_stats.max_interval);
egress_stats.avg_interval =
(egress_stats.avg_interval * nb_cycles + interval_us) /
(nb_cycles + 1);
if (thread_params.enable_threshold_tracing) {
int jitter = (egress_stats.max_interval - egress_stats.min_interval);
if (jitter > thread_params.threshold) {
tracemark("Threshold reached\n");
tracing(0);
printf("Threshold reached: %dus\n", jitter);
printf("Exiting packet-exchange...\n");
exit(EXIT_SUCCESS);
}
}
}
previous = current;
}
pthread_exit(NULL);
}
/*
* Main thread:
* - Has non-real time priority
* - Handles the IO and creates the real time thread
*/
int main(int argc, char *argv[]) {
pthread_t thread;
struct sched_param param;
pthread_attr_t attr;
// Default configuration values
thread_params.interval = 100000 * 1000;
thread_params.max_cycles = 0;
thread_params.priority = 98;
thread_params.send_tx_delay = 0;
thread_params.affinity_cpu = -1;
thread_params.start_ts = 0;
thread_params.enable_etf_tracing = 0;
thread_params.enable_threshold_tracing = 0;
main_params.refresh_rate = 50000;
main_params.verbose = 0;
main_params.enable_tracing = 0;
main_params.enable_graph = 0;
egress_params.packet_priority = 3;
egress_params.tx_buffer_len = 1024;
enable_etf = 0;
enable_timestamps = 0;
enable_histograms = 0;
tsn_task = SEND_PACKET_TASK;
// Lock all current and future pages from preventing of being paged to
// swap
if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
fprintf(stderr, "mlockall failed");
exit(EXIT_FAILURE);
}
// Process bash options
process_options(argc, argv);
set_latency_target();
egress_params.use_etf = enable_etf;
egress_params.use_timestamps = enable_timestamps;
if (enable_histograms) {
// Init histograms
memset(kernel_latency_hist, 0, sizeof(kernel_latency_hist));
memset(rtt_hist, 0, sizeof(rtt_hist));
}
if (main_params.enable_tracing) {
// Open tracing file descriptors
open_fds();
}
// Catch breaks with sighand to print the histograms
init_signals(sighand);
// Initialize the UDP packet sending socket
init_udp_send(&egress_params, &egress_stats, enable_histograms,
thread_params.enable_etf_tracing, log_timestamps, kernel_latency_hist);
// Initialize the UDP packet receiving socket if RTT is measured
if (tsn_task == RTT_TASK)
init_udp_recv(&ingress_params, &ingress_stats, enable_histograms,
kernel_latency_hist);
// Initialize pthread attributes (default values)
if (pthread_attr_init(&attr)) {
fprintf(stderr, "init pthread attributes failed\n");
exit(EXIT_FAILURE);
}
// Set a specific stack size
if (pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN)) {
fprintf(stderr, "pthread setstacksize failed\n");
exit(EXIT_FAILURE);
}
// Set scheduler policy and priority of pthread
if (pthread_attr_setschedpolicy(&attr, SCHED_FIFO)) {
fprintf(stderr, "pthread setschedpolicy failed\n");
exit(EXIT_FAILURE);
}
param.sched_priority = thread_params.priority;
if (pthread_attr_setschedparam(&attr, &param)) {
fprintf(stderr, "pthread setschedparam failed\n");
exit(EXIT_FAILURE);
}
/* Use scheduling parameters of attr */
if (pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) {
fprintf(stderr, "pthread setinheritsched failed\n");
exit(EXIT_FAILURE);
}
// Create the real time thread
if (pthread_create(&thread, &attr, packet_sending_thread, NULL))
error(EXIT_FAILURE, errno, "Couldn't create packet sending thread");
// Verbose loop
for (;;) {
usleep(main_params.refresh_rate);
if (main_params.verbose) {
// RTT stats printing
if (tsn_task == RTT_TASK) {
// N_CYCLES, RTT min / avg / max
printf("%9" PRIu64 ": RTT: %4d %4d %4d\n", nb_cycles, rtt_stats.min_rtt,
rtt_stats.avg_rtt, rtt_stats.max_rtt);
printf("\033[%dA", 1);
// Packet send stats printing
} else {
// N_CYCLES, error queue, measured send interval min / avg / max
printf("%9" PRIu64 ": [%4d, %4d], I (10us): %3d %3d %3d", nb_cycles,
(int)egress_stats.invalid_parameter,
(int)egress_stats.missed_deadline,
egress_stats.min_interval / 10, egress_stats.avg_interval / 10,
egress_stats.max_interval / 10);
// SOF_TX timestamps
if (enable_timestamps)
printf(", K: %4d %4d %4d [%4d]\n", egress_stats.min_kernel_latency,
egress_stats.avg_kernel_latency,
egress_stats.max_kernel_latency,
(int)egress_stats.high_kernel_latency);
else
printf("\n");
printf("\033[%dA", 1);
}
}
if (thread_params.max_cycles)
if (thread_params.max_cycles == nb_cycles) break;
}
if (enable_histograms) print_histograms();
exit(EXIT_SUCCESS);
}
/* Critical TSN task
*/
static void do_tsn_task(char *data, uint64_t next_txtime) {
struct timespec t1, t2;
int rtt_us;
// One way packet sending
if (tsn_task == SEND_PACKET_TASK) {
send_udp_packet(data, next_txtime);
// Round Trip Time measurement
} else if (tsn_task == RTT_TASK) {
clock_gettime(CLOCK_MONOTONIC, &t1);
send_udp_packet(data, next_txtime);
recv_udp_packet();
clock_gettime(CLOCK_MONOTONIC, &t2);
rtt_us = calcdiff_ns(t2, t1) / 1000;
rtt_stats.min_rtt = _min_(rtt_us, rtt_stats.min_rtt);
rtt_stats.max_rtt = _max_(rtt_us, rtt_stats.max_rtt);
rtt_stats.avg_rtt =
(((uint64_t)rtt_stats.avg_rtt) * (nb_cycles - 1) + rtt_us) / nb_cycles;
if (rtt_us > MAX_RTT)
fprintf(stderr, "RTT value higher than MAX_RTT : %d ( > %d)\n", rtt_us,
MAX_RTT);
else
rtt_hist[rtt_us]++;
}
}
/* Print histogram
*/
static void print_histograms() {
uint64_t duration;
int duration_hour, duration_minutes;
int max_hist_val;
uint64_t *histogram;
clock_gettime(CLOCK_MONOTONIC, &measures_end);
duration = calcdiff_ns(measures_end, measures_start);
duration_hour = duration / NSEC_PER_SEC / 3600;
duration_minutes = duration / NSEC_PER_SEC / 60 - duration_hour * 60;
if (tsn_task == SEND_PACKET_TASK) {
histogram = kernel_latency_hist;
max_hist_val = histogram_max(histogram, MAX_KERNEL_LATENCY - 1);
} else {
histogram = rtt_hist;
max_hist_val = histogram_max(histogram, MAX_RTT - 1);
}
printf("# Packet TX timestamps histogram\n");
for (int j = 0; j < max_hist_val; j++)
printf("%06d %015" PRIi64 "\n", j, histogram[j]);
printf(
"# Duration: %dh%d\n"
"# High kernel latencies: %" PRIu64
"\n"
"# Invalid parameters: %" PRIu64
"\n"
"# Missed deadlines: %" PRIu64 "\n",
duration_hour, duration_minutes, egress_stats.high_kernel_latency,
egress_stats.invalid_parameter, egress_stats.missed_deadline);
}
static void sighand(int sig_num) {
(void)sig_num;
if (enable_histograms) print_histograms();
exit(EXIT_SUCCESS);
}
/* Process bash options
*/
static void process_options(int argc, char *argv[]) {
for (;;) {
int c = getopt(argc, argv, "a:bc:d:e:ghi:l:p:q:r:s:tvTS:L");
if (c == -1) break;
switch (c) {
case 'a':
thread_params.affinity_cpu = atoi(optarg);
break;
case 'b':
tsn_task = RTT_TASK;
break;
case 'c':
thread_params.send_tx_delay = strtoull(optarg, NULL, 10) * 1000;
break;
case 'd':
egress_params.tx_buffer_len = atoi(optarg);
if (egress_params.tx_buffer_len < 1) {
fprintf(stderr, "BUF_LEN should be greater than 1\n");
exit(EXIT_FAILURE);
}
break;
case 'e':
enable_etf = 1;
thread_params.etf_offset = atoi(optarg) * 1000;
break;
case 'g':
enable_histograms = 1;
break;
case 'h':
help(argv);
exit(EXIT_SUCCESS);
break;
case 'i':
thread_params.interval = strtoull(optarg, NULL, 10) * 1000;
break;
case 'l':
thread_params.max_cycles = atoi(optarg);
break;
case 'p':
thread_params.priority = atoi(optarg);
break;
case 'q':
egress_params.packet_priority = atoi(optarg);
break;
case 's':
thread_params.start_ts = strtoull(optarg, NULL, 10);
break;
case 'r':
main_params.refresh_rate = atoi(optarg);
break;
case 't':
enable_timestamps = 1;
break;
case 'v':
main_params.verbose = 1;
break;
case 'T':
main_params.enable_tracing = 1;
thread_params.enable_etf_tracing = 1;
break;
case 'S':
main_params.enable_tracing = 1;
thread_params.enable_threshold_tracing = 1;
thread_params.threshold = atoi(optarg);
break;
case 'L':
log_timestamps = 1;
break;
}
}
if (argc != optind + 2) {
if (argc < optind + 2)
fprintf(stderr, "You need to specifiy an interface and IP address\n");
else
fprintf(stderr, "Too many arguments\n");
help(argv);
exit(EXIT_FAILURE);
}
strcpy(egress_params.network_if, argv[optind]);
strcpy(egress_params.server_ip, argv[optind + 1]);
}
#define _GNU_SOURCE
#include "common.h"
#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
static int latency_target_fd = -1;
static int32_t latency_target_value = 0;
void (*previous_handlers[NSIG])(int);
static void (*sighand)(int);
uint64_t ts_to_uint(struct timespec t) {
return t.tv_sec * NSEC_PER_SEC + t.tv_nsec;
}
struct timespec uint_to_ts(uint64_t t) {
struct timespec ts;
ts.tv_sec = t / NSEC_PER_SEC;
ts.tv_nsec = t - (ts.tv_sec * NSEC_PER_SEC);
return ts;
}
/* Latency trick
* if the file /dev/cpu_dma_latency exists,
* open it and write a zero into it. This will tell
* the power management system not to transition to
* a high cstate (in fact, the system acts like idle=poll)
* When the fd to /dev/cpu_dma_latency is closed, the behavior
* goes back to the system default.
*
* Documentation/power/pm_qos_interface.txt
*/
void set_latency_target(void) {
struct stat s;
int err;
errno = 0;
err = stat("/dev/cpu_dma_latency", &s);
if (err == -1) {
error(EXIT_FAILURE, errno, "WARN: stat /dev/cpu_dma_latency failed");
return;
}
errno = 0;
latency_target_fd = open("/dev/cpu_dma_latency", O_RDWR);
if (latency_target_fd == -1) {
error(EXIT_FAILURE, errno, "WARN: open /dev/cpu_dma_latency");
return;
}
errno = 0;
err = write(latency_target_fd, &latency_target_value, 4);
if (err < 1) {
error(EXIT_FAILURE, errno, "# error setting cpu_dma_latency to %d!",
latency_target_value);
close(latency_target_fd);
return;
}
printf("# /dev/cpu_dma_latency set to %dus\n", latency_target_value);
}
void add_ns(struct timespec *t, uint64_t ns) {
t->tv_nsec += ns;
while (t->tv_nsec >= ((int64_t)NSEC_PER_SEC)) {
t->tv_sec += 1;
t->tv_nsec -= NSEC_PER_SEC;
}
}
uint64_t calcdiff_ns(struct timespec t1, struct timespec t2) {
uint64_t diff;
diff = NSEC_PER_SEC * (uint64_t)((int)t1.tv_sec - (int)t2.tv_sec);
diff += ((int)t1.tv_nsec - (int)t2.tv_nsec);
return diff;
}
int64_t _max_(int64_t a, int64_t b) { return a > b ? a : b; }
int64_t _min_(int64_t a, int64_t b) { return a < b ? a : b; }
int histogram_min(uint64_t *histogram, int max_value) {
int ret = max_value;
for (int i = max_value; i >= 0; i--) ret = histogram[i] ? i : ret;
return ret;
}
int histogram_max(uint64_t *histogram, int max_value) {
int ret = 0;
for (int i = 0; i <= max_value; i++) ret = histogram[i] ? i : ret;
return ret;
}
static void sighand_wrapper(int sig) {
// If we get un unexpected signal, report it, if not print the histogram
if (sig == SIGINT || sig == SIGTERM)
(*sighand)(sig); // Will print the histogram
else
printf("Uknown signal interrupt: %s (%d)\n", strsignal(sig), sig);
// Execute the default handler
if (previous_handlers[sig] == SIG_DFL) {
signal(sig, SIG_DFL);
raise(sig);
} else if (previous_handlers[sig] == SIG_IGN) {
return;
} else {
(*previous_handlers[sig])(sig);
}
}
void init_signals(void (*_sighand)(int)) {
sighand = _sighand;
for (int i = 0; i < NSIG; i++) signal(i, sighand_wrapper);
}
static int get_bit(uint64_t t, int i) {
return (t & (((uint64_t) 1) << i)) > 0;
}
void encode(uint64_t t, char * b) {
for(int i = 0; i < 64; i++)
b[i] = get_bit(t, i) * 0xff;
}
uint64_t decode(char * b) {
uint64_t ret = 0;
for(int i = 0; i < 64; i++) {
int sum_bits = 0;
for(int j = 0; j < 8; j++)
sum_bits += (b[i] & (1 << j)) > 0;
//printf("Sum: %d (%x)\n", sum_bits, b[i]);
ret |= (sum_bits >= 4) * (((uint64_t) 1) << i);
}
return ret;
}
#ifndef UTILITIES_H
#define UTILITIES_H
#define _GNU_SOURCE
#include <inttypes.h>
#include <signal.h>
#include <stdint.h>
#include <time.h>
#include <unistd.h>
#ifdef WITH_XDP
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <bpf/xsk.h>
#include <linux/if_ether.h>
#include <linux/if_link.h>
#include <linux/if_xdp.h>
#include <linux/ip.h>
#include <linux/udp.h>
#endif
#define NSEC_PER_SEC UINT64_C(1000000000)
#define SERVER_PORT "50000"
#define SERVER_PORT_INT 50000
#define MAX_KERNEL_LATENCY 1000
#define MAX_RTT 1000
#define MAX_JITTER 1000
#define MAX_BUFFER_SIZE 1024
#define TIMESTAMP_BUFFER_SIZE 4096
#define err(...) \
do { \
fprintf(stderr, __VA_ARGS__); \
exit(EXIT_FAILURE); \
} while (0)
#define err_errno(...) error(EXIT_FAILURE, errno, __VA_ARGS__);
uint64_t ts_to_uint(struct timespec t);
struct timespec uint_to_ts(uint64_t t);
void add_ns(struct timespec *t, uint64_t ns);
uint64_t calcdiff_ns(struct timespec t1, struct timespec t2);
void set_latency_target(void);
void init_signals(void (*_sighand)(int));
int64_t _min_(int64_t a, int64_t b);
int64_t _max_(int64_t a, int64_t b);
int histogram_min(uint64_t *histogram, int max_value);
int histogram_max(uint64_t *histogram, int max_value);
extern void (*previous_handlers[NSIG])(int);
void encode(uint64_t t, char * b);
uint64_t decode(char * b);
#endif
#include "gpio.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
static int gpio_state;
static char cmd[128];
static char * one = "1";
static char * zero = "0";
static int fd;
void enable_gpio(int gpio_index) {
sprintf(cmd, "echo %d > /sys/class/gpio/export", gpio_index);
system(cmd);
sprintf(cmd, "echo out > /sys/class/gpio/gpio%d/direction", gpio_index);
system(cmd);
sprintf(cmd, "/sys/class/gpio/gpio%d/value", gpio_index);
fd = open(cmd, O_WRONLY);
}
void toggle_gpio() {
if(gpio_state)
write(fd, one, 1);
else
write(fd, zero, 1);
gpio_state = !gpio_state;
}
#ifndef GPIO_H
#define GPIO_H
void enable_gpio(int gpio_index);
void toggle_gpio();
#endif
#define _GNU_SOURCE
#include "recv_packet.h"
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <getopt.h>
#include <ifaddrs.h>
#include <inttypes.h>
#include <limits.h>
#include <linux/errqueue.h>
#include <linux/ethtool.h>
#include <linux/net_tstamp.h>
#include <linux/sockios.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "common.h"
#ifdef WITH_XDP
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <bpf/xsk.h>
#include <linux/if_ether.h>
#include <linux/if_link.h>
#include <linux/if_xdp.h>
#include <linux/ip.h>
#include <linux/udp.h>
#endif
static char rx_buffer[MAX_BUFFER_SIZE];
static int sock_fd;
static int so_timestamping_flags =
SOF_TIMESTAMPING_RX_SOFTWARE | SOF_TIMESTAMPING_SOFTWARE;
static ingress_param_t *params;
static ingress_stat_t *stats;
static uint64_t *kernel_latency_hist;
static int use_histogram;
uint64_t post_kernel_timestamp;
// Sets the interface
static int set_if(void) {
struct ifreq ifreq;
memset(&ifreq, 0, sizeof(ifreq));
strncpy(ifreq.ifr_name, params->network_if, sizeof(ifreq.ifr_name) - 1);
if (ioctl(sock_fd, SIOCGIFINDEX, &ifreq))
error(EXIT_FAILURE, errno, "ioctl SIOCGIFINDEX failed\n");
return ifreq.ifr_ifindex;
}
void init_udp_recv(ingress_param_t *_params, ingress_stat_t *_stats,
int _use_histogram, uint64_t *_kernel_latency_hist) {
int getaddrinfo_err;
int set_if_err;
struct addrinfo hints, *servinfo, *servinfo_it;
params = _params;
stats = _stats;
use_histogram = _use_histogram;
kernel_latency_hist = _kernel_latency_hist;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_PASSIVE;
getaddrinfo_err = getaddrinfo(NULL, SERVER_PORT, &hints, &servinfo);
if (getaddrinfo_err != 0) {
fprintf(stderr, "getaddrinfo: %s\n",
gai_strerror(getaddrinfo_err));
exit(EXIT_FAILURE);
}
for (servinfo_it = servinfo; servinfo_it;
servinfo_it = servinfo_it->ai_next) {
sock_fd = socket(servinfo->ai_family, servinfo->ai_socktype,
servinfo->ai_protocol);
if (bind(sock_fd, servinfo_it->ai_addr,
servinfo_it->ai_addrlen) == -1) {
close(sock_fd);
continue;
}
break;
}
freeaddrinfo(servinfo);
if (sock_fd == -1)
error(EXIT_FAILURE, errno, "Couldn't create receive socket");
set_if_err = set_if();
if (set_if_err < 0)
error(EXIT_FAILURE, errno, "Couldn't set interface\n");
if (setsockopt(sock_fd, SOL_SOCKET, SO_BINDTODEVICE, params->network_if,
strlen(params->network_if)))
error(EXIT_FAILURE, errno,
"setsockopt SO_BINDTODEVICE failed\n");
if (params->use_timestamps) {
if (setsockopt(sock_fd, SOL_SOCKET, SO_TIMESTAMPING,
&so_timestamping_flags,
sizeof(so_timestamping_flags)))
error(EXIT_FAILURE, errno,
"setsockopt SO_TIMESTAMPING failed\n");
}
}
/*
* Receive UDP packet
*/
void recv_udp_packet() {
struct cmsghdr *cmsg;
struct msghdr msg; // Message hardware, sent to the socket
struct iovec iov; // The iovec structures stores the RX buffer
struct sockaddr_in sin;
struct {
struct cmsghdr cm;
char control[512];
} control;
int recvmsgerr;
struct timespec ts;
iov.iov_base = &rx_buffer;
iov.iov_len = MAX_BUFFER_SIZE - 1;
memset(&msg, 0, sizeof(msg));
msg.msg_name = &sin;
msg.msg_namelen = sizeof(sin);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = &control;
msg.msg_controllen = sizeof(control);
recvmsgerr = recvmsg(sock_fd, &msg, 0);
if (recvmsgerr < 0)
error(EXIT_FAILURE, errno, "recvmsg failed, ret value: %d\n",
recvmsgerr);
if (params->use_timestamps) {
clock_gettime(CLOCK_REALTIME, &ts);
post_kernel_timestamp = ts_to_uint(ts);
}
if (params->use_timestamps) {
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg;
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SO_TIMESTAMPING) {
struct timespec *stamp =
(struct timespec *)CMSG_DATA(cmsg);
uint64_t kernel_latency =
post_kernel_timestamp - ts_to_uint(*stamp);
kernel_latency /= 1000u;
stats->min_kernel_latency = _min_(
kernel_latency, stats->min_kernel_latency);
stats->max_kernel_latency = _max_(
kernel_latency, stats->max_kernel_latency);
stats->avg_kernel_latency =
(stats->max_kernel_latency *
(stats->packets_received) +
kernel_latency) /
(stats->packets_received + 1);
if (use_histogram) {
if (kernel_latency > MAX_KERNEL_LATENCY)
stats->high_kernel_latency++;
else
kernel_latency_hist
[kernel_latency]++;
}
}
}
}
for(int i = 0; i < MAX_BUFFER_SIZE; i++)
stats->data[i] = rx_buffer[i];
}
#ifdef WITH_XDP
static int xdp_flags = XDP_FLAGS_DRV_MODE;
static struct pollfd fds[1] = {0};
static unsigned int ifindex;
static struct xdpsock xdp_socket;
static void open_xdp_socket(char *network_if) {
struct xsk_socket_config xsk_cfg;
uint32_t idx;
int ret, i;
/* Create XDP socket */
xsk_cfg.rx_size = XSK_RING_CONS__DEFAULT_NUM_DESCS;
xsk_cfg.tx_size = XSK_RING_PROD__DEFAULT_NUM_DESCS;
xsk_cfg.libbpf_flags = 0;
xsk_cfg.xdp_flags = xdp_flags;
xsk_cfg.bind_flags = 0;
ret = xsk_socket__create(&xdp_socket.xsk, network_if, 0,
xdp_socket.umem.umem, &xdp_socket.rx,
&xdp_socket.tx, &xsk_cfg);
if (ret) err("xsk_socket__create() failed");
/* Add some buffers */
ret = xsk_ring_prod__reserve(&xdp_socket.umem.fq,
XSK_RING_PROD__DEFAULT_NUM_DESCS, &idx);
if (ret != XSK_RING_PROD__DEFAULT_NUM_DESCS)
err("xsk_ring_prod__reserve() failed");
for (i = 0; i < XSK_RING_PROD__DEFAULT_NUM_DESCS; i++)
*xsk_ring_prod__fill_addr(&xdp_socket.umem.fq, idx++) =
i * FRAME_SIZE;
xsk_ring_prod__submit(&xdp_socket.umem.fq,
XSK_RING_PROD__DEFAULT_NUM_DESCS);
}
/*
* Init XDP socket
*/
void init_xdp_recv(ingress_param_t * _params) {
int ret, prog_fd, xsks_map = 0;
struct bpf_prog_load_attr prog_load_attr = {
.prog_type = BPF_PROG_TYPE_XDP,
.file = "/home/oli/rt-measures/packet-exchange/build/xdp_kern.o",
};
struct xsk_umem_config cfg = {
.fill_size = XSK_RING_PROD__DEFAULT_NUM_DESCS,
.comp_size = XSK_RING_CONS__DEFAULT_NUM_DESCS,
.frame_size = FRAME_SIZE,
.frame_headroom = XSK_UMEM__DEFAULT_FRAME_HEADROOM,
.flags = 0,
};
struct bpf_object *obj;
struct bpf_map *map;
void *buffer = NULL;
params = _params;
ret = bpf_prog_load_xattr(&prog_load_attr, &obj, &prog_fd);
if (ret || prog_fd < 0) err("bpf_prog_load_xattr() failed");
map = bpf_object__find_map_by_name(obj, "xsks_map");
xsks_map = bpf_map__fd(map);
if (xsks_map < 0) err("No xsks_map found!");
ifindex = if_nametoindex(params->network_if);
if (!ifindex) err_errno("if_nametoindex() failed");
/* Use XDP _only_ in conjuction with driver assisted mode */
ret = bpf_set_link_xdp_fd(ifindex, prog_fd, xdp_flags);
if (ret) err("bpf_set_link_xdp_fd() failed");
/* Allocate user space memory for xdp frames */
ret = posix_memalign(&buffer, sysconf(_SC_PAGE_SIZE),
NUM_FRAMES * FRAME_SIZE);
if (ret) err_errno("posix_memalign() failed");
ret = xsk_umem__create(&xdp_socket.umem.umem, buffer,
NUM_FRAMES * FRAME_SIZE, &xdp_socket.umem.fq,
&xdp_socket.umem.cq, &cfg);
if (ret) err("xsk_umem__create() failed");
xdp_socket.umem.buffer = buffer;
/* Open and bind socket */
open_xdp_socket(params->network_if);
}
void setup_poll_fd(void) {
fds[0].fd = xsk_socket__fd(xdp_socket.xsk);
fds[0].events = POLLIN;
}
static int received;
static uint32_t idx_rx = 0, idx;
/*
* Receive XDP socket
*/
int recv_xdp_packet(void) {
int ret;
if (params->xdp_polling_mode == 0) {
ret = poll(fds, 1, -1);
if (ret == 0) {
return -1;
} else if (ret < 0)
error(EXIT_FAILURE, errno, "poll failed");
received = xsk_ring_cons__peek(&xdp_socket.rx, 1, &idx_rx);
} else {
do {
received =
xsk_ring_cons__peek(&xdp_socket.rx, 1, &idx_rx);
} while (!received);
}
if (!received) return 1;
if (received != 1)
error(EXIT_FAILURE, errno,
"Received more packets than expected");
return 0;
}
void recv_xdp_cleanup(void) {
uint64_t addr;
int ret;
/* Cleanup */
xsk_ring_cons__release(&xdp_socket.rx, received);
/* Add that particular buffer back to the fill queue */
if (xsk_prod_nb_free(&xdp_socket.umem.fq, received)) {
ret =
xsk_ring_prod__reserve(&xdp_socket.umem.fq, received, &idx);
if (ret != received) err("xsk_ring_prod__reserve() failed");
*xsk_ring_prod__fill_addr(&xdp_socket.umem.fq, idx) =
xsk_umem__extract_addr(addr);
xsk_ring_prod__submit(&xdp_socket.umem.fq, received);
}
}
void close_xdp_socket(void) {
xsk_socket__delete(xdp_socket.xsk);
xsk_umem__delete(xdp_socket.umem.umem);
bpf_set_link_xdp_fd(ifindex, -1, xdp_flags);
}
#else
void init_xdp_recv(ingress_param_t * _params) { (void) _params; }
void setup_poll_fd(void) {}
void close_xdp_socket(void) {}
int recv_xdp_packet(void) { return 0; }
void recv_xdp_cleanup(void) {}
#endif
#ifdef DEBUG
/*
* Code from scheduled_tx_tools
*/
static int process_socket_error_queue(void) {
uint8_t msg_control[CMSG_SPACE(sizeof(struct sock_extended_err))];
unsigned char err_buffer[256];
struct sock_extended_err *serr;
struct cmsghdr *cmsg;
__u64 tstamp = 0;
struct iovec iov = {.iov_base = err_buffer,
.iov_len = sizeof(err_buffer)};
struct msghdr msg = {.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = msg_control,
.msg_controllen = sizeof(msg_control)};
if (recvmsg(sock_fd, &msg, MSG_ERRQUEUE) == -1) {
fprintf(stderr, "recvmsg failed");
return -1;
}
cmsg = CMSG_FIRSTHDR(&msg);
while (cmsg != NULL) {
serr = (void *)CMSG_DATA(cmsg);
if (serr->ee_origin == SO_EE_ORIGIN_TXTIME) {
tstamp = ((__u64)serr->ee_data << 32) + serr->ee_info;
switch (serr->ee_code) {
case SO_EE_CODE_TXTIME_INVALID_PARAM:
fprintf(
stderr,
"packet with tstamp %llu dropped "
"due to invalid params\n",
tstamp);
return 0;
case SO_EE_CODE_TXTIME_MISSED:
fprintf(
stderr,
"packet with tstamp %llu dropped "
"due to missed deadline\n",
tstamp);
return 0;
default:
return -1;
}
}
cmsg = CMSG_NXTHDR(&msg, cmsg);
}
return 0;
}
#endif
#ifndef RECV_PACKET
#define RECV_PACKET
#include "common.h"
typedef struct ingress_param {
char network_if[16];
int use_timestamps;
int xdp_polling_mode;
uint64_t interval;
size_t tx_buffer_len;
} ingress_param_t;
typedef struct ingress_stat {
int min_kernel_latency;
int avg_kernel_latency;
int max_kernel_latency;
int min_interval;
int avg_interval;
int max_interval;
uint64_t packets_received;
uint64_t high_kernel_latency;
uint64_t high_jitter;
char data[MAX_BUFFER_SIZE];
} ingress_stat_t;
void init_udp_recv(ingress_param_t *_params, ingress_stat_t *stats,
int use_histogram, uint64_t *_kernel_latency_hist);
void recv_udp_packet(void);
#ifdef WITH_XDP
#define NUM_FRAMES 4096
#define FRAME_SIZE XSK_UMEM__DEFAULT_FRAME_SIZE
struct xsk_umem_info {
struct xsk_ring_prod fq;
struct xsk_ring_cons cq;
struct xsk_umem *umem;
void *buffer;
};
struct xdpsock {
struct xsk_ring_cons rx;
struct xsk_ring_prod tx;
struct xsk_umem_info umem;
struct xsk_socket *xsk;
int fd;
};
#endif
void init_xdp_recv(ingress_param_t * _params);
int recv_xdp_packet(void);
void recv_xdp_cleanup(void);
void setup_poll_fd(void);
void close_xdp_socket(void);
#endif
/*
*
* UDP packet sending functions
*
* Large portions taken from scheduled tx tools gist
*
*/
#define _GNU_SOURCE
#include "send_packet.h"
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <inttypes.h>
#include <linux/errqueue.h>
#include <linux/ethtool.h>
#include <linux/net_tstamp.h>
#include <linux/sockios.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "common.h"
#include "tracer.h"
static void *poll_thread(void *p);
static void process_error_queue();
static void init_tx_buffer();
static int set_if();
static int so_timestamping_flags =
SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_SOFTWARE;
static egress_param_t *params;
static egress_stat_t *stats;
static uint64_t *kernel_latency_hist;
static int use_histogram;
static int stop_tracing;
static uint64_t packets_sent = 0;
static struct sock_txtime sk_txtime;
static char *tx_buffer;
static int sock_fd;
static int64_t timestamps_buffer[TIMESTAMP_BUFFER_SIZE];
static int ts_buf_read_index = 0;
static int ts_buf_write_index = 0;
static int log_timestamps = 0;
static FILE * log_file;
/*
* Init UDP socket
*/
void init_udp_send(egress_param_t *_params, egress_stat_t *_stats,
int _use_histogram, int _stop_tracing, int _log_timestamps, uint64_t *_kernel_latency_hist) {
int set_if_err;
pthread_t thread;
params = _params;
stats = _stats;
kernel_latency_hist = _kernel_latency_hist;
use_histogram = _use_histogram;
stop_tracing = _stop_tracing;
log_timestamps = _log_timestamps;
if(log_timestamps)
log_file = fopen("timestamps_log", "w+");
init_tx_buffer();
sock_fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock_fd < 0) error(EXIT_FAILURE, errno, "Socket creation failed\n");
set_if_err = set_if();
if (set_if_err < 0) error(EXIT_FAILURE, errno, "Couldn't set interface\n");
if (setsockopt(sock_fd, SOL_SOCKET, SO_PRIORITY, &params->packet_priority,
sizeof(params->packet_priority)))
error(EXIT_FAILURE, errno, "Couldn't set socket priority\n");
if (setsockopt(sock_fd, SOL_SOCKET, SO_BINDTODEVICE, params->network_if,
strlen(params->network_if)))
error(EXIT_FAILURE, errno, "setsockopt SO_BINDTODEVICE failed\n");
if (params->use_etf) {
sk_txtime.clockid = CLOCK_REALTIME;
sk_txtime.flags = SOF_TXTIME_REPORT_ERRORS;
if (setsockopt(sock_fd, SOL_SOCKET, SO_TXTIME, &sk_txtime,
sizeof(sk_txtime)))
error(EXIT_FAILURE, errno, "setsockopt SO_TXTIME failed\n");
}
if (params->use_timestamps) {
if (setsockopt(sock_fd, SOL_SOCKET, SO_TIMESTAMPING, &so_timestamping_flags,
sizeof(so_timestamping_flags)))
error(EXIT_FAILURE, errno, "setsockopt SO_TIMESTAMPING failed\n");
}
// Create poll thread
if (pthread_create(&thread, NULL, poll_thread, NULL))
error(EXIT_FAILURE, errno, "Couldn't create poll thread");
}
/*
* Sends udp packets
*/
void send_udp_packet(char *data, uint64_t txtime) {
struct msghdr msg; // Message hardware, sent to the socket
struct cmsghdr *cmsg; // Control message hardware, for txtime
char control[CMSG_SPACE(sizeof(txtime))] = {}; // Stores txtime
struct iovec iov; // The iovec structures stores the TX buffer
int sendmsgerr;
struct sockaddr_in sin; // Server address
struct timespec ts; // timestamp for userspace timestamping
if (params->use_timestamps) {
clock_gettime(CLOCK_REALTIME, &ts);
timestamps_buffer[ts_buf_write_index] = ts_to_uint(ts);
ts_buf_write_index = (ts_buf_write_index + 1) % TIMESTAMP_BUFFER_SIZE;
}
packets_sent++;
clock_gettime(CLOCK_REALTIME, &ts);
encode(ts_to_uint(ts), tx_buffer);
if(log_timestamps) {
fprintf(log_file, "%" PRIi64 "\n", ts_to_uint(ts));
}
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = inet_addr(params->server_ip);
sin.sin_port = htons(SERVER_PORT_INT);
iov.iov_base = tx_buffer;
iov.iov_len = params->tx_buffer_len;
memset(&msg, 0, sizeof(msg));
msg.msg_name = &sin;
msg.msg_namelen = sizeof(sin);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
if (params->use_etf) {
// We specify the transmission time in the CMSG.
msg.msg_control = control;
msg.msg_controllen = sizeof(control);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_TXTIME;
cmsg->cmsg_len = CMSG_LEN(sizeof(uint64_t));
*((uint64_t *)CMSG_DATA(cmsg)) = txtime;
msg.msg_controllen = cmsg->cmsg_len;
}
sendmsgerr = sendmsg(sock_fd, &msg, 0);
if (sendmsgerr < 0)
error(EXIT_FAILURE, errno, "sendmsg failed, ret value: %d\n", sendmsgerr);
}
static void *poll_thread(void *p) {
(void)p;
// Poll file descriptor
struct pollfd poll_fd = {.fd = sock_fd};
while (1) {
int ret;
ret = poll(&poll_fd, 1, -1);
if (ret == 1 && poll_fd.revents & POLLERR) {
process_error_queue();
}
}
return NULL;
}
static void process_error_queue() {
int recv_ret;
// IO vector
unsigned char data_buffer[256]; // Buffer in io vector
struct iovec iov = {.iov_base = data_buffer, .iov_len = sizeof(data_buffer)};
// Control data, will store error or timestamps
unsigned char msg_control[CMSG_SPACE(sizeof(struct sock_extended_err)) +
CMSG_SPACE(sizeof(struct timespec))];
// Message hardware structure, containts IO vector and control message
// hardware
struct msghdr msg = {.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = msg_control,
.msg_controllen = sizeof(msg_control)};
struct cmsghdr *cmsg;
// Timestamps and errors are received in the error queue
recv_ret = recvmsg(sock_fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
if (recv_ret == -1) {
fprintf(stderr, "recvmsg() failed\n");
return;
}
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
// If a timestamp was received
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SO_TIMESTAMPING) {
struct timespec *stamp = (struct timespec *)CMSG_DATA(cmsg);
int kernel_latency =
(ts_to_uint(*stamp) - timestamps_buffer[ts_buf_read_index]) / 1000;
ts_buf_read_index = (ts_buf_read_index + 1) % TIMESTAMP_BUFFER_SIZE;
stats->min_kernel_latency =
_min_(kernel_latency, stats->min_kernel_latency);
stats->max_kernel_latency =
_max_(kernel_latency, stats->max_kernel_latency);
stats->avg_kernel_latency =
(stats->max_kernel_latency * packets_sent + kernel_latency) /
(packets_sent + 1);
if (use_histogram) {
if (kernel_latency > MAX_KERNEL_LATENCY)
stats->high_kernel_latency++;
else
kernel_latency_hist[kernel_latency]++;
}
}
// If an error was received
else {
struct sock_extended_err *serr = (void *)CMSG_DATA(cmsg);
if (serr->ee_origin != SO_EE_ORIGIN_TXTIME) continue;
switch (serr->ee_code) {
case SO_EE_CODE_TXTIME_INVALID_PARAM:
stats->invalid_parameter++;
break;
case SO_EE_CODE_TXTIME_MISSED:
if(stop_tracing) {
tracemark("Deadline missed\n");
tracing(0);
exit(EXIT_FAILURE);
}
stats->missed_deadline++;
break;
default:
fprintf(stderr, "Uknown TxTime error\n");
}
}
}
}
// Sets the interface
static int set_if() {
struct ifreq ifreq;
memset(&ifreq, 0, sizeof(ifreq));
strncpy(ifreq.ifr_name, params->network_if, sizeof(ifreq.ifr_name) - 1);
if (ioctl(sock_fd, SIOCGIFINDEX, &ifreq))
error(EXIT_FAILURE, errno, "ioctl SIOCGIFINDEX failed\n");
return ifreq.ifr_ifindex;
}
static void init_tx_buffer() {
if (params->tx_buffer_len < 1) {
fprintf(stderr, "tx buffer length should be greater than 1\n");
exit(EXIT_FAILURE);
}
tx_buffer = malloc(params->tx_buffer_len);
}
#ifndef SEND_PACKET_H
#define SEND_PACKET_H
#include "common.h"
typedef struct egress_param {
int packet_priority;
size_t tx_buffer_len;
char server_ip[45];
char network_if[16];
int use_etf;
int use_timestamps;
} egress_param_t;
typedef struct egress_stat {
uint64_t high_kernel_latency;
uint64_t invalid_parameter;
uint64_t missed_deadline;
int min_kernel_latency;
int avg_kernel_latency;
int max_kernel_latency;
int min_interval;
int avg_interval;
int max_interval;
} egress_stat_t;
void init_udp_send(egress_param_t *_params,
egress_stat_t *_stats,
int _use_histogram, int _stop_tracing, int log_timestamps,
uint64_t *_kernel_latency_hist);
void send_udp_packet(char *data, uint64_t txtime);
#endif
/*
* Real time packet receiving server
*
* Large portions taken from cyclictest
*
*/
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <inttypes.h>
#include <limits.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <unistd.h>
#include "common.h"
#include "gpio.h"
#include "recv_packet.h"
#include "send_packet.h"
#include "tracer.h"
// Structs
enum TSNTask { RECV_PACKET_TASK, RTT_TASK, XDP_TASK };
typedef struct thread_param {
uint64_t interval;
int priority;
int affinity_cpu;
uint64_t latency_threshold;
int emit_signal;
} thread_param_t;
typedef struct main_params {
int refresh_rate;
int verbose;
int enable_tracing;
int enable_xdp_tracing;
} main_param_t;
static void process_options(int argc, char *argv[]);
static void print_histograms();
static void sighand(int sig_num);
// Static variables
static uint64_t kernel_latency_hist[MAX_KERNEL_LATENCY];
static uint64_t jitter_hist[MAX_JITTER];
static main_param_t main_params;
static thread_param_t thread_params;
static ingress_param_t ingress_params;
static egress_param_t egress_params;
static egress_stat_t egress_stats = {.min_kernel_latency = INT_MAX};
static ingress_stat_t ingress_stats = {.min_kernel_latency = INT_MAX,
.min_interval = INT_MAX};
static int enable_histograms;
static int enable_timestamps;
static enum TSNTask tsn_task;
static struct timespec measures_start;
static struct timespec measures_end;
static struct timespec emit_signal_next;
static pthread_mutex_t emit_signal_mutex;
static pthread_cond_t emit_signal_ts_received;
static int64_t min_diff_ts = INT_MAX;
static int64_t max_diff_ts = 0;
static int64_t avg_diff_ts = 0;
static int log_timestamps = 0;
static FILE * log_file;
static void help(char *argv[]) {
printf(
"Usage: %s [-f IF -a CPU -p PRIO -i USEC -r USEC] [-b CLIENT_IP] [-d "
"BUF_LEN -cgtv] [-T LATENCY_THRESHOLD -G] [-x POLL_MODE -X]\n\n"
" -h Show help\n"
" -f IF Set the network interface to be used\n"
" -a CPU Pin the real time thread to CPU\n"
" -p PRIO RT thread priority\n"
" -i USEC RT thread wake-up interval (default: 10ms)\n"
" -r USEC non-RT main thread refresh interval\n"
" -d BUF_LEN Set the length of tx buffer\n"
" -c Receive timestamp and emit signal\n"
" -b CLIENT_IP Server side RTT\n"
" -g Print histograms to sdtout on exit\n"
" -t Enable timestamps\n"
" -x POLL_MODE Use AF_XDP sockets\n"
" POLL_MODE: 0 for polling, 1 for combination of both\n"
" -X Trace during XDP packet reception\n"
" -T THRESHOLD Enable tracing until THRESHOLD is reached\n"
" -L Log timestamps in a file called timestamps_log\n"
" -v Verbose\n"
"\n",
argv[0]);
}
static void *emit_signal_thread(void *p) {
(void)p;
cpu_set_t mask;
struct timespec current;
// Set thread CPU affinity
if (thread_params.affinity_cpu) {
CPU_ZERO(&mask);
CPU_SET(thread_params.affinity_cpu, &mask);
if (sched_setaffinity(0, sizeof(mask), &mask))
error(EXIT_FAILURE, errno, "Could not set CPU affinity to CPU %d\n",
thread_params.affinity_cpu);
}
pthread_mutex_lock(&emit_signal_mutex);
for (int i = 0;;i++) {
pthread_cond_wait(&emit_signal_ts_received, &emit_signal_mutex);
clock_gettime(CLOCK_REALTIME, &current);
// Sanity check
if(current.tv_sec > emit_signal_next.tv_sec)
goto invalid_ts;
if(current.tv_sec == emit_signal_next.tv_sec && current.tv_nsec > emit_signal_next.tv_nsec)
goto invalid_ts;
if(current.tv_sec + 15 < emit_signal_next.tv_sec)
goto invalid_ts;
clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &emit_signal_next, NULL);
toggle_gpio();
}
pthread_mutex_unlock(&emit_signal_mutex);
pthread_exit(NULL);
invalid_ts:
fprintf(stderr, "Invalid timestamp : %" PRIu64 "\n", ts_to_uint(emit_signal_next));
fprintf(stderr, "Current time is : %" PRIu64 "\n", ts_to_uint(current));
fprintf(stderr, "Difference with timestamp : %" PRIi64 "\n\n\n", ((int64_t)ts_to_uint(emit_signal_next)) - ((int64_t)ts_to_uint(current)));
exit(EXIT_FAILURE);
}
/* Real-time thread:
* - Measures intervals between packet receptions
*/
static void *tsn_thread(void *p) {
(void)p;
struct timespec current, previous;
cpu_set_t mask;
char tracemark_message[128];
// Set thread CPU affinity
if (thread_params.affinity_cpu) {
CPU_ZERO(&mask);
CPU_SET(thread_params.affinity_cpu, &mask);
if (sched_setaffinity(0, sizeof(mask), &mask))
error(EXIT_FAILURE, errno, "Could not set CPU affinity to CPU %d\n",
thread_params.affinity_cpu);
}
clock_gettime(CLOCK_MONOTONIC, &measures_start);
if (tsn_task == XDP_TASK) setup_poll_fd();
// Packet receiving loop
for (ingress_stats.packets_received = 0;; ingress_stats.packets_received++) {
// RTT
if (tsn_task == RTT_TASK) {
recv_udp_packet();
send_udp_packet("", 0);
// Receive packet
} else if (tsn_task == RECV_PACKET_TASK || tsn_task == XDP_TASK) {
// Receive UDP or XDP packet
if (tsn_task == RECV_PACKET_TASK)
recv_udp_packet();
else
recv_xdp_packet();
clock_gettime(CLOCK_REALTIME, &current);
recv_xdp_cleanup();
uint64_t send_ts = decode(ingress_stats.data);
int64_t diff_us = (((int64_t) ts_to_uint(current)) - ((int64_t)send_ts)) / 1000;
if(log_timestamps) {
fprintf(log_file, "%" PRIi64" %" PRIu64 " %" PRIu64 "\n", diff_us, send_ts, ts_to_uint(current));
}
min_diff_ts = _min_(diff_us, min_diff_ts);
max_diff_ts = _max_(diff_us, max_diff_ts);
avg_diff_ts = (avg_diff_ts * ingress_stats.packets_received + diff_us) / (ingress_stats.packets_received + 1);
// Update stats
if (ingress_stats.packets_received) {
int interval_us = calcdiff_ns(current, previous) / 1000;
ingress_stats.min_interval =
_min_(interval_us, ingress_stats.min_interval);
ingress_stats.max_interval =
_max_(interval_us, ingress_stats.max_interval);
ingress_stats.avg_interval =
(ingress_stats.avg_interval * ingress_stats.packets_received +
interval_us) /
(ingress_stats.packets_received + 1);
// Histogram
if (enable_histograms) {
int dist_to_interval = interval_us - (thread_params.interval / 1000);
dist_to_interval += MAX_JITTER / 2;
if (dist_to_interval > ((int)MAX_JITTER) || dist_to_interval < 0)
ingress_stats.high_jitter++;
else
jitter_hist[dist_to_interval]++;
}
}
// If the latency hits the tracing threshold, stop tracing
int jitter = ingress_stats.max_interval - ingress_stats.min_interval;
if (main_params.enable_tracing &&
(jitter > ((int)thread_params.latency_threshold))) {
sprintf(tracemark_message, "Jitter threshold hit: %dus\n",
jitter);
tracemark(tracemark_message);
tracing(0);
printf(tracemark_message);
exit(EXIT_SUCCESS);
}
previous = current;
}
}
return NULL;
}
static void create_thread(void *(*thread_function)(void *)) {
pthread_t thread;
pthread_attr_t attr;
struct sched_param param;
// Initialize pthread attributes (default values)
if (pthread_attr_init(&attr)) {
fprintf(stderr, "init pthread attributes failed\n");
exit(EXIT_FAILURE);
}
// Set a specific stack size
if (pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN)) {
fprintf(stderr, "pthread setstacksize failed\n");
exit(EXIT_FAILURE);
}
// Set scheduler policy and priority of pthread
if (pthread_attr_setschedpolicy(&attr, SCHED_FIFO)) {
fprintf(stderr, "pthread setschedpolicy failed\n");
exit(EXIT_FAILURE);
}
param.sched_priority = thread_params.priority;
if (pthread_attr_setschedparam(&attr, &param)) {
fprintf(stderr, "pthread setschedparam failed\n");
exit(EXIT_FAILURE);
}
// Use scheduling parameters of attr
if (pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) {
fprintf(stderr, "pthread setinheritsched failed\n");
exit(EXIT_FAILURE);
}
// Create the real time thread
if (pthread_create(&thread, &attr, thread_function, NULL))
error(EXIT_FAILURE, errno, "Couldn't create packet receiving thread");
}
/* Main thread:
* - Has non-real time priority
* - Handles the IO and creates real time threads
*/
int main(int argc, char *argv[]) {
// Initial values
ingress_stats.min_interval = INT_MAX;
ingress_stats.avg_interval = 0;
ingress_stats.max_interval = 0;
ingress_stats.min_kernel_latency = INT_MAX;
ingress_stats.avg_kernel_latency = 0;
ingress_stats.max_kernel_latency = 0;
ingress_stats.packets_received = 0;
// Default configuration values
thread_params.interval = 100000 * 1000;
thread_params.priority = 98;
thread_params.emit_signal = 0;
thread_params.affinity_cpu = 0;
main_params.refresh_rate = 50000;
main_params.verbose = 0;
main_params.enable_tracing = 0;
enable_timestamps = 0;
enable_histograms = 0;
tsn_task = RECV_PACKET_TASK;
ingress_params.tx_buffer_len = 1024;
// Process bash options
process_options(argc, argv);
if(log_timestamps)
log_file = fopen("timestamps_log", "w+");
set_latency_target();
ingress_params.use_timestamps = enable_timestamps;
ingress_params.interval = thread_params.interval;
// Init histograms
if (enable_histograms) {
memset(kernel_latency_hist, 0, sizeof(kernel_latency_hist));
memset(jitter_hist, 0, sizeof(jitter_hist));
}
// Enable ftrace
if (main_params.enable_tracing || main_params.enable_xdp_tracing) open_fds();
// Catch breaks with sighand to print the histograms or exit XDP
init_signals(sighand);
// Initialize the XDP or UDP packet receiving socket
if (tsn_task == XDP_TASK)
init_xdp_recv(&ingress_params);
else
init_udp_recv(&ingress_params, &ingress_stats, enable_histograms,
kernel_latency_hist);
// Initialize the UDP packet sending socket if RTT is measured
if (tsn_task == RTT_TASK)
init_udp_send(&egress_params, &egress_stats, 0, 0, 0, NULL);
if (thread_params.emit_signal) {
pthread_mutex_init(&emit_signal_mutex, NULL);
pthread_cond_init(&emit_signal_ts_received, NULL);
enable_gpio(86);
}
create_thread(tsn_thread);
create_thread(emit_signal_thread);
// Verbose loop
for (;;) {
usleep(main_params.refresh_rate);
// Stats printing
if (main_params.verbose && ingress_stats.packets_received > 1 &&
(tsn_task == RECV_PACKET_TASK || tsn_task == XDP_TASK)) {
int jitter = ingress_stats.max_interval - ingress_stats.min_interval;
printf("%9" PRIu64 ": D : %8" PRIi64 " %8" PRIi64 " %17" PRIi64 "",
ingress_stats.packets_received,
min_diff_ts,
avg_diff_ts,
max_diff_ts);
if (enable_timestamps)
printf(", K: %4d %4d %4d [%3d]\n", ingress_stats.min_kernel_latency,
ingress_stats.avg_kernel_latency,
ingress_stats.max_kernel_latency,
(int)ingress_stats.high_kernel_latency);
else
printf("\n");
printf("\033[%dA", 1);
}
}
pthread_mutex_destroy(&emit_signal_mutex);
pthread_cond_destroy(&emit_signal_ts_received);
close_xdp_socket();
}
/* Print histograms in .json format
*/
static void print_histograms() {
uint64_t duration;
int duration_hour, duration_minutes;
int max_latency, max_jitter, min_jitter;
clock_gettime(CLOCK_MONOTONIC, &measures_end);
duration = calcdiff_ns(measures_end, measures_start);
duration_hour = duration / NSEC_PER_SEC / 3600;
duration_minutes = duration / NSEC_PER_SEC / 60 - duration_hour * 60;
max_jitter = histogram_max(jitter_hist, MAX_JITTER - 1);
min_jitter = histogram_min(jitter_hist, MAX_JITTER - 1);
if (enable_timestamps) {
max_latency = histogram_max(kernel_latency_hist, MAX_KERNEL_LATENCY - 1);
printf("# Packet RX timestamps histogram\n");
for (int j = 0; j < max_latency; j++)
printf(" %06d %015" PRIi64 "\n", j, kernel_latency_hist[j]);
printf(
"# Duration: %dh%d\n"
"# Lost packets: %" PRIu64 "\n",
duration_hour, duration_minutes, ingress_stats.high_jitter);
}
printf("# Packet jitter histogram\n");
for (int j = min_jitter; j < max_jitter; j++)
printf(" %06d %015" PRIi64 "\n", j, jitter_hist[j]);
printf(
"# Middle: %d\n"
"# Duration: %dh%d\n"
"# Lost packets: %" PRIu64 "\n",
MAX_JITTER / 2 - min_jitter, duration_hour, duration_minutes,
ingress_stats.high_jitter);
}
static void sighand(int sig_num) {
(void)sig_num;
if (enable_histograms) {
print_histograms();
}
if (tsn_task == XDP_TASK) {
close_xdp_socket();
}
if (ingress_stats.high_jitter)
fprintf(stderr, "%d packets were lost\n", ((int)ingress_stats.high_jitter));
exit(EXIT_SUCCESS);
}
/* Process bash options
*/
static void process_options(int argc, char *argv[]) {
int network_if_specified = 0;
for (;;) {
int c = getopt(argc, argv, "a:b:cd:f:ghi:p:r:tvx:XT:GL");
if (c == -1) break;
switch (c) {
case 'a':
thread_params.affinity_cpu = atoi(optarg);
break;
case 'b':
tsn_task = RTT_TASK;
strcpy(egress_params.server_ip, optarg);
break;
case 'c':
thread_params.emit_signal = 1;
break;
case 'd':
ingress_params.tx_buffer_len = atoi(optarg);
if (ingress_params.tx_buffer_len < 1) {
fprintf(stderr, "BUF_LEN should be greater than 1\n");
exit(EXIT_FAILURE);
}
break;
case 'f':
network_if_specified = 1;
strcpy(ingress_params.network_if, optarg);
break;
case 'h':
help(argv);
exit(EXIT_SUCCESS);
break;
case 'i':
thread_params.interval = strtoull(optarg, NULL, 10) * 1000;
break;
case 'g':
enable_histograms = 1;
break;
case 'p':
thread_params.priority = atoi(optarg);
break;
case 'r':
main_params.refresh_rate = atoi(optarg);
break;
case 't':
enable_timestamps = 1;
break;
case 'v':
main_params.verbose = 1;
break;
case 'x':
tsn_task = XDP_TASK;
ingress_params.xdp_polling_mode = atoi(optarg);
break;
case 'X':
main_params.enable_xdp_tracing = 1;
break;
case 'T':
main_params.enable_tracing = 1;
thread_params.latency_threshold = atoi(optarg);
break;
case 'L':
log_timestamps = 1;
break;
}
}
if (!network_if_specified) {
fprintf(stderr, "You need to specifiy an network interface\n");
help(argv);
exit(EXIT_FAILURE);
}
if (argc != optind) {
fprintf(stderr, "Too many arguments\n");
help(argv);
exit(EXIT_FAILURE);
}
}
#include "tracer.h"
#define _GNU_SOURCE
#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <getopt.h>
#include <limits.h>
#include <linux/unistd.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/sysinfo.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <time.h>
#include <unistd.h>
/*
* From cyclictest code source
*/
#define KVARS 32
#define KVARNAMELEN 32
#define KVALUELEN 32
#define MAX_PATH 255
static char *fileprefix;
static char *procfileprefix = "/proc/sys/kernel/";
static char *debugfileprefix = "/sys/kernel/debug/tracing/";
static int trace_fd = -1;
static int tracemark_fd = -1;
static int kernvar(int mode, const char *name, char *value,
size_t sizeofvalue) {
char filename[128];
int retval = 1;
int path;
size_t len_prefix = strlen(fileprefix), len_name = strlen(name);
memcpy(filename, fileprefix, len_prefix);
memcpy(filename + len_prefix, name, len_name + 1);
path = open(filename, mode);
if (path >= 0) {
if (mode == O_RDONLY) {
int got;
if ((got = read(path, value, sizeofvalue)) > 0) {
retval = 0;
value[got - 1] = '\0';
}
} else if (mode == O_WRONLY) {
if (write(path, value, sizeofvalue) == sizeofvalue) retval = 0;
}
close(path);
}
return retval;
}
static void setkernvar(const char *name, char *value) {
if (kernvar(O_WRONLY, name, value, strlen(value)))
fprintf(stderr, "could not set %s to %s\n", name, value);
}
void open_fds(void) {
fileprefix = debugfileprefix;
char trace_path[MAX_PATH];
char tracemark_path[MAX_PATH];
// Open tracing_on file
strcpy(trace_path, fileprefix);
strcat(trace_path, "tracing_on");
if ((trace_fd = open(trace_path, O_WRONLY)) == -1)
printf("unable to open %s for tracing", trace_path);
// Open trace mark file
strcpy(tracemark_path, fileprefix);
strcat(tracemark_path, "trace_marker");
if ((tracemark_fd = open(tracemark_path, O_WRONLY)) == -1)
printf("unable to open %s for tracing", tracemark_path);
}
void setup_tracer(int enable_graph) {
fileprefix = procfileprefix;
setkernvar("ftrace_enabled", "1");
fileprefix = debugfileprefix;
// Clear old traces by setting tracer to nop first
setkernvar("current_tracer", "nop");
if(enable_graph)
setkernvar("current_tracer", "function_graph");
else
setkernvar("current_tracer", "function");
open_fds();
tracing(0);
}
void tracing(int on) {
if (on)
write(trace_fd, "1", 1);
else
write(trace_fd, "0", 1);
}
void tracemark(char * s) {
write(tracemark_fd, s, strlen(s));
}
#ifndef TRACER_H
#define TRACER_H
void setup_tracer(int enable_graph);
void open_fds(void);
void tracing(int on);
void tracemark(char * s);
#endif
#define KBUILD_MODNAME "blub"
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/if_link.h>
#include <linux/if_xdp.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include "bpf_helpers.h"
#define bpf_printk(fmt, ...) \
({ \
char ____fmt[] = fmt; \
bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \
})
struct bpf_map_def SEC("maps") xsks_map = {
.type = BPF_MAP_TYPE_XSKMAP,
.key_size = sizeof(int),
.value_size = sizeof(int),
.max_entries = 64,
};
static inline int parse_ipv4(void *data, unsigned long long nh_off,
void *data_end) {
struct iphdr *iph = data + nh_off;
if ((void *)(iph + 1) > data_end) return 0;
return iph->protocol;
}
SEC("xdp_sock")
int xdp_sock_prog(struct xdp_md *ctx) {
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
int idx = ctx->rx_queue_index;
unsigned int ipproto = 0;
unsigned long long nh_off;
/* Check if it's a UDP frame: If UDP -> Redirect to active xsk for user
* space. If not -> pass to stack.
*/
nh_off = sizeof(*eth);
if (data + nh_off > data_end) return XDP_PASS;
if (eth->h_proto == __builtin_bswap16(ETH_P_IP))
ipproto = parse_ipv4(data, nh_off, data_end);
if (ipproto != IPPROTO_UDP) return XDP_PASS;
/* If socket bound to rx_queue then redirect to user space */
if (bpf_map_lookup_elem(&xsks_map, &idx))
return bpf_redirect_map(&xsks_map, idx, 0);
/* Else pass to Linux' network stack */
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment