24 nemu以及npc如何实现device的内存映射以及端口映射
输入输出
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_AMSDL_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 pressedcase 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;}
#endifdefault: 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
引入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 readword_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);
}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
