android qemu-kvm i8254 pit虚拟设备

ubuntu12.04下使用android emulator,启用kvm加速,模拟i8254定时器的代码比较旧,对应于qemu0.14或者之前的版本,这时还没有QOM(qemu object model)模型,虚拟设备的代码是比较简单的。

玩虚拟设备之前,首先得搞明白真实设备怎么玩,有篇文档:http://blog.csdn.net/u013007900/article/details/50408903,看不太明白就再看看计组和哈工大出版的C语言测控,以前上课用的这个。

8254使用的端口时0x40~0x43,共计4个8bit端口,输入时钟频率1193kHZ,使用IRQ0,对应中断向量表中的INT 8。怎么对应,看http://www.360doc.com/content/09/1017/08/128139_7395798.shtmlhttp://blog.csdn.net/duguteng/article/details/7552774

8259主片的IRQ0~7对应INT 8~INT F,从片的IRQ8~IRQ15对应INT 70~INT 77。

有份以前上C语言测控时写的代码,使用了8254的,输入采样周期(in ms)和采样次数,每次采样时打印一个'8'。

注意定时器的最大周期比较短,大约55ms,所以需要使用软件方式扩大定时器的周期,注意周期不是10ms的倍数时的特殊处理。

定时器0工作于模式3,方波发生器。用学硬件的话来说,就是自动重装定时器;用学软件的话来说,就是周期定时器,不是oneshot的。

/* C语言测控程序设计
 * 2012年3月29日
 * 系统XP sp3,编译器:TC3.0,编辑器:VIM7.3
 * */

#include <stdio.h>
#include <dos.h>
#include <graphics.h>
#include <math.h>
#include <string.h>

/*参数*/
float   gfT;                                    //采样周期
long    glN;                                    //采样次数
int     giFlag;                                 //标记时间到
long    glUserCnt;                              //已采样次数
int     giTimerN;                               //采样周期除以10ms
int     giTimerSmallValue;                      //采样周期模10ms后,对应的定时器初值
int     giTimerCnt;                             //定时器中断次数

void    LoadConfig(void);                       //读取配置文件
void    interrupt (*OldIsr08)(void);            //原先的中断函数指针
void    interrupt MyIsr08(void);                //自定义的中断函数
void    TimerInit(void);                        //定时器初始化函数
void    TimerExit(void);                        //定时器恢复函数
void    UserTimerIsr(void);                     //每个采样周期都会调用的函数

int     main()
{
    /*读取配置*/
    LoadConfig();

    /*初始化*/
    TimerInit();

    while((glUserCnt < glN) || (glN == 0))
    {
        if(kbhit())     //特定按键退出
        {
            if(getch() == ' ')
                break;
        }
        if(giFlag)
        {
            giFlag = 0;
            putchar('8');
        }
    }

    /*恢复定时器和dos界面*/
    TimerExit();
    printf("\nthe times of interrupt is: %ld\n",glUserCnt);
    getch();
    return 0;
}

/*定时器中断函数,每到用户设定的时间,调用一次UserTimerIsr()*/
void    interrupt MyIsr08(void)
{
    giTimerCnt++;
    if(giTimerN == 0)   //采样周期小于10ms的情况
    {
        giTimerCnt = 0;
        UserTimerIsr();
        outportb(0x20, 0x20); //清除中断标志位,可以看8259相关的资料
        return;
    }
    if((giTimerSmallValue == 0) && (giTimerCnt == giTimerN))    //采样周期是10ms的倍数的情况
    {
        giTimerCnt = 0;
        UserTimerIsr();
        outportb(0x20, 0x20);
        return;
    }
    if((giTimerSmallValue != 0) && (giTimerN != 0)) //采样周期大于10ms,且不是10ms倍数的情况
    {
        if(giTimerCnt == 1)
        {
            disable();
            outportb(0x43, 0x36);
            outportb(0x40, 0x9d);
            outportb(0x40, 0x2e);
            enable();
        }
        if(giTimerCnt == (giTimerN + 1))
        {
            giTimerCnt = 0;
            disable();
            outportb(0x43, 0x36);
            outportb(0x40, giTimerSmallValue & 0xff);
            outportb(0x40, (giTimerSmallValue >> 8) & 0xff);
            enable();
            UserTimerIsr();
        }
        outportb(0x20, 0x20);
        return;
    }
    outportb(0x20, 0x20);
}

