1 //
2 // Copyright (c) 2010-2024 Antmicro
3 //
4 // This file is licensed under MIT License.
5 // Full license text is available in 'licenses/MIT.txt' file.
6 //
7 #include <errno.h>
8 #include <stdbool.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 
13 #include "renode_api.h"
14 
15 #define LINE_BUFFER_SIZE 86
16 
get_error_message(renode_error_t * error)17 char *get_error_message(renode_error_t *error)
18 {
19     if (error->message == NULL)
20     {
21         return "<no message>";
22     }
23     return error->message;
24 }
25 
26 #ifdef LOG_PERF
27 #include <time.h>
28 
29 static int tsi;
30 static struct timespec t[2];
31 
perf_start()32 static void perf_start()
33 {
34     clock_gettime(CLOCK_REALTIME, &t[tsi]);
35 }
36 
perf_stop(uint64_t times,uint64_t value,renode_time_unit_t time_unit)37 static void perf_stop(uint64_t times, uint64_t value, renode_time_unit_t time_unit)
38 {
39     tsi ^= 1;
40     clock_gettime(CLOCK_REALTIME, &t[tsi]);
41 
42     int64_t seconds = t[tsi].tv_sec - t[tsi ^ 1].tv_sec;
43     int64_t nanoseconds = t[tsi].tv_nsec - t[tsi ^ 1].tv_nsec;
44     if(nanoseconds < 0)
45     {
46         seconds -= 1;
47         nanoseconds += 1000000000;
48     }
49     int64_t microseconds = (nanoseconds + 500) / 1000;
50 
51     int64_t virt_seconds = value * times / (time_unit == TU_SECONDS ? 1 : (time_unit == TU_MICROSECONDS ? 1000000 : 1000));
52     int64_t virt_microseconds = (value * times) % (time_unit == TU_SECONDS ? 1 : (time_unit == TU_MICROSECONDS ? 1000000 : 1000));
53 
54     fprintf(stderr, "delta: %lu.%06lus real\t %lu.%06lus virt\t real / virt %lf\n", seconds, microseconds, virt_seconds, virt_microseconds, (seconds * 1e6 + microseconds) / (virt_seconds * 1e6 + virt_microseconds));
55 }
56 
start_virtual_time_check(renode_t * renode)57 static void start_virtual_time_check(renode_t *renode)
58 {
59     (void)renode;
60 }
61 
stop_virtual_time_check(renode_t * renode,uint64_t value,renode_time_unit_t time_unit)62 static void stop_virtual_time_check(renode_t *renode, uint64_t value, renode_time_unit_t time_unit)
63 {
64     (void)renode;
65     (void)value;
66     (void)time_unit;
67 }
68 
69 #else // !LOG_PERF
70 
perf_start()71 static void perf_start() {}
72 
perf_stop(uint64_t times,uint64_t value,renode_time_unit_t time_unit)73 static void perf_stop(uint64_t times, uint64_t value, renode_time_unit_t time_unit)
74 {
75     (void)times;
76     (void)value;
77     (void)time_unit;
78 }
79 
80 static uint64_t vt0;
81 
get_current_virtual_time(renode_t * renode)82 static uint64_t get_current_virtual_time(renode_t *renode)
83 {
84     renode_error_t *error;
85     uint64_t current_time;
86     if ((error = renode_get_current_time(renode, TU_MICROSECONDS, &current_time)) != NO_ERROR) {
87         fprintf(stderr, "Get current time failed with: %s\n", get_error_message(error));
88         renode_free_error(error);
89         exit(EXIT_FAILURE);
90     }
91 
92     return current_time;
93 }
94 
start_virtual_time_check(renode_t * renode)95 static void start_virtual_time_check(renode_t *renode)
96 {
97     vt0 = get_current_virtual_time(renode);
98 }
99 
stop_virtual_time_check(renode_t * renode,uint64_t value,renode_time_unit_t time_unit)100 static void stop_virtual_time_check(renode_t *renode, uint64_t value, renode_time_unit_t time_unit)
101 {
102     uint64_t vt1 = get_current_virtual_time(renode);
103     uint64_t vt_delta = vt1 - vt0;
104 
105     int microseconds = vt1 % TU_SECONDS;
106     int seconds_total = vt1 / TU_SECONDS;
107     int seconds = seconds_total % 60;
108     int minutes_total = seconds_total / 60;
109     int minutes = minutes_total % 60;
110     int hours_total = minutes_total / 60;
111 
112     printf("Elapsed virtual time %02d:%02d:%02d.%06d\n", hours_total, minutes, seconds, microseconds);
113 
114     if (vt_delta != value * time_unit) {
115         fprintf(stderr, "Reported current virtual time doesn't match the expected virtual time after running for the requested interval\n");
116         exit(EXIT_FAILURE);
117     }
118 }
119 
120 #endif
121 
exit_with_usage_info(const char * argv0)122 void exit_with_usage_info(const char *argv0)
123 {
124     fprintf(stderr,
125         "Usage:\n"
126         "  %s <PORT> <VALUE_WITH_UNIT> [<REPEAT>]\n"
127         "  where:\n"
128         "  * <VALUE_WITH_UNIT> is an integer with a time unit, e.g.: '100ms'\n"
129         "  * accepted time units are 's', 'ms' and 'us' (for microseconds)\n"
130         "  * <REPEAT> is an optional number of times (default: 1) to run\n"
131         "  * the simulation for\n",
132         argv0);
133     exit(EXIT_FAILURE);
134 }
135 
read_time_value(char * buffer,char ** endptr,uint64_t * value,renode_time_unit_t * time_unit)136 bool read_time_value(char *buffer, char **endptr, uint64_t *value, renode_time_unit_t *time_unit)
137 {
138     char *noendptr;
139     if (endptr == NULL) {
140         endptr = &noendptr;
141     }
142     // base=0 tries to figure out the number's base automatically.
143     uint64_t new_value = strtoull(buffer, endptr, /* base: */ 0);
144     if (errno != 0) {
145         perror("conversion to uint64_t value");
146         exit(EXIT_FAILURE);
147     }
148 
149     if (*endptr == buffer) {
150         return false;
151     }
152 
153     renode_time_unit_t unit = -1;
154     switch (**endptr) {
155         case 'u':
156             unit = TU_MICROSECONDS;
157             *endptr += 2;
158             break;
159         case 'm':
160             unit = TU_MILLISECONDS;
161             *endptr += 2;
162             break;
163         case 's':
164             unit = TU_SECONDS;
165             *endptr += 1;
166             break;
167         default:
168             return false;
169     }
170 
171     *value = new_value;
172     *time_unit = unit;
173     return true;
174 }
175 
run_options_prompt(uint64_t * value,renode_time_unit_t * time_unit,uint64_t * times)176 void run_options_prompt(uint64_t *value, renode_time_unit_t *time_unit, uint64_t *times)
177 {
178     char option;
179     bool retry;
180 
181     do {
182         retry = false;
183         printf("Continue running for %lu%ss %lu times? [y/N/c] ", *value, *time_unit == TU_MICROSECONDS ? "u" : (*time_unit == TU_MILLISECONDS ? "m" : ""), *times);
184 
185         if ((option = getchar()) != '\n') while(getchar() != '\n');
186 
187         switch (option)
188         {
189         case 'y':
190         case 'Y':
191             return;
192         case 'c':
193         case 'C':
194             printf("Enter new value: ");
195             {
196                 char buffer[LINE_BUFFER_SIZE];
197                 char *endptr;
198 
199                 if (fgets(buffer, LINE_BUFFER_SIZE, stdin) != buffer) {
200                     fprintf(stderr, "Failed to read line\n");
201                     retry = true;
202                     break;
203                 }
204 
205                 size_t len = strlen(buffer);
206                 if (len == LINE_BUFFER_SIZE - 1 && buffer[len - 1] != '\n') {
207                     fprintf(stderr, "Failed to read line: line too long\n");
208                     retry = true;
209                     break;
210                 }
211 
212                 errno = 0;
213                 if (!read_time_value(buffer, &endptr, value, time_unit)) {
214                     fprintf(stderr, "Failed to parse time value\n");
215                     retry = true;
216                     break;
217                 }
218 
219                 if (*endptr != '\n' && *endptr != '\0')
220                 {
221                     *times = strtoull(endptr, NULL, /* base: */ 0);
222                     if (errno != 0) {
223                         perror("conversion to uint64_t value");
224                         exit(EXIT_FAILURE);
225                     }
226                 }
227                 else
228                 {
229                     *times = 1;
230                 }
231             }
232             return;
233         case '\n':
234         case 'n':
235         case 'N':
236             *times = 0;
237             return;
238         default:
239             retry = true;
240             break;
241         }
242 
243     } while(retry);
244 }
245 
main(int argc,char ** argv)246 int main(int argc, char **argv)
247 {
248     if (argc < 3 || 4 < argc) {
249         exit_with_usage_info(argv[0]);
250     }
251 
252     uint64_t value;
253     renode_time_unit_t time_unit;
254     if (!read_time_value(argv[2], NULL, &value, &time_unit)) {
255         exit_with_usage_info(argv[0]);
256     }
257 
258     uint64_t times = 1;
259     if (argc == 4)
260     {
261         times = strtoull(argv[3], NULL, /* base: */ 0);
262         if (errno != 0) {
263             perror("conversion to uint64_t value");
264             exit(EXIT_FAILURE);
265         }
266     }
267 
268     renode_error_t *error;
269     renode_t *renode;
270     if ((error = renode_connect(argv[1], &renode)) != NO_ERROR) {
271         fprintf(stderr, "Connecting to Renode failed with: %s\n", get_error_message(error));
272         renode_free_error(error);
273         exit(EXIT_FAILURE);
274     }
275 
276     perf_start();
277 
278     uint64_t i = times;
279     while(i > 0)
280     {
281         start_virtual_time_check(renode);
282 
283         if ((error = renode_run_for(renode, time_unit, value)) != NO_ERROR) {
284             fprintf(stderr, "Run for failed with: %s\n", get_error_message(error));
285             renode_free_error(error);
286             exit(EXIT_FAILURE);
287         }
288 
289         stop_virtual_time_check(renode, value, time_unit);
290 
291         i -= 1;
292         if (i == 0)
293         {
294             perf_stop(times, value, time_unit);
295             run_options_prompt(&value, &time_unit, &times);
296             i = times;
297             perf_start();
298         }
299     }
300 
301     if ((error = renode_disconnect(&renode)) != NO_ERROR) {
302         fprintf(stderr, "Disconnecting from Renode failed with: %s\n", get_error_message(error));
303         renode_free_error(error);
304         exit(EXIT_FAILURE);
305     }
306 
307     exit(EXIT_SUCCESS);
308 }
309