#include "drivers/tty/ldisc.h" #include #include #include #include #include #include #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) { // NOT_YET_IMPLEMENTED("DRIVERS: ldisc_init"); // 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) { // NOT_YET_IMPLEMENTED("DRIVERS: ldisc_wait_read"); // if it's full, return 0 if (ldisc->ldisc_full) { return 0; } // while there are no need chars to be read, sleep // TODO: check if this is the right condition while ( (ldisc->ldisc_head == ldisc->ldisc_tail) || (ldisc->ldisc_head == ldisc->ldisc_cooked) ) { 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) { // NOT_YET_IMPLEMENTED("DRIVERS: ldisc_read"); // read from ldisc buffer to buf size_t i = 0; int break_loop = 0; while (i < count && !break_loop) { // if we have new chars to read if (ldisc->ldisc_head != ldisc->ldisc_tail) { char c = ldisc->ldisc_buffer[ldisc->ldisc_tail]; switch (c) { case EOT: buf[i] = c; 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; } } } // 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) { // NOT_YET_IMPLEMENTED("DRIVERS: ldisc_key_pressed"); // if the buffer is full, ignore the incoming char if (ldisc->ldisc_full) { return; } 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), "\b", 1); } break; case '\n': // emit a `\n` to the vterminal vterminal_write(ldisc_to_tty(ldisc), "\n", 1); // add the new char to the buffer ldisc->ldisc_buffer[ldisc->ldisc_tail] = c; 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); break; case EOT: // add the new char to the buffer ldisc->ldisc_buffer[ldisc->ldisc_head] = c; 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); break; case ETX: // clear uncooked portion of the line ldisc->ldisc_head = ldisc->ldisc_cooked; break; default: // if none applies, fallback to vterminal_key_pressed // vterminal_write(ldisc_to_tty(ldisc), &c, 1); ldisc->ldisc_buffer[ldisc->ldisc_head] = c; // update the buffer if it's full if ((ldisc->ldisc_head + 1) % LDISC_BUFFER_SIZE == ldisc->ldisc_tail) { ldisc->ldisc_full = 1; } else { ldisc->ldisc_head = (ldisc->ldisc_head + 1) % LDISC_BUFFER_SIZE; } 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) { // NOT_YET_IMPLEMENTED("DRIVERS: ldisc_get_current_line_raw"); // 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; }