/*初始化定时器*/
void    TimerInit(void)
{
    giTimerN = (int)(gfT / 10);
    giTimerSmallValue = (int)((gfT - giTimerN * 10) * 1193); // 输入时钟频率1193kHZ
    disable();
    OldIsr08 = getvect(0x08);
    if(giTimerSmallValue)
    {
        outportb(0x43, 0x36);
        outportb(0x40, giTimerSmallValue & 0xff);
        outportb(0x40, (giTimerSmallValue >> 8) & 0xff);
    }
    else
    {
        outportb(0x43, 0x36);
        outportb(0x40, 0x9d);
        outportb(0x40, 0x2e);
    }
    setvect(0x08, MyIsr08);
    enable();
}

/*恢复定时器原先的服务函数和周期*/
void    TimerExit(void)
{
    disable();
    outportb(0x43, 0x36);
    outportb(0x40, 0x00);
    outportb(0x40, 0x00);
    setvect(0x08, OldIsr08);
    enable();
}

/*每个采样周期都会调用的函数*/
void    UserTimerIsr(void)
{
    glUserCnt++;
    giFlag = 1;
}

/*获取配置信息*/
void    LoadConfig(void)
{
    printf("input T and N\n");
    scanf("%f %ld", &gfT, &glN);
    while(getchar() != 10);
    if( gfT <= 0 || glN < 0)
    {
        printf("error, try again\n");
        LoadConfig();
    }
}

真的看完了,现在开始看模拟的。

8254的初始化是在pc_init1中执行的,设置iobase为0x40,IRQ为0,INT 8:

pit = pit_init(0x40, i8259[0]);

8254是有三个timer的,只用到了channel 0的timer。

qemu有自己的定时器,输入时钟是1G,对应1ns。8254的输入时钟是1193kHZ,如何模拟的呢?

根据8254的设置,计算出来下一个中断到临的tick次数,在根据8254和qemu timer频率的不同,对tick进行转换,然后设置qemu timer的定时设置,当qemu timer超时时,callback函数就是8254的中断处理函数pit_irq_timer。在中断函数中,再进行一些其它的处理,如重新装载之类的。

PITState *pit_init(int base, qemu_irq irq)
{
    PITState *pit = &pit_state;
    PITChannelState *s;

    s = &pit->channels[0];
    /* the timer 0 is connected to an IRQ */
    s->irq_timer = timer_new(QEMU_CLOCK_VIRTUAL, SCALE_NS, pit_irq_timer, s);
    s->irq = irq;

    register_savevm(NULL, "i8254", base, 1, pit_save, pit_load, pit);

    qemu_register_reset(pit_reset, 0, pit);
    register_ioport_write(base, 4, 1, pit_ioport_write, pit);
    register_ioport_read(base, 3, 1, pit_ioport_read, pit);

    pit_reset(pit);

    return pit;
}

qemu_register_reset是用链表保存一些复位函数的:

void qemu_register_reset(QEMUResetHandler *func, int order, void *opaque)
{
    QEMUResetEntry **pre, *re;

    pre = &first_reset_entry;
    while (*pre != NULL && (*pre)->order >= order) {
        pre = &(*pre)->next;
    }
    re = g_malloc0(sizeof(QEMUResetEntry));
    re->func = func;
    re->opaque = opaque;
    re->order = order;
    re->next = NULL;
    *pre = re;
}

当然pit_init最后也调用了pit_reset函数对寄存器进行复位,将mode设置为3,设置gate,计数值归零:

static void pit_reset(void *opaque)
{
    PITState *pit = opaque;
    PITChannelState *s;
    int i;

    for(i = 0;i < 3; i++) {
        s = &pit->channels[i];
        s->mode = 3;
        s->gate = (i != 2);
        pit_load_count(s, 0);
    }
}

这两行设置了寄存器的读写函数,注意这里是PMIO方式,不是MMIO方式的寄存器。0x40~0x43的写函数设置为pit_ioport_write;0x40~0x42的读函数设置为pit_ioport_read:

register_ioport_write(base, 4, 1, pit_ioport_write, pit);
register_ioport_read(base, 3, 1, pit_ioport_read, pit);

写函数,看懂寄存器的使用后,这个函数还是比较简单的:

