Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.15.
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
  4 */
  5
  6#include <dirent.h>
  7#include <stdarg.h>
  8#include <stdlib.h>
  9#include <string.h>
 10#include <unistd.h>
 11#include <ctype.h>
 12#include <errno.h>
 13#include <fcntl.h>
 14#include <sched.h>
 15#include <stdio.h>
 16
 17#include "utils.h"
 18
 19#define MAX_MSG_LENGTH	1024
 20int config_debug;
 21
 22/*
 23 * err_msg - print an error message to the stderr
 24 */
 25void err_msg(const char *fmt, ...)
 26{
 27	char message[MAX_MSG_LENGTH];
 28	va_list ap;
 29
 30	va_start(ap, fmt);
 31	vsnprintf(message, sizeof(message), fmt, ap);
 32	va_end(ap);
 33
 34	fprintf(stderr, "%s", message);
 35}
 36
 37/*
 38 * debug_msg - print a debug message to stderr if debug is set
 39 */
 40void debug_msg(const char *fmt, ...)
 41{
 42	char message[MAX_MSG_LENGTH];
 43	va_list ap;
 44
 45	if (!config_debug)
 46		return;
 47
 48	va_start(ap, fmt);
 49	vsnprintf(message, sizeof(message), fmt, ap);
 50	va_end(ap);
 51
 52	fprintf(stderr, "%s", message);
 53}
 54
 55/*
 56 * get_llong_from_str - get a long long int from a string
 57 */
 58long long get_llong_from_str(char *start)
 59{
 60	long long value;
 61	char *end;
 62
 63	errno = 0;
 64	value = strtoll(start, &end, 10);
 65	if (errno || start == end)
 66		return -1;
 67
 68	return value;
 69}
 70
 71/*
 72 * get_duration - fill output with a human readable duration since start_time
 73 */
 74void get_duration(time_t start_time, char *output, int output_size)
 75{
 76	time_t now = time(NULL);
 77	struct tm *tm_info;
 78	time_t duration;
 79
 80	duration = difftime(now, start_time);
 81	tm_info = gmtime(&duration);
 82
 83	snprintf(output, output_size, "%3d %02d:%02d:%02d",
 84			tm_info->tm_yday,
 85			tm_info->tm_hour,
 86			tm_info->tm_min,
 87			tm_info->tm_sec);
 88}
 89
 90/*
 91 * parse_cpu_list - parse a cpu_list filling a char vector with cpus set
 92 *
 93 * Receives a cpu list, like 1-3,5 (cpus 1, 2, 3, 5), and then set the char
 94 * in the monitored_cpus.
 95 *
 96 * XXX: convert to a bitmask.
 97 */
 98int parse_cpu_list(char *cpu_list, char **monitored_cpus)
 99{
100	char *mon_cpus;
101	const char *p;
102	int end_cpu;
103	int nr_cpus;
104	int cpu;
105	int i;
106
107	nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
108
109	mon_cpus = calloc(nr_cpus, sizeof(char));
110	if (!mon_cpus)
111		goto err;
112
113	for (p = cpu_list; *p; ) {
114		cpu = atoi(p);
115		if (cpu < 0 || (!cpu && *p != '0') || cpu >= nr_cpus)
116			goto err;
117
118		while (isdigit(*p))
119			p++;
120		if (*p == '-') {
121			p++;
122			end_cpu = atoi(p);
123			if (end_cpu < cpu || (!end_cpu && *p != '0') || end_cpu >= nr_cpus)
124				goto err;
125			while (isdigit(*p))
126				p++;
127		} else
128			end_cpu = cpu;
129
130		if (cpu == end_cpu) {
131			debug_msg("cpu_list: adding cpu %d\n", cpu);
132			mon_cpus[cpu] = 1;
133		} else {
134			for (i = cpu; i <= end_cpu; i++) {
135				debug_msg("cpu_list: adding cpu %d\n", i);
136				mon_cpus[i] = 1;
137			}
138		}
139
140		if (*p == ',')
141			p++;
142	}
143
144	*monitored_cpus = mon_cpus;
145
146	return 0;
147
148err:
149	debug_msg("Error parsing the cpu list %s", cpu_list);
150	return 1;
151}
152
153/*
154 * parse_duration - parse duration with s/m/h/d suffix converting it to seconds
155 */
156long parse_seconds_duration(char *val)
157{
158	char *end;
159	long t;
160
161	t = strtol(val, &end, 10);
162
163	if (end) {
164		switch (*end) {
165		case 's':
166		case 'S':
167			break;
168		case 'm':
169		case 'M':
170			t *= 60;
171			break;
172		case 'h':
173		case 'H':
174			t *= 60 * 60;
175			break;
176
177		case 'd':
178		case 'D':
179			t *= 24 * 60 * 60;
180			break;
181		}
182	}
183
184	return t;
185}
186
187/*
188 * parse_ns_duration - parse duration with ns/us/ms/s converting it to nanoseconds
189 */
190long parse_ns_duration(char *val)
191{
192	char *end;
193	long t;
194
195	t = strtol(val, &end, 10);
196
197	if (end) {
198		if (!strncmp(end, "ns", 2)) {
199			return t;
200		} else if (!strncmp(end, "us", 2)) {
201			t *= 1000;
202			return t;
203		} else if (!strncmp(end, "ms", 2)) {
204			t *= 1000 * 1000;
205			return t;
206		} else if (!strncmp(end, "s", 1)) {
207			t *= 1000 * 1000 * 1000;
208			return t;
209		}
210		return -1;
211	}
212
213	return t;
214}
215
216/*
217 * This is a set of helper functions to use SCHED_DEADLINE.
218 */
219#ifdef __x86_64__
220# define __NR_sched_setattr	314
221# define __NR_sched_getattr	315
222#elif __i386__
223# define __NR_sched_setattr	351
224# define __NR_sched_getattr	352
225#elif __arm__
226# define __NR_sched_setattr	380
227# define __NR_sched_getattr	381
228#elif __aarch64__ || __riscv
229# define __NR_sched_setattr	274
230# define __NR_sched_getattr	275
231#elif __powerpc__
232# define __NR_sched_setattr	355
233# define __NR_sched_getattr	356
234#elif __s390x__
235# define __NR_sched_setattr	345
236# define __NR_sched_getattr	346
237#endif
238
239#define SCHED_DEADLINE		6
240
241static inline int sched_setattr(pid_t pid, const struct sched_attr *attr,
242				unsigned int flags) {
243	return syscall(__NR_sched_setattr, pid, attr, flags);
244}
245
246static inline int sched_getattr(pid_t pid, struct sched_attr *attr,
247				unsigned int size, unsigned int flags)
248{
249	return syscall(__NR_sched_getattr, pid, attr, size, flags);
250}
251
252int __set_sched_attr(int pid, struct sched_attr *attr)
253{
254	int flags = 0;
255	int retval;
256
257	retval = sched_setattr(pid, attr, flags);
258	if (retval < 0) {
259		err_msg("Failed to set sched attributes to the pid %d: %s\n",
260			pid, strerror(errno));
261		return 1;
262	}
263
264	return 0;
265}
266
267/*
268 * procfs_is_workload_pid - check if a procfs entry contains a comm_prefix* comm
269 *
270 * Check if the procfs entry is a directory of a process, and then check if the
271 * process has a comm with the prefix set in char *comm_prefix. As the
272 * current users of this function only check for kernel threads, there is no
273 * need to check for the threads for the process.
274 *
275 * Return: True if the proc_entry contains a comm file with comm_prefix*.
276 * Otherwise returns false.
277 */
278static int procfs_is_workload_pid(const char *comm_prefix, struct dirent *proc_entry)
279{
280	char buffer[MAX_PATH];
281	int comm_fd, retval;
282	char *t_name;
283
284	if (proc_entry->d_type != DT_DIR)
285		return 0;
286
287	if (*proc_entry->d_name == '.')
288		return 0;
289
290	/* check if the string is a pid */
291	for (t_name = proc_entry->d_name; t_name; t_name++) {
292		if (!isdigit(*t_name))
293			break;
294	}
295
296	if (*t_name != '\0')
297		return 0;
298
299	snprintf(buffer, MAX_PATH, "/proc/%s/comm", proc_entry->d_name);
300	comm_fd = open(buffer, O_RDONLY);
301	if (comm_fd < 0)
302		return 0;
303
304	memset(buffer, 0, MAX_PATH);
305	retval = read(comm_fd, buffer, MAX_PATH);
306
307	close(comm_fd);
308
309	if (retval <= 0)
310		return 0;
311
312	retval = strncmp(comm_prefix, buffer, strlen(comm_prefix));
313	if (retval)
314		return 0;
315
316	/* comm already have \n */
317	debug_msg("Found workload pid:%s comm:%s", proc_entry->d_name, buffer);
318
319	return 1;
320}
321
322/*
323 * set_comm_sched_attr - set sched params to threads starting with char *comm_prefix
324 *
325 * This function uses procfs to list the currently running threads and then set the
326 * sched_attr *attr to the threads that start with char *comm_prefix. It is
327 * mainly used to set the priority to the kernel threads created by the
328 * tracers.
329 */
330int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr)
331{
332	struct dirent *proc_entry;
333	DIR *procfs;
334	int retval;
335
336	if (strlen(comm_prefix) >= MAX_PATH) {
337		err_msg("Command prefix is too long: %d < strlen(%s)\n",
338			MAX_PATH, comm_prefix);
339		return 1;
340	}
341
342	procfs = opendir("/proc");
343	if (!procfs) {
344		err_msg("Could not open procfs\n");
345		return 1;
346	}
347
348	while ((proc_entry = readdir(procfs))) {
349
350		retval = procfs_is_workload_pid(comm_prefix, proc_entry);
351		if (!retval)
352			continue;
353
354		/* procfs_is_workload_pid confirmed it is a pid */
355		retval = __set_sched_attr(atoi(proc_entry->d_name), attr);
356		if (retval) {
357			err_msg("Error setting sched attributes for pid:%s\n", proc_entry->d_name);
358			goto out_err;
359		}
360
361		debug_msg("Set sched attributes for pid:%s\n", proc_entry->d_name);
362	}
363	return 0;
364
365out_err:
366	closedir(procfs);
367	return 1;
368}
369
370#define INVALID_VAL	(~0L)
371static long get_long_ns_after_colon(char *start)
372{
373	long val = INVALID_VAL;
374
375	/* find the ":" */
376	start = strstr(start, ":");
377	if (!start)
378		return -1;
379
380	/* skip ":" */
381	start++;
382	val = parse_ns_duration(start);
383
384	return val;
385}
386
387static long get_long_after_colon(char *start)
388{
389	long val = INVALID_VAL;
390
391	/* find the ":" */
392	start = strstr(start, ":");
393	if (!start)
394		return -1;
395
396	/* skip ":" */
397	start++;
398	val = get_llong_from_str(start);
399
400	return val;
401}
402
403/*
404 * parse priority in the format:
405 * SCHED_OTHER:
406 *		o:<prio>
407 *		O:<prio>
408 * SCHED_RR:
409 *		r:<prio>
410 *		R:<prio>
411 * SCHED_FIFO:
412 *		f:<prio>
413 *		F:<prio>
414 * SCHED_DEADLINE:
415 *		d:runtime:period
416 *		D:runtime:period
417 */
418int parse_prio(char *arg, struct sched_attr *sched_param)
419{
420	long prio;
421	long runtime;
422	long period;
423
424	memset(sched_param, 0, sizeof(*sched_param));
425	sched_param->size = sizeof(*sched_param);
426
427	switch (arg[0]) {
428	case 'd':
429	case 'D':
430		/* d:runtime:period */
431		if (strlen(arg) < 4)
432			return -1;
433
434		runtime = get_long_ns_after_colon(arg);
435		if (runtime == INVALID_VAL)
436			return -1;
437
438		period = get_long_ns_after_colon(&arg[2]);
439		if (period == INVALID_VAL)
440			return -1;
441
442		if (runtime > period)
443			return -1;
444
445		sched_param->sched_policy   = SCHED_DEADLINE;
446		sched_param->sched_runtime  = runtime;
447		sched_param->sched_deadline = period;
448		sched_param->sched_period   = period;
449		break;
450	case 'f':
451	case 'F':
452		/* f:prio */
453		prio = get_long_after_colon(arg);
454		if (prio == INVALID_VAL)
455			return -1;
456
457		if (prio < sched_get_priority_min(SCHED_FIFO))
458			return -1;
459		if (prio > sched_get_priority_max(SCHED_FIFO))
460			return -1;
461
462		sched_param->sched_policy   = SCHED_FIFO;
463		sched_param->sched_priority = prio;
464		break;
465	case 'r':
466	case 'R':
467		/* r:prio */
468		prio = get_long_after_colon(arg);
469		if (prio == INVALID_VAL)
470			return -1;
471
472		if (prio < sched_get_priority_min(SCHED_RR))
473			return -1;
474		if (prio > sched_get_priority_max(SCHED_RR))
475			return -1;
476
477		sched_param->sched_policy   = SCHED_RR;
478		sched_param->sched_priority = prio;
479		break;
480	case 'o':
481	case 'O':
482		/* o:prio */
483		prio = get_long_after_colon(arg);
484		if (prio == INVALID_VAL)
485			return -1;
486
487		if (prio < sched_get_priority_min(SCHED_OTHER))
488			return -1;
489		if (prio > sched_get_priority_max(SCHED_OTHER))
490			return -1;
491
492		sched_param->sched_policy   = SCHED_OTHER;
493		sched_param->sched_priority = prio;
494		break;
495	default:
496		return -1;
497	}
498	return 0;
499}
500
501/*
502 * set_cpu_dma_latency - set the /dev/cpu_dma_latecy
503 *
504 * This is used to reduce the exit from idle latency. The value
505 * will be reset once the file descriptor of /dev/cpu_dma_latecy
506 * is closed.
507 *
508 * Return: the /dev/cpu_dma_latecy file descriptor
509 */
510int set_cpu_dma_latency(int32_t latency)
511{
512	int retval;
513	int fd;
514
515	fd = open("/dev/cpu_dma_latency", O_RDWR);
516	if (fd < 0) {
517		err_msg("Error opening /dev/cpu_dma_latency\n");
518		return -1;
519	}
520
521	retval = write(fd, &latency, 4);
522	if (retval < 1) {
523		err_msg("Error setting /dev/cpu_dma_latency\n");
524		close(fd);
525		return -1;
526	}
527
528	debug_msg("Set /dev/cpu_dma_latency to %d\n", latency);
529
530	return fd;
531}