1 // SPDX-License-Identifier: BSD-2-Clause
2 /*
3  * Copyright (c) 2017, Linaro Limited
4  */
5 
6 #include <compiler.h>
7 #include <console.h>
8 #include <drivers/cbmem_console.h>
9 #include <drivers/semihosting_console.h>
10 #include <drivers/ffa_console.h>
11 #include <drivers/serial.h>
12 #include <kernel/dt.h>
13 #include <kernel/dt_driver.h>
14 #include <kernel/panic.h>
15 #include <libfdt.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <string_ext.h>
19 
20 static struct serial_chip *serial_console __nex_bss;
21 
22 /* May be overridden by platform */
plat_console_init(void)23 __weak void plat_console_init(void)
24 {
25 }
26 
console_init(void)27 void console_init(void)
28 {
29 	if (IS_ENABLED(CFG_SEMIHOSTING_CONSOLE))
30 		semihosting_console_init(CFG_SEMIHOSTING_CONSOLE_FILE);
31 	else if (IS_ENABLED(CFG_FFA_CONSOLE))
32 		ffa_console_init();
33 	else
34 		plat_console_init();
35 }
36 
console_putc(int ch)37 void __weak console_putc(int ch)
38 {
39 	if (!serial_console)
40 		return;
41 
42 	if (ch == '\n')
43 		serial_console->ops->putc(serial_console, '\r');
44 	serial_console->ops->putc(serial_console, ch);
45 }
46 
console_flush(void)47 void __weak console_flush(void)
48 {
49 	if (!serial_console || !serial_console->ops->flush)
50 		return;
51 
52 	serial_console->ops->flush(serial_console);
53 }
54 
register_serial_console(struct serial_chip * chip)55 void register_serial_console(struct serial_chip *chip)
56 {
57 	serial_console = chip;
58 }
59 
60 #ifdef CFG_DT
find_chosen_node(void * fdt)61 static int find_chosen_node(void *fdt)
62 {
63 	int offset = 0;
64 
65 	if (!fdt)
66 		return -1;
67 
68 	offset = fdt_path_offset(fdt, "/secure-chosen");
69 
70 	if (offset < 0)
71 		offset = fdt_path_offset(fdt, "/chosen");
72 
73 	return offset;
74 }
75 
get_console_node_from_dt(void * fdt,int * offs_out,char ** path_out,char ** params_out)76 TEE_Result get_console_node_from_dt(void *fdt, int *offs_out,
77 				    char **path_out, char **params_out)
78 {
79 	const struct fdt_property *prop;
80 	const char *uart;
81 	const char *parms = NULL;
82 	int offs;
83 	char *stdout_data;
84 	char *p;
85 	TEE_Result rc = TEE_ERROR_GENERIC;
86 
87 	/* Probe console from secure DT and fallback to non-secure DT */
88 	offs = find_chosen_node(fdt);
89 	if (offs < 0) {
90 		DMSG("No console directive from DTB");
91 		return TEE_ERROR_ITEM_NOT_FOUND;
92 	}
93 
94 	prop = fdt_get_property(fdt, offs, "stdout-path", NULL);
95 	if (!prop) {
96 		/*
97 		 * A secure-chosen or chosen node is present but defined
98 		 * no stdout-path property: no console expected
99 		 */
100 		IMSG("Switching off console");
101 		register_serial_console(NULL);
102 		return TEE_ERROR_ITEM_NOT_FOUND;
103 	}
104 
105 	stdout_data = nex_strdup(prop->data);
106 	if (!stdout_data)
107 		panic();
108 	p = strchr(stdout_data, ':');
109 	if (p) {
110 		*p = '\0';
111 		parms = p + 1;
112 	}
113 
114 	/* stdout-path may refer to an alias */
115 	uart = fdt_get_alias(fdt, stdout_data);
116 	if (!uart) {
117 		/* Not an alias, assume we have a node path */
118 		uart = stdout_data;
119 	}
120 	offs = fdt_path_offset(fdt, uart);
121 	if (offs >= 0) {
122 		if (offs_out)
123 			*offs_out = offs;
124 		if (params_out)
125 			*params_out = parms ? nex_strdup(parms) : NULL;
126 		if (path_out)
127 			*path_out = uart ? nex_strdup(uart) : NULL;
128 
129 		rc = TEE_SUCCESS;
130 	}
131 
132 	nex_free(stdout_data);
133 
134 	return rc;
135 }
136 
configure_console_from_dt(void)137 void configure_console_from_dt(void)
138 {
139 	const struct dt_driver *dt_drv;
140 	const struct serial_driver *sdrv;
141 	struct serial_chip *dev;
142 	char *uart = NULL;
143 	char *parms = NULL;
144 	void *fdt;
145 	int offs;
146 
147 	fdt = get_dt();
148 
149 	if (IS_ENABLED(CFG_CBMEM_CONSOLE) && cbmem_console_init_from_dt(fdt))
150 		return;
151 
152 	if (get_console_node_from_dt(fdt, &offs, &uart, &parms))
153 		return;
154 
155 	dt_drv = dt_find_compatible_driver(fdt, offs);
156 	if (!dt_drv || dt_drv->type != DT_DRIVER_UART)
157 		goto out;
158 
159 	sdrv = (const struct serial_driver *)dt_drv->driver;
160 	if (!sdrv)
161 		goto out;
162 
163 	dev = sdrv->dev_alloc();
164 	if (!dev)
165 		goto out;
166 
167 	/*
168 	 * If the console is the same as the early console, dev_init() might
169 	 * clear pending data. Flush to avoid that.
170 	 */
171 	console_flush();
172 	if (sdrv->dev_init(dev, fdt, offs, parms) < 0) {
173 		sdrv->dev_free(dev);
174 		goto out;
175 	}
176 
177 	IMSG("Switching console to device: %s", uart);
178 	register_serial_console(dev);
179 out:
180 	nex_free(uart);
181 	nex_free(parms);
182 }
183 
184 #endif /* CFG_DT */
185