static void pit_ioport_write(void *opaque, uint32_t addr, uint32_t val)
{
    PITState *pit = opaque;
    int channel, access;
    PITChannelState *s;

    addr &= 3;
    if (addr == 3) {
        channel = val >> 6;
        if (channel == 3) {
            /* read back command */
            for(channel = 0; channel < 3; channel++) {
                s = &pit->channels[channel];
                if (val & (2 << channel)) {
                    if (!(val & 0x20)) {
                        pit_latch_count(s);
                    }
                    if (!(val & 0x10) && !s->status_latched) {
                        /* status latch */
                        /* XXX: add BCD and null count */
                        s->status =  (pit_get_out1(s, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) 7) |
                            (s->rw_mode << 4) |
                            (s->mode << 1) |
                            s->bcd;
                        s->status_latched = 1;
                    }
                }
            }
        } else {
            s = &pit->channels[channel];
            access = (val >> 4) & 3;
            if (access == 0) {
                pit_latch_count(s);
            } else {
                s->rw_mode = access;
                s->read_state = access;
                s->write_state = access;

                s->mode = (val >> 1) & 7;
                s->bcd = val & 1;
                /* XXX: update irq timer ? */
            }
        }
    } else {
        s = &pit->channels[addr];
        switch(s->write_state) {
        default:
        case RW_STATE_LSB:
            pit_load_count(s, val);
            break;
        case RW_STATE_MSB:
            pit_load_count(s, val << 8);
            break;
        case RW_STATE_WORD0:
            s->write_latch = val;
            s->write_state = RW_STATE_WORD1;
            break;
        case RW_STATE_WORD1:
            pit_load_count(s, s->write_latch | (val << 8));
            s->write_state = RW_STATE_WORD0;
            break;
        }
    }
}

pit_latch_count用于锁存当前的计数值:

static void pit_latch_count(PITChannelState *s)
{
    if (!s->count_latched) {
        s->latched_count = pit_get_count(s);
        s->count_latched = s->rw_mode;
    }
}

pit_load_count用于装载计数值,count_load_time是装载时tick的值(tick++ in every ns);count是8254的周期,8254自己的计数值会按照1193kHZ的频率递减的。注意和count_load_time单位的不同,以及后续单位的转换。最后调用pit_irq_timer_update,对qemu timer进行更新。

static inline void pit_load_count(PITChannelState *s, int val)
{
    if (val == 0)
        val = 0x10000;
    s->count_load_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
    s->count = val;
    pit_irq_timer_update(s, s->count_load_time);
}

pit_irq_timer_update函数干两件事:

1、计算irq_level,就是比较tick的值和设定的值,满足条件时就会qemu_set_irq触发中断请求

2、计算expire_time,并且调用timer_mod更新qemu timer,让qemu timer在8254下一个需要产生中断的时候产生timeout,并调用callback,也就是8254的中断函数

static void pit_irq_timer_update(PITChannelState *s, int64_t current_time)
{
    int64_t expire_time;
    int irq_level;

    if (!s->irq_timer)
        return;
    expire_time = pit_get_next_transition_time(s, current_time);
    irq_level = pit_get_out1(s, current_time);
    qemu_set_irq(s->irq, irq_level);
#ifdef DEBUG_PIT
    printf("irq_level=%d next_delay=%f\n",
           irq_level,
           (double)(expire_time - current_time) / get_ticks_per_sec());
#endif
    s->next_transition_time = expire_time;
    if (expire_time != -1)
        timer_mod(s->irq_timer, expire_time);
    else
        timer_del(s->irq_timer);
}

8254的中断函数,也就是qemu timer的callback函数,也调用了pit_irq_timer_update:

static void pit_irq_timer(void *opaque)
{
    PITChannelState *s = opaque;

    pit_irq_timer_update(s, s->next_transition_time);
}

寄存器的读函数:

static uint32_t pit_ioport_read(void *opaque, uint32_t addr)
{
    PITState *pit = opaque;
    int ret, count;
    PITChannelState *s;

    addr &= 3;
    s = &pit->channels[addr];
    if (s->status_latched) {
        s->status_latched = 0;
        ret = s->status;
    } else if (s->count_latched) {
        switch(s->count_latched) {
        default:
        case RW_STATE_LSB:
            ret = s->latched_count & 0xff;
            s->count_latched = 0;
            break;
        case RW_STATE_MSB:
            ret = s->latched_count >> 8;
            s->count_latched = 0;
            break;
        case RW_STATE_WORD0:
            ret = s->latched_count & 0xff;
            s->count_latched = RW_STATE_MSB;
            break;
        }
    } else {
        switch(s->read_state) {
        default:
        case RW_STATE_LSB:
            count = pit_get_count(s);
            ret = count & 0xff;
            break;
        case RW_STATE_MSB:
            count = pit_get_count(s);
            ret = (count >> 8) & 0xff;
            break;
        case RW_STATE_WORD0:
            count = pit_get_count(s);
            ret = count & 0xff;
            s->read_state = RW_STATE_WORD1;
            break;
        case RW_STATE_WORD1:
            count = pit_get_count(s);
            ret = (count >> 8) & 0xff;
            s->read_state = RW_STATE_WORD0;
            break;
        }
    }
    return ret;
}

