OSDev: Почему прокрутка моего VGA-терминала не работает? - PullRequest
0 голосов
/ 03 ноября 2018

Я разрабатываю операционную систему как личное хобби, чтобы я мог узнать о программной инженерии и компьютерной архитектуре.

Я пытаюсь заставить VGA-терминал «прокручиваться», когда текст достигает нижнего края, или VGA_HEIGHT. Я использую код из вики OSDev, смешанный с моим собственным кодом.

Моя цель - скопировать каждую строку, а затем записать ее в строку чуть выше.

Вот код, который я использую:

void terminal_putentryat(unsigned char c, uint8_t color, size_t x, size_t y) {
    const size_t index = y * VGA_WIDTH + x;
    terminal_buffer[index] = vga_entry(c, color);
}

void terminal_putchar(char c) {
    unsigned char uc = c;

    switch(c) {
      case NEWLINE:
        terminal_row++;
        terminal_column = 0;
        terminal_putentryat(' ', terminal_color, terminal_column, terminal_row);
        update_cursor(terminal_column + 1, terminal_row);
        break;

      case '\t':
        /* TODO: Implement tab */
        terminal_column += 4;
        break;

      default:
        terminal_putentryat(uc, terminal_color, terminal_column, terminal_row);
        update_cursor(terminal_column + 1, terminal_row);
        if (++terminal_column == VGA_WIDTH) {
          terminal_column = 0;
          if (++terminal_row == VGA_HEIGHT)
            terminal_row = 0;
        }
    }
    if(terminal_row >= VGA_HEIGHT) {
      terminal_print_error();
      terminal_buffer[(15 * VGA_WIDTH) + 15] = terminal_buffer[(0 * VGA_WIDTH) + 4];
      size_t i, j;
      for(i = 0; i < VGA_WIDTH-1; i++) {
        for(j = VGA_HEIGHT-2; j > 0; j--)
          terminal_buffer[(j * VGA_WIDTH) + i] = terminal_buffer[((j+1) * VGA_WIDTH) + i];
      }
    }
}

Но эта функция работает только частично. В частности, этот раздел:

if(terminal_row >= VGA_HEIGHT) {
      terminal_print_error();
      terminal_buffer[(15 * VGA_WIDTH) + 15] = terminal_buffer[(0 * VGA_WIDTH) + 4];
      size_t i, j;
      for(i = 0; i < VGA_WIDTH-1; i++) {
        for(j = VGA_HEIGHT-2; j > 0; j--)
          terminal_buffer[(j * VGA_WIDTH) + i] = terminal_buffer[((j+1) * VGA_WIDTH) + i];
      }
    }

Копирует данные только частично. Например, когда я пишу в терминал с помощью printf (), если строка длиннее прокручиваемых данных, она не будет прокручиваться.

/*
 * This is the screen driver. It contains functions which print
 * characters and colors to the screen
 * using the VGA controller.
 */

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include <kernel/tty.h>

#include "vga.h"

#define REG_SCREEN_CTRL 0x3D4
#define REG_SCREEN_DATA 0x3D5

#define NEWLINE 0x0A
#define TAB 0x09

static const size_t VGA_WIDTH = 80;
static const size_t VGA_HEIGHT = 25;
static uint16_t *const VGA_MEMORY = (uint16_t *)0xC03FF000;

static size_t terminal_row;
static size_t terminal_column;
static uint8_t terminal_color;
static uint16_t *terminal_buffer;

int get_offset(int col, int row) {
  return 2 * (row * VGA_WIDTH + col);
}

int get_offset_row(int offset) {
  return offset / (2 * VGA_WIDTH);
}

int get_offset_col(int offset) {
  return (offset - (get_offset_row(offset) * 2 * VGA_WIDTH)) / 2;
}

static void scroll() {
  if(terminal_row >= VGA_HEIGHT) {

  }
}
void terminal_print_error(void) {
  if(terminal_row >= VGA_HEIGHT) {
    terminal_row = 0;
    /* print white/red E to bottom right corner of screen */
    terminal_putentryat('E', vga_entry_color(VGA_COLOR_RED, VGA_COLOR_WHITE),
                        VGA_WIDTH - 1, VGA_HEIGHT - 1);
  }
}

