summaryrefslogtreecommitdiffstats
path: root/src/target/firmware/lib/console.c
blob: 7135ae25e25cd3ea1bf61954507941d4f17683fe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/* Ringbuffer based serial console layer, imported from OpenPCD */

/* (C) 2006-2010 by Harald Welte <laforge@gnumonks.org>
 *
 * All Rights Reserved
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */

#include <stdint.h>
#include <string.h>
#include <console.h>
#include <uart.h>

#include <asm/system.h>

struct cons {
	char buf[CONS_RB_SIZE];
	char *next_inbyte;
	char *next_outbyte;
	int initialized;
};
static struct cons cons;

void cons_init(void)
{
	memset(cons.buf, 0, sizeof(cons.buf));
	cons.next_inbyte = &cons.buf[0];
	cons.next_outbyte = &cons.buf[0];
	cons.initialized = 1;
}

/* determine how many bytes are left in the ringbuffer without overwriting
   bytes that haven't been written to the console yet */
static int __cons_rb_space(void)
{
	if (cons.next_inbyte == cons.next_outbyte)
		return sizeof(cons.buf)-1;
	else if (cons.next_outbyte > cons.next_inbyte)
		return (cons.next_outbyte - cons.next_inbyte) -1;
	else
		return sizeof(cons.buf) - 1 - (cons.next_inbyte - cons.next_outbyte);
}

/* pull one char out of debug ring buffer */
static int cons_rb_pull(char *ret)
{
	unsigned long flags;

	local_irq_save(flags);

	if (cons.next_outbyte == cons.next_inbyte) {
		local_irq_restore(flags);
		return -1;
	}

	*ret = *cons.next_outbyte;

	cons.next_outbyte++;
	if (cons.next_outbyte >= &cons.buf[0]+sizeof(cons.buf)) {
		cons.next_outbyte = &cons.buf[0];
	}
#if 0
	 else if (cons.next_outbyte > &cons.buf[0]+sizeof(cons.buf)) {
		cons.next_outbyte -= sizeof(cons.buf);
	}
#endif

	local_irq_restore(flags);

	return 0;
}

/* returns if everything was flushed (1) or if there's more to flush (0) */
static void __rb_flush_wait(void)
{
	char ch;
	while (cons_rb_pull(&ch) >= 0)
		uart_putchar_wait(CONS_UART_NR, ch);
}

/* returns if everything was flushed (1) or if there's more to flush (0) */
static int __rb_flush(void)
{
	while (!uart_tx_busy(CONS_UART_NR)) {
		char ch;
		if (cons_rb_pull(&ch) < 0) {
			/* no more data to write, disable interest in Tx FIFO interrupts */
			return 1;
		}
		uart_putchar_nb(CONS_UART_NR, ch);
	}

	/* if we reach here, UART Tx FIFO is busy again */
	return 0;
}

/* flush pending data from debug ring buffer to serial port */
int cons_rb_flush(void)
{
	return __rb_flush();
}

/* Append bytes to ring buffer, not more than we have left! */
static void __cons_rb_append(const char *data, int len)
{
	if (cons.next_inbyte + len >= &cons.buf[0]+sizeof(cons.buf)) {
		int before_tail = (&cons.buf[0]+sizeof(cons.buf)) - cons.next_inbyte;
		/* copy the first part before we wrap */
		memcpy(cons.next_inbyte, data, before_tail);
		data += before_tail;
		len -= before_tail;
		/* reset the buffer */
		cons.next_inbyte = &cons.buf[0];
	}
	memcpy(cons.next_inbyte, data, len);
	cons.next_inbyte += len;
}

/* append bytes to the ringbuffer, do one wrap */
int cons_rb_append(const char *data, int len)
{
	unsigned long flags;
	int bytes_left;
	const char *data_cur;

	/* we will never be able to write more than the console buffer */
	if (len > (int) sizeof(cons.buf))
		len = sizeof(cons.buf);

	local_irq_save(flags);

	bytes_left = __cons_rb_space();
	data_cur = data;

	if (len > bytes_left) {
		/* append what we can */
		__cons_rb_append(data_cur, bytes_left);
		/* busy-wait for all characters to be transmitted */
		__rb_flush_wait();
		/* fill it with the remaining bytes */
		len -= bytes_left;
		data_cur += bytes_left;
	}
	__cons_rb_append(data_cur, len);

	/* we want to get Tx FIFO interrupts */
	uart_irq_enable(CONS_UART_NR, UART_IRQ_TX_EMPTY, 1);

	local_irq_restore(flags);

	return len;
}

int cons_puts(const char *s)
{
	if (cons.initialized) {
		return cons_rb_append(s, strlen(s));
	} else {
		/* if the console is not active yet, we need to fall back */
		int i = strlen(s);
		while (i--)
			uart_putchar_wait(CONS_UART_NR, *s++);
		return i;
	}
}

int cons_putchar(char c)
{
	if (cons.initialized)
		return cons_rb_append(&c, 1);
	else {
		/* if the console is not active yet, we need to fall back */
		uart_putchar_wait(CONS_UART_NR, c);
		return 0;
	}
}