当kvm执行到PMIO的操作时,会退出,然后调用kvm_handle_io:

        case KVM_EXIT_IO:
            dprintf("handle_io\n");
            ret = kvm_handle_io(cpu, run->io.port,
                                (uint8_t *)run + run->io.data_offset,
                                run->io.direction,
                                run->io.size,
                                run->io.count);
            break;

static int kvm_handle_io(CPUState *cpu, uint16_t port, void *data,
                         int direction, int size, uint32_t count)
{
    int i;
    uint8_t *ptr = data;

    for (i = 0; i < count; i++) {
        if (direction == KVM_EXIT_IO_IN) {
            switch (size) {
            case 1:
                stb_p(ptr, cpu_inb(port));
                break;
            case 2:
                stw_p(ptr, cpu_inw(port));
                break;
            case 4:
                stl_p(ptr, cpu_inl(port));
                break;
            }
        } else {
            switch (size) {
            case 1:
                cpu_outb(port, ldub_p(ptr));
                break;
            case 2:
                cpu_outw(port, lduw_p(ptr));
                break;
            case 4:
                cpu_outl(port, ldl_p(ptr));
                break;
            }
        }

        ptr += size;
    }

    return 1;
}

以8bit读为例子:

uint8_t cpu_inb(pio_addr_t addr)
{
    uint8_t val;
    val = ioport_read(0, addr);
    LOG_IOPORT("inb : %04"FMT_pioaddr" %02"PRIx8"\n", addr, val);
    return val;
}

static uint32_t ioport_read(int index, uint32_t address)
{
    static IOPortReadFunc * const default_func[3] = {
        default_ioport_readb,
        default_ioport_readw,
        default_ioport_readl
    };
    IOPortReadFunc *func = ioport_read_table[index][address];
    if (!func)
        func = default_func[index];
    return func(ioport_opaque[address], address);
}

PMIO的地址和opaque以及读写函数的绑定,使用register_ioport_read,register_ioport_write函数,在i8254.c的pit_init中调用的:

int register_ioport_read(pio_addr_t start, int length, int size,
                         IOPortReadFunc *func, void *opaque)
{
    pio_addr_t i;
    int bsize;

    if (ioport_bsize(size, &bsize)) {
        hw_error("register_ioport_read: invalid size");
        return -1;
    }
    for(i = start; i < start + length; i += size) {
        ioport_read_table[bsize][i] = func;
        if (ioport_opaque[i] != NULL && ioport_opaque[i] != opaque)
            hw_error("register_ioport_read: invalid opaque");
        ioport_opaque[i] = opaque;
    }
    return 0;
}

pit_save,pit_load,register_savevm用于快照和恢复的,可以不看。

现在qemu的8254都是使用了QOM模型了,这个模型太TMD的复杂了。另外hw/i386/kvm/timer/i8254.c中提供了kvm-pit,使用kvm提供的内核态的8254的模拟,中断的处理和IO的读写都在内核态,不需要退出kvm了,速度要更快些。类似的,8259之类的也有kvm内核态的实现,所以说android emulator的性能还是有提升空间的。

时间: 2024-08-18 07:01:41

android qemu-kvm i8254 pit虚拟设备的相关文章

android qemu-kvm i8259 中断控制器虚拟设备

ubuntu12.04下使用android emulator,启用kvm加速,模拟i8259中断控制器的代码比较旧,对应于qemu0.14或者之前的版本,这时还没有QOM(qemu object model)模型,虚拟设备的代码是比较简单的. 玩虚拟设备之前,首先得搞明白真实设备怎么玩:http://www.360doc.com/content/09/1017/08/128139_7395798.shtml和http://blog.csdn.net/duguteng/article/detail

