Оглавление
§ Для чего порты
Сами по себе эти инструкции несложные, их немного: IN – ввод данных из порта от 0 до 65535 и OUT – вывод данных в порт от 0 до 65535. Стоит заметить, что скорость работы портов крайне низкая. Буквально 100 наносекунд тратится на то, чтобы записать или прочитать из порта. А 100 нс – это невероятно медленная скорость для современных компьютеров.
Порты предназначены для общения с внешним миром. Процессор – это такая вещь в себе, но ему нужно знать, что вокруг происходит.
§ Реализация инструкции
Сначала надо написать две функции, для ввода данных из порта и вывода в порт
uint8_t io_ports[65536];
uint8_t port_in(uint16_t port) {
return io_ports[port];
}
void port_out(uint16_t port, uint8_t data) {
io_ports[port] = data;
}
Функции пока что не заполнены, но будут заполняться по мере написания для них устройств. Главные устройства это конечно таймер, видеоадаптер, клавиатура. Реализовано будет далее. А сейчас надо реализовать инструкции и это сделать очень просто.
case 0xE4: case 0xE5:
case 0xEC: case 0xED:
i_tmp = opcode_id & 8 ? regs16[REG_DX] : fetch(1);
regs[REG_AL] = port_in(i_tmp);
if (opcode_id & 1) regs[REG_AH] = port_in(i_tmp);
break;
case 0xE6: case 0xE7:
case 0xEE: case 0xEF:
i_tmp = opcode_id & 8 ? regs16[REG_DX] : fetch(1);
port_out(i_tmp, regs[REG_AL]);
if (opcode_id & 1) port_out(i_tmp, regs[REG_AH]);
break;
§ Таймер
Первым делом запрограммирую таймер. Для этого придется добавить несколько новых строк в заголовочные файлы:
int io_hi_lo;
int irq8_prevtime;
int pit_frequency;
int pit_interval;
И выполнить их инициализацию
io_hi_lo = 0;
pit_frequency = 65536;
pit_interval = pit_frequency;
Переменная io_hi_lo принимает значение 0 – младший байт или 1 – старший байт, для записи в регистр pit_frequency, который является 16 битным и здесь записана скорость вызова таймера. При pit_frequency = 0 скорость таймера будет равна 18.2065 Гц (IRQ вызывается каждые 54.9254 с).
Также необходимо помнить, что существует программируемый контроллер прерываний (PIC), который тоже надо сделать, но пока сделаю все по-простому. Будем считать что таймер будет вызываться всегда, если есть IF=1. Просто дело в том, что можно заблокировать вызов IRQ 8 аппаратно.
void call_interrupt8() {
if (--pit_interval <= 0) {
ftime(&ms_clock);
pit_interval = pit_frequency;
int time_curr = ms_clock.millitm;
int time_diff = time_curr - irq8_prevtime;
if (time_diff < 0) time_diff += 1000;
int time_pit = pit_frequency / 1191;
if (time_diff >= time_pit) {
irq8_prevtime = time_curr;
interrupt(8);
}
}
}
Это вспомогательная процедура, которая вызывается на каждой инструкции при включенном IF=1. Вызывается проверка истечения времени таймера каждые pit_frequency раз, при этом используя pit_interval, который уменьшается с каждой инструкцией на 1. После того как pit_interval достиг 0, происходит его обновление и запуск процедуры проверки истечения времени. Берется текущее время, вычисляется разность между предыдущим временем запроса INT8 и если она равна pit_frequency / 1191, то тогда обновляется новое время и делается запрос на INT8.