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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
|
#include "drivers/tty/ldisc.h"
#include <drivers/keyboard.h>
#include <drivers/tty/tty.h>
#include <errno.h>
#include <util/bits.h>
#include <util/debug.h>
#include <util/string.h>
#define ldisc_to_tty(ldisc) CONTAINER_OF((ldisc), tty_t, tty_ldisc)
/**
* Initialize the line discipline. Don't forget to wipe the buffer associated
* with the line discipline clean.
*
* @param ldisc line discipline.
*/
void ldisc_init(ldisc_t *ldisc)
{
// reset static buffer
memset(ldisc->ldisc_buffer, 0, LDISC_BUFFER_SIZE);
// init the queue
sched_queue_init(&ldisc->ldisc_read_queue);
// fill in other fields
ldisc->ldisc_head = 0;
ldisc->ldisc_tail = 0;
ldisc->ldisc_cooked = 0;
ldisc->ldisc_full = 0; // 0 not full
}
/**
* While there are no new characters to be read from the line discipline's
* buffer, you should make the current thread to sleep on the line discipline's
* read queue. Note that this sleep can be cancelled. What conditions must be met
* for there to be no characters to be read?
*
* @param ldisc the line discipline
* @param lock the lock associated with `ldisc`
* @return 0 if there are new characters to be read or the ldisc is full.
* If the sleep was interrupted, return what
* `sched_cancellable_sleep_on` returned (i.e. -EINTR)
*/
long ldisc_wait_read(ldisc_t *ldisc)
{
// while there are no need chars to be read, sleep
while (
ldisc->ldisc_head == ldisc->ldisc_tail
)
{
long ret = sched_cancellable_sleep_on(&ldisc->ldisc_read_queue);
if (ret != 0)
{
return ret;
}
}
// if we are here, it means we have new chars to read
return 0;
}
/**
* Reads `count` bytes (at max) from the line discipline's buffer into the
* provided buffer. Keep in mind the the ldisc's buffer is circular.
*
* If you encounter a new line symbol before you have read `count` bytes, you
* should stop copying and return the bytes read until now.
*
* If you encounter an `EOT` you should stop reading and you should NOT include
* the `EOT` in the count of the number of bytes read
*
* @param ldisc the line discipline
* @param buf the buffer to read into.
* @param count the maximum number of bytes to read from ldisc.
* @return the number of bytes read from the ldisc.
*/
size_t ldisc_read(ldisc_t *ldisc, char *buf, size_t count)
{
dbg(DBG_DISK, "ldisc_read: count = %d\n", count);
// read from ldisc buffer to buf
size_t i = 0;
int break_loop = 0;
while (i < count && !break_loop)
{
// if we have no new chars to read, break loop
if (ldisc->ldisc_head == ldisc->ldisc_tail)
{
break_loop = 1;
continue;
}
char c = ldisc->ldisc_buffer[ldisc->ldisc_tail];
dbg(DBG_DISK, "ldisc_read: %c\n", c);
switch (c)
{
case EOT:
buf[i] = c;
ldisc->ldisc_tail = (ldisc->ldisc_tail + 1) % LDISC_BUFFER_SIZE;
break_loop = 1;
break;
// case ETX:
// ldisc->ldisc_tail = ldisc->ldisc_cooked;
// break;
case '\n':
buf[i] = c;
ldisc->ldisc_tail = (ldisc->ldisc_tail + 1) % LDISC_BUFFER_SIZE;
i++;
break_loop = 1;
break;
default:
buf[i] = c;
ldisc->ldisc_tail = (ldisc->ldisc_tail + 1) % LDISC_BUFFER_SIZE;
i++;
break;
}
dbg(DBG_DISK, "ldisc_read: i = %d\n", i);
}
// return the number of bytes read
return i;
}
/**
* Place the character received into the ldisc's buffer. You should also update
* relevant fields of the struct.
*
* An easier way of handling new characters is making sure that you always have
* one byte left in the line discipline. This way, if the new character you
* received is a new line symbol (user hit enter), you can still place the new
* line symbol into the buffer; if the new character is not a new line symbol,
* you shouldn't place it into the buffer so that you can leave the space for
* a new line symbol in the future.
*
* If the line discipline is full, all incoming characters should be ignored.
*
* Here are some special cases to consider:
* 1. If the character is a backspace:
* * if there is a character to remove you must also emit a `\b` to
* the vterminal.
* 2. If the character is end of transmission (EOT) character (typing ctrl-d)
* 3. If the character is end of text (ETX) character (typing ctrl-c)
* 4. If your buffer is almost full and what you received is not a new line
* symbol
*
* If you did receive a new line symbol, you should wake up the thread that is
* sleeping on the wait queue of the line discipline. You should also
* emit a `\n` to the vterminal by using `vterminal_write`.
*
* If you encounter the `EOT` character, you should add it to the buffer,
* cook the buffer, and wake up the reader (but do not emit an `\n` character
* to the vterminal)
*
* In case of `ETX` you should cause the input line to be effectively transformed
* into a cooked blank line. You should clear uncooked portion of the line, by
* adjusting ldisc_head.
*
* Finally, if the none of the above cases apply you should fallback to
* `vterminal_key_pressed`.
*
* Don't forget to write the corresponding characters to the virtual terminal
* when it applies!
*
* @param ldisc the line discipline
* @param c the new character
*/
void ldisc_key_pressed(ldisc_t *ldisc, char c)
{
switch (c)
{
case '\b':
// if there is a character to remove
if (ldisc->ldisc_head != ldisc->ldisc_tail)
{
// remove the last char
ldisc->ldisc_head = (ldisc->ldisc_head - 1 + LDISC_BUFFER_SIZE) % LDISC_BUFFER_SIZE;
// emit a `\b` to the vterminal
vterminal_write(&ldisc_to_tty(ldisc)->tty_vterminal, "\b", 1);
if (ldisc->ldisc_full)
{
ldisc->ldisc_full = 0;
}
}
break;
case '\n':
// add the new char to the buffer
ldisc->ldisc_buffer[ldisc->ldisc_head] = c;
if (!ldisc->ldisc_full)
{
ldisc->ldisc_head = (ldisc->ldisc_head + 1) % LDISC_BUFFER_SIZE;
}
// cook the buffer
ldisc->ldisc_cooked = ldisc->ldisc_head;
sched_wakeup_on(&ldisc->ldisc_read_queue, 0);
// emit a `\n` to the vterminal
vterminal_write(&ldisc_to_tty(ldisc)->tty_vterminal, "\n", 1);
if (ldisc->ldisc_full)
{
ldisc->ldisc_full = 0;
}
break;
case EOT:
// add the new char to the buffer
dbg(DBG_DISK, "EOT\n");
ldisc->ldisc_buffer[ldisc->ldisc_head] = c;
if (!ldisc->ldisc_full)
{
ldisc->ldisc_head = (ldisc->ldisc_head + 1) % LDISC_BUFFER_SIZE;
}
// cook the buffer
ldisc->ldisc_cooked = ldisc->ldisc_head;
// wake up the thread that is sleeping on the wait queue of the line discipline
sched_wakeup_on(&ldisc->ldisc_read_queue, 0);
vterminal_write(&ldisc_to_tty(ldisc)->tty_vterminal, "\n", 1);
if (ldisc->ldisc_full)
{
ldisc->ldisc_full = 0;
}
break;
case ETX:
// clear uncooked portion of the line
dbg(DBG_DISK, "ETX\n");
ldisc->ldisc_head = (ldisc->ldisc_cooked + 1) % LDISC_BUFFER_SIZE;
ldisc->ldisc_cooked = ldisc->ldisc_head;
// wake up the thread that is sleeping on the wait queue of the line discipline
sched_wakeup_on(&ldisc->ldisc_read_queue, 0);
// emit a `\n` to the vterminal
vterminal_write(&ldisc_to_tty(ldisc)->tty_vterminal, "\n", 1);
break;
default:
// if the buffer is full, ignore the incoming char
// if (ldisc->ldisc_full)
// {
// return;
// }
// if none applies, fallback to vterminal_key_pressed
// vterminal_write(ldisc_to_tty(ldisc), &c, 1);
// update the buffer if it's not full
if (!ldisc->ldisc_full)
{
ldisc->ldisc_buffer[ldisc->ldisc_head] = c;
ldisc->ldisc_head = (ldisc->ldisc_head + 1) % LDISC_BUFFER_SIZE;
if (ldisc->ldisc_head + 1 == ldisc->ldisc_tail)
{
ldisc->ldisc_full = 1;
}
vterminal_key_pressed(&ldisc_to_tty(ldisc)->tty_vterminal);
}
break;
}
}
/**
* Copy the raw part of the line discipline buffer into the buffer provided.
*
* @param ldisc the line discipline
* @param s the character buffer to write to
* @return the number of bytes copied
*/
size_t ldisc_get_current_line_raw(ldisc_t *ldisc, char *s)
{
// copy the raw part of the line discipline buffer into the buffer provided
size_t i = 0;
size_t j = ldisc->ldisc_cooked;
while (j != ldisc->ldisc_head)
{
s[i] = ldisc->ldisc_buffer[j];
j = (j + 1) % LDISC_BUFFER_SIZE;
i++;
}
// return the number of bytes copied
return i;
}
|