输入输出
实现IOE: 时钟
按照文档完成am/src/platform/nemu/ioe/timer.c
即可, 代码本身很简单, 直接贴出如下:
static uint64_t boot_time = 0;
void __am_timer_init() {
uint32_t high = inl(RTC_ADDR+4);
uint32_t low = inl(RTC_ADDR);
boot_time = (uint64_t)low + (((uint64_t)high) << 32);
}
void __am_timer_uptime(AM_TIMER_UPTIME_T *uptime) {
uint32_t high = inl(RTC_ADDR+4);
uint32_t low = inl(RTC_ADDR);
uptime->us = (uint64_t)low + (((uint64_t)high) << 32) - boot_time;
}
int is_leap_year(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
int days_in_month(int year, int month) {
static const int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (month == 2 && is_leap_year(year)) {
return 29;
}
return days[month - 1];
}
void __am_timer_rtc(AM_TIMER_RTC_T *rtc) {
uint32_t high = inl(RTC_ADDR+4);
uint32_t low = inl(RTC_ADDR);
uint64_t total_second = ((uint64_t)low + (((uint64_t)high) << 32)) / 1000000;
total_second += 8 * 3600; // Asia/Shanghai UTC+8
uint32_t days = total_second / 86400;
uint32_t second_in_day = total_second % 86400;
rtc->year = 1970;
while(true) {
int days_in_year = is_leap_year(rtc->year) ? 366 : 365;
if (days >= days_in_year) {
days -= days_in_year;
rtc->year++;
} else {
break;
}
}
rtc->month = 1;
while (1) {
int dim = days_in_month(rtc->year, rtc->month);
if (days >= dim) {
days -= dim;
rtc->month++;
} else {
break;
}
}
rtc->day = days + 1;
rtc->hour = second_in_day / 3600;
second_in_day %= 3600;
rtc->minute = second_in_day / 60;
rtc->second = second_in_day % 60;
}
若想查看实时时间, 可修改nemu/src/utils/timer.c
中的get_time
函数如下:
uint64_t get_time() {
uint64_t now = get_time_internal();
return now;
}
实现IOE: 按键
查看nemu的src/device/keyboard.c
, 按键数据的格式实际上为:
位置 | 意义 |
---|---|
[7] | 按键状态 |
[6:0] | 按键号码 |
代码实现如下:
#define KEYDOWN_MASK 0x8000
void __am_input_keybrd(AM_INPUT_KEYBRD_T *kbd) {
uint32_t key_code = inl(KBD_ADDR);
kbd->keydown = key_code & KEYDOWN_MASK ? true : false;
kbd->keycode = key_code & ~KEYDOWN_MASK;
}
实现IOE: VGA
文档提供的初始化代码提示很明确, 照着修改即可:
void __am_gpu_fbdraw(AM_GPU_FBDRAW_T *ctl) {
uint32_t *pixels = ctl->pixels;
for (uint32_t i = ctl->y; i < ctl->y + ctl->h; i++) {
for (uint32_t j = ctl->x; j < ctl->x + ctl->w; j++) {
config.fb[config.w * i + j] = pixels[ctl->w * (i - ctl->y) + (j - ctl->x)];
}
}
if (ctl->sync) {
outl(SYNC_ADDR, 1);
}
}
实现IOE: 声卡
这是一个重量级任务, 刚开始我也一头雾水. 前面的内容都是基础教学, 到这里难度突然上升, 既要完成硬件(nemu
)又要完成驱动(am
).
仔细阅读文档后, 发现基本逻辑没有太大变化, 但仍然感到困惑. 于是, 我查阅了SDL声音库的使用案例, 简单了解了声卡原理.
接着, 我查看了am-test
的audio_test
, 发现测试只是在配置完成后向一个缓冲区不断写入数据, 与其他IOE没有太大区别. 配置过程也只是将音频参数传输给硬件.
于是, 我先完成了驱动配置部分:
/*
由下面两段代码分析__am_audio_ctrl实际功能
am-kernels/tests/am-tests/src/tests/audio.c
io_write(AM_AUDIO_CTRL, 8000, 1, 1024);
abstract-machine/am/include/amdev.h
AM_DEVREG(15, AUDIO_CTRL, WR, int freq, channels, samples);
*/
void __am_audio_ctrl(AM_AUDIO_CTRL_T *ctrl) {
// 将音频的参数传给硬件
outl(AUDIO_FREQ_ADDR, ctrl->freq);
outl(AUDIO_CHANNELS_ADDR, ctrl->channels);
outl(AUDIO_SAMPLES_ADDR, ctrl->samples);
// 未完
}
在硬件方面, 我们知道驱动已经将音频参数传输过来, 根据SDL手册说明, 我们需要进行初始化才能播放音乐. 首先创建一个audio_init
函数用于初始化:
static void audio_init(void) {
SDL_AudioSpec s = {}; //
s.freq = audio_base[reg_freq]; // 频率
s.channels = audio_base[reg_channels]; // 频道
s.samples = audio_base[reg_samples]; // 采样率
s.callback = ...; // 播放回调函数
...
SDL_InitSubSystem(SDL_INIT_AUDIO);
SDL_OpenAudio(&s, NULL);
SDL_PauseAudio(0);
}
这里有一个名为callback
的参数, 即SDL用于定期读取音频数据的函数, 由我们定义. 其函数原型为void audio_callback(void *userdata, uint8_t *stream, int len)
. 若存在新的音频数据, 就将其写入stream
内.
现在思路开始清晰了, 我们需要在软件完成参数传递后, 由NEMU完成SDL音频初始化, 然后软件一直向缓冲区写数据, SDL则会定时调用audio_callback
读数据.
这里会产生一个问题, 我们如何知道音频是否被播放了?假如不管不顾地往里面写, 一定有概率将部分数据覆盖, 造成音频功能故障.
回看文档就会发现, 实际上已经给过提示了, 使用流缓冲区剩余大小reg_count
, 来限制写入, audio_callback
播放完成后才会让reg_count
增加. 我们仅需要让双方都知道当前有效数据的起始位置及大小, 就能轻松保证不冲突.
首先, 在nemu这边, 定义一个变量用于标记位置, 然后根据audio_callback
要求的长度len
与现有数据的长度reg_count
的不同:
static int audio_sbuf_flag = 0;
static void audio_callback(void *userdata, uint8_t *stream, int len) {
int len_ctrl = len;
if(audio_base[reg_count] < len) { // 需求数据长度大于有效数据长度
len_ctrl = audio_base[reg_count];
}
if(len_ctrl + audio_sbuf_flag < CONFIG_SB_SIZE) { // 回环读取处理
memcpy(stream, sbuf + audio_sbuf_flag, len_ctrl);
audio_sbuf_flag += len_ctrl;
} else {
memcpy(stream, sbuf + audio_sbuf_flag, CONFIG_SB_SIZE - audio_sbuf_flag);
memcpy(stream + CONFIG_SB_SIZE - audio_sbuf_flag, sbuf, len_ctrl - (CONFIG_SB_SIZE - audio_sbuf_flag));
audio_sbuf_flag = len_ctrl - (CONFIG_SB_SIZE - audio_sbuf_flag);
}
audio_base[reg_count] -= len_ctrl; // 更新有效数据长度
if (len_ctrl < len) { // 长度不足, 清除后续数据, 避免杂音
memset(stream + len_ctrl, 0, len - len_ctrl);
}
}
对应的, 驱动方面可以完成, 这里如同上方一样采用回环写入:
static int audio_sbuf_flag = 0;
void __am_audio_play(AM_AUDIO_PLAY_T *ctl) {
int data_len = ctl->buf.end - ctl->buf.start;
int sbuf_size = inl(AUDIO_SBUF_SIZE_ADDR);
assert(data_len < sbuf_size);
while (data_len > sbuf_size - inl(AUDIO_COUNT_ADDR)) {};
uint8_t *buf = (uint8_t*)AUDIO_SBUF_ADDR;
if (data_len + audio_sbuf_flag < sbuf_size) {
memcpy(buf + audio_sbuf_flag, ctl->buf.start, data_len);
audio_sbuf_flag += data_len;
} else {
memcpy(buf + audio_sbuf_flag, ctl->buf.start, sbuf_size - audio_sbuf_flag);
memcpy(buf, ctl->buf.start + (sbuf_size - audio_sbuf_flag), data_len - (sbuf_size - audio_sbuf_flag));
audio_sbuf_flag = data_len - (sbuf_size - audio_sbuf_flag);
}
int count = inl(AUDIO_COUNT_ADDR);
count += data_len;
outl(AUDIO_COUNT_ADDR, count);
}
双方都定义了audio_sbuf_flag
, 属实没想到什么好办法, 如果你有什么好想法, 欢迎发邮件.
最后, 我们只需要知道什么时候进行初始化即可, 根据整个的音频逻辑, 在进行配置参数后, 硬件就应该完成初始化工作, 所以我们直接在audio_io_handler
中这样写:
static void audio_io_handler(uint32_t offset, int len, bool is_write) {
if (audio_base[reg_init] && !audio_init_finish && is_write) {
audio_init_finish = true;
audio_init();
}
}
然后补全__am_audio_ctrl
并修改__am_audio_config
:
void __am_audio_config(AM_AUDIO_CONFIG_T *cfg) {
cfg->bufsize = inl(AUDIO_SBUF_SIZE_ADDR);
cfg->present = true;
}
void __am_audio_ctrl(AM_AUDIO_CTRL_T *ctrl) {
outl(AUDIO_FREQ_ADDR, ctrl->freq);
outl(AUDIO_CHANNELS_ADDR, ctrl->channels);
outl(AUDIO_SAMPLES_ADDR, ctrl->samples);
outl(AUDIO_INIT_ADDR, true);
audio_sbuf_flag = 0;
}
声卡完整代码(有修改)
nemu:
static int audio_sbuf_flag = 0;
static void audio_callback(void *userdata, uint8_t *stream, int len) {
int len_ctrl = len;
if(audio_base[reg_count] < len) {
len_ctrl = audio_base[reg_count];
}
SDL_LockAudio();
if (len_ctrl < len) {
memset(stream + len_ctrl, 0, len - len_ctrl);
}
if(len_ctrl + audio_sbuf_flag < CONFIG_SB_SIZE) {
memcpy(stream, sbuf + audio_sbuf_flag, len_ctrl);
audio_sbuf_flag += len_ctrl;
} else {
memcpy(stream, sbuf + audio_sbuf_flag, CONFIG_SB_SIZE - audio_sbuf_flag);
memcpy(stream + CONFIG_SB_SIZE - audio_sbuf_flag, sbuf, len_ctrl - (CONFIG_SB_SIZE - audio_sbuf_flag));
audio_sbuf_flag = len_ctrl - (CONFIG_SB_SIZE - audio_sbuf_flag);
}
SDL_UnlockAudio();
audio_base[reg_count] -= len_ctrl;
}
static void audio_init(void) {
SDL_AudioSpec s = {};
s.freq = audio_base[reg_freq];
s.channels = audio_base[reg_channels];
s.samples = audio_base[reg_samples];
s.callback = audio_callback;
s.userdata = NULL;
audio_base[reg_count] = 0;
audio_base[reg_sbuf_size] = CONFIG_SB_SIZE;
SDL_InitSubSystem(SDL_INIT_AUDIO);
SDL_OpenAudio(&s, NULL);
SDL_PauseAudio(0);
}
static void audio_io_handler(uint32_t offset, int len, bool is_write) {
if (audio_base[reg_init] && !audio_init_finish && is_write) {
audio_init_finish = true;
audio_init();
}
}
am:
static int audio_sbuf_flag = 0;
void __am_audio_init() {
}
void __am_audio_config(AM_AUDIO_CONFIG_T *cfg) {
cfg->bufsize = inl(AUDIO_SBUF_SIZE_ADDR);
cfg->present = true;
}
void __am_audio_ctrl(AM_AUDIO_CTRL_T *ctrl) {
outl(AUDIO_FREQ_ADDR, ctrl->freq);
outl(AUDIO_CHANNELS_ADDR, ctrl->channels);
outl(AUDIO_SAMPLES_ADDR, ctrl->samples);
outl(AUDIO_INIT_ADDR, true);
audio_sbuf_flag = 0;
}
void __am_audio_status(AM_AUDIO_STATUS_T *stat) {
stat->count = inl(AUDIO_COUNT_ADDR);
}
void __am_audio_play(AM_AUDIO_PLAY_T *ctl) {
int data_len = ctl->buf.end - ctl->buf.start;
int sbuf_size = inl(AUDIO_SBUF_SIZE_ADDR);
assert(data_len < sbuf_size);
while (data_len > sbuf_size - inl(AUDIO_COUNT_ADDR)) {};
uint8_t *buf = (uint8_t*)AUDIO_SBUF_ADDR;
if (data_len + audio_sbuf_flag < sbuf_size) {
memcpy(buf + audio_sbuf_flag, ctl->buf.start, data_len);
audio_sbuf_flag += data_len;
} else {
memcpy(buf + audio_sbuf_flag, ctl->buf.start, sbuf_size - audio_sbuf_flag);
memcpy(buf, ctl->buf.start + (sbuf_size - audio_sbuf_flag), data_len - (sbuf_size - audio_sbuf_flag));
audio_sbuf_flag = data_len - (sbuf_size - audio_sbuf_flag);
}
int count = inl(AUDIO_COUNT_ADDR);
count += data_len;
outl(AUDIO_COUNT_ADDR, count);
}