void terminal_initialize(void) {
    terminal_row = 0;
    terminal_column = 0;
    terminal_color = vga_entry_color(VGA_COLOR_BLACK, VGA_COLOR_CYAN);
    terminal_buffer = VGA_MEMORY;
    for (size_t y = 0; y < VGA_HEIGHT; y++) {
        for (size_t x = 0; x < VGA_WIDTH; x++) {
            const size_t index = y * VGA_WIDTH + x;
            terminal_buffer[index] = vga_entry(' ', terminal_color);
        }
    }
}

void terminal_setcolor(uint8_t color) {
    terminal_color = color;
}

void terminal_putentryat(unsigned char c, uint8_t color, size_t x, size_t y) {
    const size_t index = y * VGA_WIDTH + x;
    terminal_buffer[index] = vga_entry(c, color);
}

void terminal_putchar(char c) {
    unsigned char uc = c;

    switch(c) {
      case NEWLINE:
        terminal_row++;
        terminal_column = 0;
        terminal_putentryat(' ', terminal_color, terminal_column, terminal_row);
        update_cursor(terminal_column + 1, terminal_row);
        break;

      case '\t':
        /* TODO: Implement tab */
        terminal_column += 4;
        break;

      default:
        terminal_putentryat(uc, terminal_color, terminal_column, terminal_row);
        update_cursor(terminal_column + 1, terminal_row);
        if (++terminal_column == VGA_WIDTH) {
          terminal_column = 0;
          if (++terminal_row == VGA_HEIGHT)
            terminal_row = 0;
        }
    }
    if(terminal_row >= VGA_HEIGHT) {
      terminal_print_error();
      terminal_buffer[(15 * VGA_WIDTH) + 15] = terminal_buffer[(0 * VGA_WIDTH) + 4];
      size_t i, j;
      for(i = 0; i < VGA_WIDTH-1; i++) {
        for(j = VGA_HEIGHT-2; j > 0; j--)
          terminal_buffer[(j * VGA_WIDTH) + i] = terminal_buffer[((j+1) * VGA_WIDTH) + i];
      }
    }
}

void terminal_write(const char *data, size_t size) {
    for (size_t i = 0; i < size; i++)
        terminal_putchar(data[i]);
}

void terminal_writestring(const char *data) {
    terminal_write(data, strlen(data));
}

/* inb */
unsigned char port_byte_in(unsigned short port) {
  unsigned char result;
  asm ("in %%dx, %%al" : "=a" (result) : "d" (port));
  return result;
}

void port_byte_out(unsigned short port, unsigned char data) {
  asm ("out %%al, %%dx" : : "a" (data), "d" (port));
}

int get_cursor_offset() {
  /* Use the VGA ports to get the current cursor position
   * 1. Ask for high byte of the cursor offset (data 14)
   * 2. Ask for low byte (data 15)
   */
  port_byte_out(REG_SCREEN_CTRL, 14);
  int offset = port_byte_in(REG_SCREEN_DATA) << 8; /* High byte: << 8 */
  port_byte_out(REG_SCREEN_CTRL, 15);
  offset += port_byte_in(REG_SCREEN_DATA);
  return offset * 2; /* Position * size of character cell */
}

void set_cursor_offset(int offset) {
  /* Similar to get_cursor_offset, but instead of reading we write data */
  offset /= 2;
  port_byte_out(REG_SCREEN_CTRL, 14);
  port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset >> 8));
  port_byte_out(REG_SCREEN_CTRL, 15);
  port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset & 0xff));
}

void update_cursor(int x, int y) {
  uint16_t pos = y * VGA_WIDTH + x;

  port_byte_out(REG_SCREEN_CTRL, 15);
  port_byte_out(REG_SCREEN_DATA, (uint8_t)(pos & 0xFF));
  port_byte_out(REG_SCREEN_CTRL, 14);
  port_byte_out(REG_SCREEN_DATA, (uint8_t)(pos >> 8) & 0xFF);
}

/*
void enable_cursor(uint8_t cursor_start, uint8_t cursor_end) {
  outb(0x3D4, 0x0A);
  outb(0x3D, (inb(0x3D5) & 0xC0) | cursor_start);

  outb(0x3D4, 0x0B);
  outb(0x3D5, (inb(0x3D5) & 0xE0) | cursor_end);
}
*/

