1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright (c) 2011 The Chromium OS Authors.
4  */
5 
6 #include <command.h>
7 #include <console.h>
8 #include <env.h>
9 #include <malloc.h>
10 #include <vsprintf.h>
11 #include <uthread.h>
12 
13 /* Spawn arguments and job index  */
14 struct spa {
15 	int argc;
16 	char **argv;
17 	unsigned int job_idx;
18 };
19 
20 /*
21  * uthread group identifiers for each running job
22  * 0: job slot available, != 0: uthread group id
23  * Note that job[0] is job_id 1, job[1] is job_id 2 etc.
24  */
25 static unsigned int job[CONFIG_CMD_SPAWN_NUM_JOBS];
26 /* Return values of the commands run as jobs */
27 static enum command_ret_t job_ret[CONFIG_CMD_SPAWN_NUM_JOBS];
28 
spa_free(struct spa * spa)29 static void spa_free(struct spa *spa)
30 {
31 	int i;
32 
33 	if (!spa)
34 		return;
35 
36 	for (i = 0; i < spa->argc; i++)
37 		free(spa->argv[i]);
38 	free(spa->argv);
39 	free(spa);
40 }
41 
spa_create(int argc,char * const argv[])42 static struct spa *spa_create(int argc, char *const argv[])
43 {
44 	struct spa *spa;
45 	int i;
46 
47 	spa = calloc(1, sizeof(*spa));
48 	if (!spa)
49 		return NULL;
50 	spa->argc = argc;
51 	spa->argv = malloc(argc * sizeof(char *));
52 	if (!spa->argv)
53 		goto err;
54 	for (i = 0; i < argc; i++) {
55 		spa->argv[i] = strdup(argv[i]);
56 		if (!spa->argv[i])
57 			goto err;
58 	}
59 	return spa;
60 err:
61 	spa_free(spa);
62 	return NULL;
63 }
64 
spawn_thread(void * arg)65 static void spawn_thread(void *arg)
66 {
67 	struct spa *spa = (struct spa *)arg;
68 	ulong cycles = 0;
69 	int repeatable = 0;
70 
71 	job_ret[spa->job_idx] = cmd_process(0, spa->argc, spa->argv,
72 					    &repeatable, &cycles);
73 	spa_free(spa);
74 }
75 
next_job_id(void)76 static unsigned int next_job_id(void)
77 {
78 	int i;
79 
80 	for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++)
81 		if (!job[i])
82 			return i + 1;
83 
84 	/* No job available */
85 	return 0;
86 }
87 
refresh_jobs(void)88 static void refresh_jobs(void)
89 {
90 	int i;
91 
92 	for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++)
93 		if (job[i] && uthread_grp_done(job[i]))
94 			job[i] = 0;
95 
96 }
97 
do_spawn(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])98 static int do_spawn(struct cmd_tbl *cmdtp, int flag, int argc,
99 		    char *const argv[])
100 {
101 	unsigned int id;
102 	unsigned int idx;
103 	struct spa *spa;
104 	int ret;
105 
106 	if (argc == 1)
107 		return CMD_RET_USAGE;
108 
109 	spa = spa_create(argc - 1, argv + 1);
110 	if (!spa)
111 		return CMD_RET_FAILURE;
112 
113 	refresh_jobs();
114 
115 	id = next_job_id();
116 	if (!id)
117 		return CMD_RET_FAILURE;
118 	idx = id - 1;
119 
120 	job[idx] = uthread_grp_new_id();
121 
122 	ret = uthread_create(NULL, spawn_thread, spa, 0, job[idx]);
123 	if (ret) {
124 		job[idx] = 0;
125 		return CMD_RET_FAILURE;
126 	}
127 
128 	ret = env_set_ulong("job_id", id);
129 	if (ret)
130 		return CMD_RET_FAILURE;
131 
132 	return CMD_RET_SUCCESS;
133 }
134 
135 U_BOOT_CMD(spawn, CONFIG_SYS_MAXARGS, 0, do_spawn,
136 	   "run commands and summarize execution time",
137 	   "command [args...]\n");
138 
wait_job(unsigned int idx)139 static enum command_ret_t wait_job(unsigned int idx)
140 {
141 	int prev = disable_ctrlc(false);
142 
143 	while (!uthread_grp_done(job[idx])) {
144 		if (ctrlc()) {
145 			puts("<INTERRUPT>\n");
146 			disable_ctrlc(prev);
147 			return CMD_RET_FAILURE;
148 		}
149 		uthread_schedule();
150 	}
151 
152 	job[idx] = 0;
153 	disable_ctrlc(prev);
154 
155 	return job_ret[idx];
156 }
157 
do_wait(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])158 static int do_wait(struct cmd_tbl *cmdtp, int flag, int argc,
159 		   char *const argv[])
160 {
161 	enum command_ret_t ret = CMD_RET_SUCCESS;
162 	unsigned long id;
163 	unsigned int idx;
164 	int i;
165 
166 	if (argc == 1) {
167 		for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++)
168 			if (job[i])
169 				ret = wait_job(i);
170 	} else {
171 		for (i = 1; i < argc; i++) {
172 			id = dectoul(argv[i], NULL);
173 			if (id < 1 || id > CONFIG_CMD_SPAWN_NUM_JOBS)
174 				return CMD_RET_USAGE;
175 			idx = (int)id - 1;
176 			ret = wait_job(idx);
177 		}
178 	}
179 
180 	return ret;
181 }
182 
183 U_BOOT_CMD(wait, CONFIG_SYS_MAXARGS, 0, do_wait,
184 	   "wait for one or more jobs to complete",
185 	   "[job_id ...]\n"
186 	   "    - Wait until all specified jobs have exited and return the\n"
187 	   "      exit status of the last job waited for. When no job_id is\n"
188 	   "      given, wait for all the background jobs.\n");
189