输入输出
Bios的全称:Basic Input/Output System
设备也有自己的状态寄存器,也有自己的功能部件,控制设备工作的信号称为"命令字", 可以理解成"设备的指令", 设备的工作就是负责接收命令字, 并进行译码和执行,访问设备 = 读出数据 + 写入数据 + 控制状态.
设备与CPU的接口到底是什么?设备的寄存器
Am中设备的寄存器是什么?
在abstract-machine\am\src\platform\nemu\ioe\ioe.c文件中存在:
typedef void (*handler_t)(void *buf);
static void *lut[128] = {
[AM_TIMER_CONFIG] = __am_timer_config,
[AM_TIMER_RTC ] = __am_timer_rtc,
[AM_TIMER_UPTIME] = __am_timer_uptime,
[AM_INPUT_CONFIG] = __am_input_config,
[AM_INPUT_KEYBRD] = __am_input_keybrd,
[AM_GPU_CONFIG ] = __am_gpu_config,
[AM_GPU_FBDRAW ] = __am_gpu_fbdraw,
[AM_GPU_STATUS ] = __am_gpu_status,
[AM_UART_CONFIG ] = __am_uart_config,
[AM_AUDIO_CONFIG] = __am_audio_config,
[AM_AUDIO_CTRL ] = __am_audio_ctrl,
[AM_AUDIO_STATUS] = __am_audio_status,
[AM_AUDIO_PLAY ] = __am_audio_play,
[AM_DISK_CONFIG ] = __am_disk_config,
[AM_DISK_STATUS ] = __am_disk_status,
[AM_DISK_BLKIO ] = __am_disk_blkio,
[AM_NET_CONFIG ] = __am_net_config,
};
代表着已经存在的设备寄存器,每个设备寄存器都维护着属于自己一个单独的类,在文件\abstract-machine\am\include\amdev.h中
#define AM_DEVREG(id, reg, perm, ...) \
enum { AM_##reg = (id) }; \
typedef struct { __VA_ARGS__; } AM_##reg##_T;
AM_DEVREG(1, UART_CONFIG, RD, bool present);
AM_DEVREG(2, UART_TX, WR, char data);
AM_DEVREG(3, UART_RX, RD, char data);
AM_DEVREG(4, TIMER_CONFIG, RD, bool present, has_rtc);
AM_DEVREG(5, TIMER_RTC, RD, int year, month, day, hour, minute, second);
AM_DEVREG(6, TIMER_UPTIME, RD, uint64_t us);
AM_DEVREG(7, INPUT_CONFIG, RD, bool present);
AM_DEVREG(8, INPUT_KEYBRD, RD, bool keydown; int keycode);
AM_DEVREG(9, GPU_CONFIG, RD, bool present, has_accel; int width, height, vmemsz);
AM_DEVREG(10, GPU_STATUS, RD, bool ready);
AM_DEVREG(11, GPU_FBDRAW, WR, int x, y; void* pixels; int w, h; bool sync);
AM_DEVREG(12, GPU_MEMCPY, WR, uint32_t dest; void* src; int size);
AM_DEVREG(13, GPU_RENDER, WR, uint32_t root);
AM_DEVREG(14, AUDIO_CONFIG, RD, bool present; int bufsize);
AM_DEVREG(15, AUDIO_CTRL, WR, int freq, channels, samples);
AM_DEVREG(16, AUDIO_STATUS, RD, int count);
AM_DEVREG(17, AUDIO_PLAY, WR, Area buf);
AM_DEVREG(18, DISK_CONFIG, RD, bool present; int blksz, blkcnt);
AM_DEVREG(19, DISK_STATUS, RD, bool ready);
AM_DEVREG(20, DISK_BLKIO, WR, bool write; void* buf; int blkno, blkcnt);
AM_DEVREG(21, NET_CONFIG, RD, bool present);
AM_DEVREG(22, NET_STATUS, RD, int rx_len, tx_len);
AM_DEVREG(23, NET_TX, WR, Area buf);
AM_DEVREG(24, NET_RX, WR, Area buf);
每个设备拥有的寄存器数量不同,例如串口拥有UART_CONFIG、UART_TX、UART_RX三个不同的寄存器
设备的寄存器如何去更新
这些设备的信息,本质上是通过设备本身的运算单元去更新维护的,在nemu中是这样进行的。
时钟是如何实现的:
uint64_t us = get_time();
键盘更新是如何实现的:
#ifndef CONFIG_TARGET_AM
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
nemu_state.state = NEMU_QUIT;
break;
#ifdef CONFIG_HAS_KEYBOARD
// If a key was pressed
case SDL_KEYDOWN:
case SDL_KEYUP: {
uint8_t k = event.key.keysym.scancode;
bool is_keydown = (event.key.type == SDL_KEYDOWN);
send_key(k, is_keydown);
break;
}
#endif
default: break;
}
}
#endif
}
上述是按键的实现过程,我是完成了PA3之后回来看这段代码,发现原来如此,自己在navy-app中实现的mini-SDL
是不是和这个很相似,当时就在想为什么我要将一个输入输出封装这么多次。
VGA是如何实现的:
static void init_screen() {
SDL_Window* window = NULL;
char title[128];
sprintf(title, "%s-NEMU", str(__GUEST_ISA__));
SDL_Init(SDL_INIT_VIDEO);
SDL_CreateWindowAndRenderer(
SCREEN_W * (MUXDEF(CONFIG_VGA_SIZE_400x300, 2, 1)),
SCREEN_H * (MUXDEF(CONFIG_VGA_SIZE_400x300, 2, 1)),
0, &window, &renderer);
SDL_SetWindowTitle(window, title);
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STATIC, SCREEN_W, SCREEN_H);
}
static inline void update_screen() {
SDL_UpdateTexture(texture, NULL, vmem, SCREEN_W * sizeof(uint32_t));
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
}
通过上述SDL内置库实现,随后通过AM对vmem对进行写操作就能够实现设备的更新。
串口是如何实现的
在AM中我们对serial_bal这个地址进行写操作就可以
进行相应操作之后,设备的值是如何更新的呢
键盘与GPU的值是通过nemu中update_device进行更新的,
CPU的内存映射与端口映射
再次刨析device代码
#include <SDL2/SDL.h>
引入SDL库,SDL库是什么?为什么能够帮助实现这么多外设的建立:
SDL库是Simple DirectMedia Layer的缩写,简单直接的多媒体层,不仅包括图像处理,音频处理,输入输出,还支持多线程和事件的开发,而且SDL是跨平台的。因为SDL开源性质,所以非常多的应用都是用SDL作为底层。
我们可以将SDL理解为,SDL自己维护了一套各种设备的驱动模块,我们只需要利用SDL进行使用即可。
mmio_map代码剖析
void add_mmio_map(const char *name, paddr_t addr, void *space, uint32_t len, io_callback_t callback) {
assert(nr_map < NR_MAP);
maps[nr_map] = (IOMap){ .name = name, .low = addr, .high = addr + len - 1,
.space = space, .callback = callback };
Log("Add mmio map '%s' at [" FMT_PADDR ", " FMT_PADDR "]",
maps[nr_map].name, maps[nr_map].low, maps[nr_map].high);
nr_map ++;
}
add_mmio_map("rtc", CONFIG_RTC_MMIO, rtc_port_base, 8, rtc_io_handler);
name为设备的名字,例如这里的为 rtc,addr就是指该设备地址的起始位:CONFIG_RTC_MMIO,len为设备寄存器的总长度,
new_space()
uint8_t* new_space(int size) {
uint8_t* p = p_space;
// page aligned;
size = (size + (PAGE_SIZE - 1)) & ~PAGE_MASK;
p_space += size;
assert(p_space - io_space < IO_SPACE_MAX);
return p;
}
word_t map_read(paddr_t addr, int len, IOMap* map) {
assert(len >= 1 && len <= 8);
check_bound(map, addr);
paddr_t offset = addr - map->low;
invoke_callback(map->callback, offset, len, false); // prepare data to read
word_t ret = host_read(map->space + offset, len);
return ret;
}
static inline int find_mapid_by_addr(IOMap* maps, int size, paddr_t addr) {
int i;
for (i = 0; i < size; i++) {
if (map_inside(maps + i, addr)) {
difftest_skip_ref();
return i;
}
}
return -1;
}
static inline bool map_inside(IOMap* map, paddr_t addr) {
return (addr >= map->low && addr <= map->high);
}