1 Ответ

0 голосов
/ 03 ноября 2018

Я считаю, что циклическая прокрутка работает в основном , однако исправьте большую часть кода, прежде чем он вызовет некоторые ошибки, с которыми вы столкнулись.

Случай по умолчанию в вашем операторе переключателя увеличивает строку курсора, когда ширина курсора выходит за правый край экрана. if (++terminal_row == VGA_HEIGHT) затем сбрасывает строку курсора на 0, если приращение строки проходит за нижний край экрана. Это предотвращает запуск кода прокрутки. Вы должны удалить if (++terminal_row == VGA_HEIGHT) terminal_row = 0; и заменить просто terminal_row++;, поскольку логика, следующая непосредственно за вашим переключателем, обрабатывает переменную строки терминала.

Я бы рекомендовал отделить логику, которая модифицирует terminal_row и terminal_column от логики, которая проверяет, сбрасывает и прокручивает эти переменные. Например, ваша обработка символа '\ t', если он помещен в последние 3 символа строки, приведет к переполнению символов на следующей строке без обновления переменных terminal_row и terminal_column до того места, где они должны быть.

  • Ваш символ новой строки всегда будет оставлять пустой символ в начале строки, потому что вы делаете terminal_putentryat после изменения курсора на новую строку, а не до. На самом деле, вам не нужно было делать terminal_putentryat для любой новой строки, потому что видимые символы не меняются, только положение курсора.
  • Возможно, вы захотите изменить обработку \t для вызова terminal_write(' '); вместо непосредственного изменения переменной столбца. Это упрощает логику, которая фактически обновляет ваш терминал. Во втором абзаце подробно описаны некоторые проблемы, которые устраняет это изменение.
  • update_cursor() следует вызывать только один раз, в конце terminal_putchar(), поскольку каждый введенный вами символ должен обновлять курсор. Это может измениться, если вы хотите, чтобы terminal_putchar() обрабатывал символы шириной 0, но мне это кажется нелогичным, поскольку эта функция специально предназначена для обработки отображаемых символов.
  • Цикл изменения буфера терминала вверх для прокрутки никогда не очищает нижний ряд символов
  • Логика внизу вашей функции для обработки terminal_row >= VGA_HEIGHT никогда не сбрасывает значение Terminal_row в допустимое значение. Он вызывает terminal_print_error(), но эта функция сбрасывает вашу строку в 0, если вы хотите сохранить строку в нижней части.

    void terminal_putchar(char c) {
        unsigned char uc = c;
    
        // Handle character output and terminal_row/column modification
        switch(c) {
          case NEWLINE:
            terminal_row++;
            terminal_column = 0;
            break;
    
          case '\t':
            terminal_write('    ');
            break;
    
          default:
            terminal_putentryat(uc, terminal_color, terminal_column, terminal_row);
            terminal_column++;
        }
    
        // Handle validation on terminal_column before terminal_row, since the logic in terminal_column can update terminal_row
        if(terminal_column >= VGA_WIDTH) {
              terminal_column = 0;
              terminal_row++;
        }
    
        // Handle validating terminal_row, and scrolling the screen upwards if necessary.
        if(terminal_row >= VGA_HEIGHT) {
            // You shouldn't need terminal_print_error() since you are handling the case where terminal_row >= VGA_HEIGHT
            // terminal_print_error();
    
            // What does this line do? Appears to set the 16th character of the 16th row to the same value as the 5th character of the 1st row.
            // terminal_buffer[(15 * VGA_WIDTH) + 15] = terminal_buffer[(0 * VGA_WIDTH) + 4];
    
            size_t i, j;
            for(i = 0; i < VGA_WIDTH-1; i++) {
                for(j = VGA_HEIGHT-2; j > 0; j--) {
                    terminal_buffer[(j * VGA_WIDTH) + i] = terminal_buffer[((j+1) * VGA_WIDTH) + i];
                }               
            }
    
            // Also clear out the bottom row
            for(i = 0; i < VGA_WIDTH-1; i++) {
                terminal_putentryat(' ', terminal_color, i, VGA_HEIGHT-1);
            }
    
            terminal_row = VGA_HEIGHT-1;
        }
    
        update_cursor(terminal_column, terminal_row);
    }
    
...