KVM虚拟机IO处理过程(二) ----QEMU/KVM I/O 处理过程

   接着KVM虚拟机IO处理过程中Guest Vm IO处理过程(http://blog.csdn.net/dashulu/article/details/16820281),本篇文章主要描述IO从guest vm跳转到kvm和qemu后的处理过程.     首先回顾一下kvm的启动过程(http://blog.csdn.net/dashulu/article/details/17074675).qemu通过调用kvm提供的一系列接口来启动kvm. qemu的入口为vl.c中的main函数,m

KVM/QEMU/qemu-kvm/libvirt 概念全解

目录 目录 前言 KVM QEMU KVM 与 QEMU qemu-kvm Libvirt Libvirt 在 OpenStack 中的应用 前言 如果是刚开始接触虚拟机技术的话, 对上述的概念肯定会有所混淆, 傻傻的分不清. 尤其在看虚拟化技术文档时导致理解能力下降, 所以在开始学习虚拟化技术之前对这些概念有一个整体的认识和清晰的理解, 就显得很有必要了. KVM KVM(Kernel-basedVirtual Machine 基于内核的虚拟机), 狭义 KVM 指的是一个嵌入到 Linux

AQEMU 0.8.2发布 QEMU和KVM模拟器的GUI

AQEMU是一个QEMU和KVM模拟器的GUI.它有一个友好的用户界面,并允许您设置了多数QEMU和KVM的选项. AQEMU 0.8.2该版本QEMU/KVM 0.14支持.一些错误的修正. 下载地址:http://sourceforge.net/projects/aqemu/files/aqemu/0.8.2/aqemu-0.8.2.tar.bz2/download

UBuntu系统安装KVM创建虚拟机遭遇故障

 现象: ping不通虚拟机,ssh登录不上,console方式登录不上, 虚拟机无法shutdown,貌似电源管理也没有安装成功. 先来解决网络问题: 还好可以通过virt-cat来检查虚拟机内部的文件. 先关闭虚拟机 virsh destroy vm1 然后检查网卡设置文件: root@dbkvm:~# virt-cat -d vm1 /etc/network/interfaces # This file describes the network interfaces available

KVM下用DevStack快速安装和配置OpenStack开发环境教程

  OpenStack是一个开源的云计算管理平台项目,支持几乎所有类型的云环境,项目目标是提供实施简单.可大规模扩展.丰富.标准统一的云计算管理平台.本文我们来谈谈DevStack安装和配置OpenStack开发环境. OpenStack的安装和配置有一点复杂,特别对于初学者来,第一次安装OpenStack时经常会碰到很多的问题.不过在Openstack社区中,一些开发者开发了一些自动化脚本来方便搭建OpenStack的开发环境,其中,DevStack是其中相对比较完善的,也是OpenStack

qemu-kvm虚拟机中硬盘跟内存有qemu标识

问题描述 qemu-kvm虚拟机中硬盘跟内存有qemu标识 200C 大家好,我是一个Linux小菜,我目前遇到一个问题,就是我在Ubuntu server14.1下,安装了qemu-kvm version2.1.0,我创建的虚拟机,硬盘跟内存都带有qemu的标识,这个是个很头大的问题,找了很多方法,都摸不到头绪,希望有经验的各位老师来帮我出出主意,非常感谢大家.必有重谢 图片说明 解决方案 KVM虚拟机和QEMUKVM虚拟机和QEMU 解决方案二: 直接在源码中搜索,然后修改为你要的,保存,重

【android】 android studio使用

1.下载 百度搜索,百度会直接给出下载链接. 2.配套工具 androidSDK.是一个压缩包,解压后得到目录xxx.在studio中设置sdk目录=此目录xxx即可.注意绝对路径中不能含有空格. 3.avd调试 avd,android virtual device,安卓虚拟设备. 3.1常见错误 avd启动时有时会报错,硬件加速器不能用等. 答:需要下载一个驱动程序,名字与路径是这样的:android-sdk-windows\extras\intel\Hardware_Accelerated_

Android官方命令深入分析之虚拟机

Android SDK包含了一个运行在计算机上的移动设备虚拟机.这个虚拟机可以允许你在没有物理设备的情况下开发和测试Android应用. 键盘命令 虚拟设备按键 对应键盘按键 Home HOME 菜单 F2 Back ESC 拨号 F3 挂断 F4 搜索 F5 电源按钮 F7 声音+ Ctrl+F5 声音- Ctrl+F6 相机 Ctrl+F3 切换横竖屏幕 Ctrl+F11 网络开关 F8 全屏模式 Alt+Enter