[南大ICS-PA2] 输入输出

输入输出

设备与CPU

设备也有自己的状态寄存器(相当于CPU的寄存器),也有自己的功能部件(相当于CPU的运算器)。

在程序看来, 访问设备 = 读出数据 + 写入数据 + 控制状态。

那么在CPU看来, 这些行为究竟意味着什么呢? 具体要从哪里读数据? 把数据写入到哪里? 如何查询/设置设备的状态? 一个最本质的问题是, CPU和设备之间的接口, 究竟是什么?

既然设备也有寄存器, 一种最简单的方法就是把设备的寄存器作为接口, 让CPU来访问这些寄存器。

端口I/O

一种I/O编址方式是端口映射I/O(port-mapped I/O), CPU使用专门的I/O指令对设备进行访问, 并把设备的地址称作端口号

事实上, 设备的API及其行为都会在相应的文档里面有清晰的定义, 在PA中我们无需了解这些细节, 只需要知道, 驱动开发者可以通过RTFM, 来编写相应程序来访问设备即可。

内存映射I/O

内存映射I/O这种编址方式非常巧妙, 它是通过不同的物理内存地址给设备编址的. 这种编址方式将一部分物理内存的访问"重定向"到I/O地址空间中, CPU尝试访问这部分物理内存的时候, 实际上最终是访问了相应的I/O设备, CPU却浑然不知。

内存映射I/O的编程模型和普通的编程完全一样: 程序员可以直接把I/O设备当做内存来访问。

理解volatile关键字

详解C/C++中volatile关键字

volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。

主要是避免其他位置改变了变量的值,而在本代码中,不认为它改变了,所以一直保存在寄存器中,没有去内存中读或者写入内存。

NEMU中的输入输出

NEMU的框架代码已经在nemu/src/device/目录下提供了设备相关的代码,

映射和I/O方式

设备

将输入输出抽象成IOE

IOE(I/O Extension)

访问设备 = 读出数据 + 写入数据 + 控制状态 = 读/写操作

串口

实现printf

static char sprint_buf[1024];
/*可变函数在内部实现的过程中是从右向左压入堆栈,从而保证了可变参数的第一个参数始终位于栈顶*/
int printf(const char *fmt, ...)//可以有一个或多个固定参数
{
  va_list args; //用于存放参数列表的数据结构
  int n;
  /*根据最后一个fmt来初始化参数列表,至于为什么是最后一个参数,是与va_start有关。*/
  va_start(args, fmt);
  n = vsprintf(sprint_buf, fmt, args);
  va_end(args);//执行清理参数列表的工作
  putstr(sprint_buf);
  return n;
}

修改am-kernels/kernels/hello/hello.c,加入头文件klib.h,加入语句printf("Hello world!\n");,成功输出"Hello world!"。

待解决的问题

如果向sprint_buf写入的超过1024怎么办?

时钟

实现IOE

abstract-machine/am/src/platform/nemu/ioe/timer.c中实现AM_TIMER_UPTIME的功能. 在abstract-machine/am/src/platform/nemu/include/nemu.habstract-machine/am/src/$ISA/$ISA.h中有一些输入输出相关的代码供你使用.

实现后, 在$ISA-nemu中运行am-kernel/tests/am-tests中的real-time clock test测试. 如果你的实现正确, 你将会看到程序每隔1秒往终端输出一行信息. 由于我们没有实现AM_TIMER_RTC, 测试总是输出1900年0月0日0时0分0秒, 这属于正常行为, 可以忽略.

void __am_timer_init() {
  outl(RTC_ADDR, 0);
  outl(RTC_ADDR + 4, 0);
}

void __am_timer_uptime(AM_TIMER_UPTIME_T *uptime) {
  uptime->us = inl(RTC_ADDR + 4);
  uptime->us <<= 32;
  uptime->us += inl(RTC_ADDR);
}

测试运行命令:make ARCH=riscv32-nemu run mainargs=t

看看NEMU跑多快

在测试microbench的test时,又补充了一个指令的实现

  // mulhu rd, rs1, rs2 x[rd] = (x[rs1] 𝑢 ×𝑢 x[rs2]) ≫𝑢 XLEN
  INSTPAT("0000001 ????? ????? 011 ????? 01100 11", mulhu   , R, R(dest) = ((uint64_t)src1 * (uint64_t)src2) >> 32); 

设备访问的踪迹 - dtrace

键盘

实现IOE(2)

abstract-machine/am/src/platform/nemu/ioe/input.c中实现AM_INPUT_KEYBRD的功能. 实现后, 在$ISA-nemu中运行am-tests中的readkey test测试. 如果你的实现正确, 在程序运行时弹出的新窗口中按下按键, 你将会看到程序输出相应的按键信息, 包括按键名, 键盘码, 以及按键状态.

void __am_input_keybrd(AM_INPUT_KEYBRD_T *kbd) {
  int k = AM_KEY_NONE;
  k = inl(KBD_ADDR);
  kbd->keydown = (k & KEYDOWN_MASK ? true : false);
  kbd->keycode = k & ~KEYDOWN_MASK;
}

VGA

事实上, VGA设备还有两个寄存器: 屏幕大小寄存器和同步寄存器. 我们在讲义中并未介绍它们, 我们把它们作为相应的练习留给大家. 具体地, 屏幕大小寄存器的硬件(NEMU)功能已经实现, 但软件(AM)还没有去使用它; 而对于同步寄存器则相反, 软件(AM)已经实现了同步屏幕的功能, 但硬件(NEMU)尚未添加相应的支持.

  • AM_GPU_CONFIG, AM显示控制器信息, 可读出屏幕大小信息widthheight

  • AM_GPU_FBDRAW, AM帧缓冲控制器, 可写入绘图信息, 向屏幕(x, y)坐标处绘制w*h的矩形图像. 图像像素按行优先方式存储在pixels中, 每个像素用32位整数以00RRGGBB的方式描述颜色. 若synctrue, 则马上将帧缓冲中的内容同步到屏幕上.

  • 屏幕大小寄存器在硬件中已经实现,在nemu/src/device/vga.c/void init_vga()

vgactl_port_base[0] = (screen_width() << 16) | screen_height();
add_mmio_map("vgactl", CONFIG_VGA_CTL_MMIO, vgactl_port_base, 8, NULL);
  • 同步寄存器,要在硬件中实现,即实现当同步寄存器为非零时,调用update_screen(),然后将同步寄存器清零。

    void vga_update_screen() {
      // TODO: call `update_screen()` when the sync register is non-zero,
      // then zero out the sync register
    #ifdef CONFIG_HAS_PORT_IO
      uint32_t sync = mmio_read(CONFIG_VGA_CTL_MMIO + 4, 4);
      if (sync != 0) {
        update_screen();
      } else {
        mmio_write(CONFIG_VGA_CTL_MMIO + 4, 4, 0);
      }
    }
    
  • 要修改void __am_gpu_config(AM_GPU_CONFIG_T *cfg),不然测试中,总读到行列值为0

    void __am_gpu_config(AM_GPU_CONFIG_T *cfg) {
      uint32_t screen_size = inl(VGACTL_ADDR);
      int width = (screen_size >> 16) & 0xffff;   // TODO: get the correct width
      int height = screen_size & 0xffff;           // TODO: get the correct height
      *cfg = (AM_GPU_CONFIG_T) {
        .present = true, .has_accel = false,
        .width = width, .height = height,		// 原本这里是0,导致错误
        .vmemsz = 0
      };
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值