1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * ACPI AML interfacing userspace utility
4 *
5 * Copyright (C) 2015, Intel Corporation
6 * Authors: Lv Zheng <lv.zheng@intel.com>
7 */
8
9#include <acpi/acpi.h>
10
11/* Headers not included by include/acpi/platform/aclinux.h */
12#include <unistd.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16#include <error.h>
17#include <stdbool.h>
18#include <fcntl.h>
19#include <assert.h>
20#include <sys/select.h>
21#include "../../../../../include/linux/circ_buf.h"
22
23#define ACPI_AML_FILE		"/sys/kernel/debug/acpi/acpidbg"
24#define ACPI_AML_SEC_TICK	1
25#define ACPI_AML_USEC_PEEK	200
26#define ACPI_AML_BUF_SIZE	4096
27
28#define ACPI_AML_BATCH_WRITE_CMD	0x00 /* Write command to kernel */
29#define ACPI_AML_BATCH_READ_LOG		0x01 /* Read log from kernel */
30#define ACPI_AML_BATCH_WRITE_LOG	0x02 /* Write log to console */
31
32#define ACPI_AML_LOG_START		0x00
33#define ACPI_AML_PROMPT_START		0x01
34#define ACPI_AML_PROMPT_STOP		0x02
35#define ACPI_AML_LOG_STOP		0x03
36#define ACPI_AML_PROMPT_ROLL		0x04
37
38#define ACPI_AML_INTERACTIVE	0x00
39#define ACPI_AML_BATCH		0x01
40
41#define circ_count(circ) \
42	(CIRC_CNT((circ)->head, (circ)->tail, ACPI_AML_BUF_SIZE))
43#define circ_count_to_end(circ) \
44	(CIRC_CNT_TO_END((circ)->head, (circ)->tail, ACPI_AML_BUF_SIZE))
45#define circ_space(circ) \
46	(CIRC_SPACE((circ)->head, (circ)->tail, ACPI_AML_BUF_SIZE))
47#define circ_space_to_end(circ) \
48	(CIRC_SPACE_TO_END((circ)->head, (circ)->tail, ACPI_AML_BUF_SIZE))
49
50#define acpi_aml_cmd_count()	circ_count(&acpi_aml_cmd_crc)
51#define acpi_aml_log_count()	circ_count(&acpi_aml_log_crc)
52#define acpi_aml_cmd_space()	circ_space(&acpi_aml_cmd_crc)
53#define acpi_aml_log_space()	circ_space(&acpi_aml_log_crc)
54
55#define ACPI_AML_DO(_fd, _op, _buf, _ret)				\
56	do {								\
57		_ret = acpi_aml_##_op(_fd, &acpi_aml_##_buf##_crc);	\
58		if (_ret == 0) {					\
59			fprintf(stderr,					\
60				"%s %s pipe closed.\n", #_buf, #_op);	\
61			return;						\
62		}							\
63	} while (0)
64#define ACPI_AML_BATCH_DO(_fd, _op, _buf, _ret)				\
65	do {								\
66		_ret = acpi_aml_##_op##_batch_##_buf(_fd,		\
67			 &acpi_aml_##_buf##_crc);			\
68		if (_ret == 0)						\
69			return;						\
70	} while (0)
71
72
73static char acpi_aml_cmd_buf[ACPI_AML_BUF_SIZE];
74static char acpi_aml_log_buf[ACPI_AML_BUF_SIZE];
75static struct circ_buf acpi_aml_cmd_crc = {
76	.buf = acpi_aml_cmd_buf,
77	.head = 0,
78	.tail = 0,
79};
80static struct circ_buf acpi_aml_log_crc = {
81	.buf = acpi_aml_log_buf,
82	.head = 0,
83	.tail = 0,
84};
85static const char *acpi_aml_file_path = ACPI_AML_FILE;
86static unsigned long acpi_aml_mode = ACPI_AML_INTERACTIVE;
87static bool acpi_aml_exit;
88
89static bool acpi_aml_batch_drain;
90static unsigned long acpi_aml_batch_state;
91static char acpi_aml_batch_prompt;
92static char acpi_aml_batch_roll;
93static unsigned long acpi_aml_log_state;
94static char *acpi_aml_batch_cmd = NULL;
95static char *acpi_aml_batch_pos = NULL;
96
97static int acpi_aml_set_fl(int fd, int flags)
98{
99	int ret;
100
101	ret = fcntl(fd, F_GETFL, 0);
102	if (ret < 0) {
103		perror("fcntl(F_GETFL)");
104		return ret;
105	}
106	flags |= ret;
107	ret = fcntl(fd, F_SETFL, flags);
108	if (ret < 0) {
109		perror("fcntl(F_SETFL)");
110		return ret;
111	}
112	return ret;
113}
114
115static int acpi_aml_set_fd(int fd, int maxfd, fd_set *set)
116{
117	if (fd > maxfd)
118		maxfd = fd;
119	FD_SET(fd, set);
120	return maxfd;
121}
122
123static int acpi_aml_read(int fd, struct circ_buf *crc)
124{
125	char *p;
126	int len;
127
128	p = &crc->buf[crc->head];
129	len = circ_space_to_end(crc);
130	len = read(fd, p, len);
131	if (len < 0)
132		perror("read");
133	else if (len > 0)
134		crc->head = (crc->head + len) & (ACPI_AML_BUF_SIZE - 1);
135	return len;
136}
137
138static int acpi_aml_read_batch_cmd(int unused, struct circ_buf *crc)
139{
140	char *p;
141	int len;
142	int remained = strlen(acpi_aml_batch_pos);
143
144	p = &crc->buf[crc->head];
145	len = circ_space_to_end(crc);
146	if (len > remained) {
147		memcpy(p, acpi_aml_batch_pos, remained);
148		acpi_aml_batch_pos += remained;
149		len = remained;
150	} else {
151		memcpy(p, acpi_aml_batch_pos, len);
152		acpi_aml_batch_pos += len;
153	}
154	if (len > 0)
155		crc->head = (crc->head + len) & (ACPI_AML_BUF_SIZE - 1);
156	return len;
157}
158
159static int acpi_aml_read_batch_log(int fd, struct circ_buf *crc)
160{
161	char *p;
162	int len;
163	int ret = 0;
164
165	p = &crc->buf[crc->head];
166	len = circ_space_to_end(crc);
167	while (ret < len && acpi_aml_log_state != ACPI_AML_LOG_STOP) {
168		if (acpi_aml_log_state == ACPI_AML_PROMPT_ROLL) {
169			*p = acpi_aml_batch_roll;
170			len = 1;
171			crc->head = (crc->head + 1) & (ACPI_AML_BUF_SIZE - 1);
172			ret += 1;
173			acpi_aml_log_state = ACPI_AML_LOG_START;
174		} else {
175			len = read(fd, p, 1);
176			if (len <= 0) {
177				if (len < 0)
178					perror("read");
179				ret = len;
180				break;
181			}
182		}
183		switch (acpi_aml_log_state) {
184		case ACPI_AML_LOG_START:
185			if (*p == '\n')
186				acpi_aml_log_state = ACPI_AML_PROMPT_START;
187			crc->head = (crc->head + 1) & (ACPI_AML_BUF_SIZE - 1);
188			ret += 1;
189			break;
190		case ACPI_AML_PROMPT_START:
191			if (*p == ACPI_DEBUGGER_COMMAND_PROMPT ||
192			    *p == ACPI_DEBUGGER_EXECUTE_PROMPT) {
193				acpi_aml_batch_prompt = *p;
194				acpi_aml_log_state = ACPI_AML_PROMPT_STOP;
195			} else {
196				if (*p != '\n')
197					acpi_aml_log_state = ACPI_AML_LOG_START;
198				crc->head = (crc->head + 1) & (ACPI_AML_BUF_SIZE - 1);
199				ret += 1;
200			}
201			break;
202		case ACPI_AML_PROMPT_STOP:
203			if (*p == ' ') {
204				acpi_aml_log_state = ACPI_AML_LOG_STOP;
205				acpi_aml_exit = true;
206			} else {
207				/* Roll back */
208				acpi_aml_log_state = ACPI_AML_PROMPT_ROLL;
209				acpi_aml_batch_roll = *p;
210				*p = acpi_aml_batch_prompt;
211				crc->head = (crc->head + 1) & (ACPI_AML_BUF_SIZE - 1);
212				ret += 1;
213			}
214			break;
215		default:
216			assert(0);
217			break;
218		}
219	}
220	return ret;
221}
222
223static int acpi_aml_write(int fd, struct circ_buf *crc)
224{
225	char *p;
226	int len;
227
228	p = &crc->buf[crc->tail];
229	len = circ_count_to_end(crc);
230	len = write(fd, p, len);
231	if (len < 0)
232		perror("write");
233	else if (len > 0)
234		crc->tail = (crc->tail + len) & (ACPI_AML_BUF_SIZE - 1);
235	return len;
236}
237
238static int acpi_aml_write_batch_log(int fd, struct circ_buf *crc)
239{
240	char *p;
241	int len;
242
243	p = &crc->buf[crc->tail];
244	len = circ_count_to_end(crc);
245	if (!acpi_aml_batch_drain) {
246		len = write(fd, p, len);
247		if (len < 0)
248			perror("write");
249	}
250	if (len > 0)
251		crc->tail = (crc->tail + len) & (ACPI_AML_BUF_SIZE - 1);
252	return len;
253}
254
255static int acpi_aml_write_batch_cmd(int fd, struct circ_buf *crc)
256{
257	int len;
258
259	len = acpi_aml_write(fd, crc);
260	if (circ_count_to_end(crc) == 0)
261		acpi_aml_batch_state = ACPI_AML_BATCH_READ_LOG;
262	return len;
263}
264
265static void acpi_aml_loop(int fd)
266{
267	fd_set rfds;
268	fd_set wfds;
269	struct timeval tv;
270	int ret;
271	int maxfd = 0;
272
273	if (acpi_aml_mode == ACPI_AML_BATCH) {
274		acpi_aml_log_state = ACPI_AML_LOG_START;
275		acpi_aml_batch_pos = acpi_aml_batch_cmd;
276		if (acpi_aml_batch_drain)
277			acpi_aml_batch_state = ACPI_AML_BATCH_READ_LOG;
278		else
279			acpi_aml_batch_state = ACPI_AML_BATCH_WRITE_CMD;
280	}
281	acpi_aml_exit = false;
282	while (!acpi_aml_exit) {
283		tv.tv_sec = ACPI_AML_SEC_TICK;
284		tv.tv_usec = 0;
285		FD_ZERO(&rfds);
286		FD_ZERO(&wfds);
287
288		if (acpi_aml_cmd_space()) {
289			if (acpi_aml_mode == ACPI_AML_INTERACTIVE)
290				maxfd = acpi_aml_set_fd(STDIN_FILENO, maxfd, &rfds);
291			else if (strlen(acpi_aml_batch_pos) &&
292				 acpi_aml_batch_state == ACPI_AML_BATCH_WRITE_CMD)
293				ACPI_AML_BATCH_DO(STDIN_FILENO, read, cmd, ret);
294		}
295		if (acpi_aml_cmd_count() &&
296		    (acpi_aml_mode == ACPI_AML_INTERACTIVE ||
297		     acpi_aml_batch_state == ACPI_AML_BATCH_WRITE_CMD))
298			maxfd = acpi_aml_set_fd(fd, maxfd, &wfds);
299		if (acpi_aml_log_space() &&
300		    (acpi_aml_mode == ACPI_AML_INTERACTIVE ||
301		     acpi_aml_batch_state == ACPI_AML_BATCH_READ_LOG))
302			maxfd = acpi_aml_set_fd(fd, maxfd, &rfds);
303		if (acpi_aml_log_count())
304			maxfd = acpi_aml_set_fd(STDOUT_FILENO, maxfd, &wfds);
305
306		ret = select(maxfd+1, &rfds, &wfds, NULL, &tv);
307		if (ret < 0) {
308			perror("select");
309			break;
310		}
311		if (ret > 0) {
312			if (FD_ISSET(STDIN_FILENO, &rfds))
313				ACPI_AML_DO(STDIN_FILENO, read, cmd, ret);
314			if (FD_ISSET(fd, &wfds)) {
315				if (acpi_aml_mode == ACPI_AML_BATCH)
316					ACPI_AML_BATCH_DO(fd, write, cmd, ret);
317				else
318					ACPI_AML_DO(fd, write, cmd, ret);
319			}
320			if (FD_ISSET(fd, &rfds)) {
321				if (acpi_aml_mode == ACPI_AML_BATCH)
322					ACPI_AML_BATCH_DO(fd, read, log, ret);
323				else
324					ACPI_AML_DO(fd, read, log, ret);
325			}
326			if (FD_ISSET(STDOUT_FILENO, &wfds)) {
327				if (acpi_aml_mode == ACPI_AML_BATCH)
328					ACPI_AML_BATCH_DO(STDOUT_FILENO, write, log, ret);
329				else
330					ACPI_AML_DO(STDOUT_FILENO, write, log, ret);
331			}
332		}
333	}
334}
335
336static bool acpi_aml_readable(int fd)
337{
338	fd_set rfds;
339	struct timeval tv;
340	int ret;
341	int maxfd = 0;
342
343	tv.tv_sec = 0;
344	tv.tv_usec = ACPI_AML_USEC_PEEK;
345	FD_ZERO(&rfds);
346	maxfd = acpi_aml_set_fd(fd, maxfd, &rfds);
347	ret = select(maxfd+1, &rfds, NULL, NULL, &tv);
348	if (ret < 0)
349		perror("select");
350	if (ret > 0 && FD_ISSET(fd, &rfds))
351		return true;
352	return false;
353}
354
355/*
356 * This is a userspace IO flush implementation, replying on the prompt
357 * characters and can be turned into a flush() call after kernel implements
358 * .flush() filesystem operation.
359 */
360static void acpi_aml_flush(int fd)
361{
362	while (acpi_aml_readable(fd)) {
363		acpi_aml_batch_drain = true;
364		acpi_aml_loop(fd);
365		acpi_aml_batch_drain = false;
366	}
367}
368
369void usage(FILE *file, char *progname)
370{
371	fprintf(file, "usage: %s [-b cmd] [-f file] [-h]\n", progname);
372	fprintf(file, "\nOptions:\n");
373	fprintf(file, "  -b     Specify command to be executed in batch mode\n");
374	fprintf(file, "  -f     Specify interface file other than");
375	fprintf(file, "         /sys/kernel/debug/acpi/acpidbg\n");
376	fprintf(file, "  -h     Print this help message\n");
377}
378
379int main(int argc, char **argv)
380{
381	int fd = -1;
382	int ch;
383	int len;
384	int ret = EXIT_SUCCESS;
385
386	while ((ch = getopt(argc, argv, "b:f:h")) != -1) {
387		switch (ch) {
388		case 'b':
389			if (acpi_aml_batch_cmd) {
390				fprintf(stderr, "Already specify %s\n",
391					acpi_aml_batch_cmd);
392				ret = EXIT_FAILURE;
393				goto exit;
394			}
395			len = strlen(optarg);
396			acpi_aml_batch_cmd = calloc(len + 2, 1);
397			if (!acpi_aml_batch_cmd) {
398				perror("calloc");
399				ret = EXIT_FAILURE;
400				goto exit;
401			}
402			memcpy(acpi_aml_batch_cmd, optarg, len);
403			acpi_aml_batch_cmd[len] = '\n';
404			acpi_aml_mode = ACPI_AML_BATCH;
405			break;
406		case 'f':
407			acpi_aml_file_path = optarg;
408			break;
409		case 'h':
410			usage(stdout, argv[0]);
411			goto exit;
412			break;
413		case '?':
414		default:
415			usage(stderr, argv[0]);
416			ret = EXIT_FAILURE;
417			goto exit;
418			break;
419		}
420	}
421
422	fd = open(acpi_aml_file_path, O_RDWR | O_NONBLOCK);
423	if (fd < 0) {
424		perror("open");
425		ret = EXIT_FAILURE;
426		goto exit;
427	}
428	acpi_aml_set_fl(STDIN_FILENO, O_NONBLOCK);
429	acpi_aml_set_fl(STDOUT_FILENO, O_NONBLOCK);
430
431	if (acpi_aml_mode == ACPI_AML_BATCH)
432		acpi_aml_flush(fd);
433	acpi_aml_loop(fd);
434
435exit:
436	if (fd >= 0)
437		close(fd);
438	if (acpi_aml_batch_cmd)
439		free(acpi_aml_batch_cmd);
440	return ret;
441}
442