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