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_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);
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值