読者です 読者をやめる 読者になる 読者になる

はわわーっ

はわわわわっ

xenのハイパーコールをちょっと読んだメモ

xen

libxenctrlでxc_hvm_set_isa_irq_levelを実行したときにどうなってるのかちょっと見てみた。

まず、libxenctrl。

int xc_hvm_set_isa_irq_level(
    xc_interface *xch, domid_t dom,
    uint8_t isa_irq,
    unsigned int level)
{
    DECLARE_HYPERCALL;
    DECLARE_HYPERCALL_BUFFER(struct xen_hvm_set_isa_irq_level, arg);
    int rc;

    arg = xc_hypercall_buffer_alloc(xch, arg, sizeof(*arg));
    if ( arg == NULL )
    {
        PERROR("Could not allocate memory for xc_hvm_set_isa_irq_level hypercall");
        return -1;
    }

    hypercall.op     = __HYPERVISOR_hvm_op;
    hypercall.arg[0] = HVMOP_set_isa_irq_level;
    hypercall.arg[1] = HYPERCALL_BUFFER_AS_ARG(arg);

    arg->domid   = dom;
    arg->isa_irq = isa_irq;
    arg->level   = level;

    rc = do_xen_hypercall(xch, &hypercall);

    xc_hypercall_buffer_free(xch, arg);

    return rc;
}

関数はこう。hypercallしてるのはdo_xen_hypercallで

int do_xen_hypercall(xc_interface *xch, privcmd_hypercall_t *hypercall)
{       
    return xch->ops->u.privcmd.hypercall(xch, xch->ops_handle, hypercall);
}

こうなってる。プラットフォームによってここで呼ばれるのが変わるみたい。linuxの場合は

static struct xc_osdep_ops linux_privcmd_ops = {
    .open = &linux_privcmd_open,
    .close = &linux_privcmd_close,

    .u.privcmd = {
        .alloc_hypercall_buffer = &linux_privcmd_alloc_hypercall_buffer,
        .free_hypercall_buffer = &linux_privcmd_free_hypercall_buffer,
    
        .hypercall = &linux_privcmd_hypercall,
    
        .map_foreign_batch = &linux_privcmd_map_foreign_batch,
        .map_foreign_bulk = &linux_privcmd_map_foreign_bulk,
        .map_foreign_range = &linux_privcmd_map_foreign_range,
        .map_foreign_ranges = &linux_privcmd_map_foreign_ranges,
    },                       
};

こういう構造体があったのでlinux_privcmd_hypercallが呼ばれてるはず。
中身は

static int linux_privcmd_hypercall(xc_interface *xch, xc_osdep_handle h, privcmd_hypercall_t *hypercall)
{   
    int fd = (int)h;
    return ioctl(fd, IOCTL_PRIVCMD_HYPERCALL, hypercall);
}

こうなってた。ioctlを実行してるだけっぽい。
さっきの構造体のopenのところで

static xc_osdep_handle linux_privcmd_open(xc_interface *xch)
{   
    int flags, saved_errno;
    int fd = open("/proc/xen/privcmd", O_RDWR);
        
    if ( fd == -1 )
    {
        PERROR("Could not obtain handle on privileged command interface");
        return XC_OSDEP_OPEN_ERROR;
    }   
        
    /* Although we return the file handle as the 'xc handle' the API
       does not specify / guarentee that this integer is in fact
       a file handle. Thus we must take responsiblity to ensure
       it doesn't propagate (ie leak) outside the process */
    if ( (flags = fcntl(fd, F_GETFD)) < 0 )
    {
        PERROR("Could not get file handle flags");
        goto error;
    }   
    
    flags |= FD_CLOEXEC;
        
    if ( fcntl(fd, F_SETFD, flags) < 0 )
    {
        PERROR("Could not set file handle flags");
        goto error;
    }

    return (xc_osdep_handle)fd;

 error:
    saved_errno = errno;
    close(fd);
    errno = saved_errno;
    return XC_OSDEP_OPEN_ERROR;
}

これが呼ばれてるはずなので、/proc/xen/privcmdにioctlしてるのかな。

で、ここからlinuxカーネルのコードに移る。

const struct file_operations xen_privcmd_fops = {
        .owner = THIS_MODULE,
        .unlocked_ioctl = privcmd_ioctl,
        .mmap = privcmd_mmap,
};
EXPORT_SYMBOL_GPL(xen_privcmd_fops);

static struct miscdevice privcmd_dev = {
        .minor = MISC_DYNAMIC_MINOR,
        .name = "xen/privcmd",
        .fops = &xen_privcmd_fops,
};

/proc/xen/privcmdはこの構造体と結びついてる感じなのでioctlするとprivcmd_ioctlが呼ばれるはず。

static long privcmd_ioctl(struct file *file,
                          unsigned int cmd, unsigned long data)
{
        int ret = -ENOSYS;
        void __user *udata = (void __user *) data;

        switch (cmd) {
        case IOCTL_PRIVCMD_HYPERCALL:
                ret = privcmd_ioctl_hypercall(udata);
                break;

        case IOCTL_PRIVCMD_MMAP:
                ret = privcmd_ioctl_mmap(udata);
                break;

        case IOCTL_PRIVCMD_MMAPBATCH:
                ret = privcmd_ioctl_mmap_batch(udata, 1);
                break;

        case IOCTL_PRIVCMD_MMAPBATCH_V2:
                ret = privcmd_ioctl_mmap_batch(udata, 2);
                break;

        default:
                ret = -EINVAL;
                break;
        }

        return ret;
}

コマンドで分岐してるだけっぽい。今はIOCTL_PRIVCMD_HYPERCALLになってるはず。

static long privcmd_ioctl_hypercall(void __user *udata)
{
        struct privcmd_hypercall hypercall;
        long ret;

        if (copy_from_user(&hypercall, udata, sizeof(hypercall)))
                return -EFAULT;

        ret = privcmd_call(hypercall.op,
                           hypercall.arg[0], hypercall.arg[1],
                           hypercall.arg[2], hypercall.arg[3],
                           hypercall.arg[4]);

        return ret;
}

中身はこうなってて、ユーザスペースからデータをもらってprivcmd_callを呼んでる。
ちなみに、privcmd_hypercall構造体は

struct privcmd_hypercall {
        __u64 op;
        __u64 arg[5];      
};

こうなってて、xenのほうと同じになってる。まぁ当然そうなるのか。

privcmd_callは

static inline long
privcmd_call(unsigned call,
             unsigned long a1, unsigned long a2,
             unsigned long a3, unsigned long a4,
             unsigned long a5)
{
        __HYPERCALL_DECLS;
        __HYPERCALL_5ARG(a1, a2, a3, a4, a5);

        asm volatile("call *%[call]"
                     : __HYPERCALL_5PARAM
                     : [call] "a" (&hypercall_page[call])
                     : __HYPERCALL_CLOBBER5);
                
        return (long)__res;
}

こうなってるんだけど、ここからがさっぱり意味不明。何がどうなってんのかわからん。

extern struct { char _entry[32]; } hypercall_page[];

#define __HYPERCALL             "call hypercall_page+%c[offset]"
#define __HYPERCALL_ENTRY(x)                                            \
        [offset] "i" (__HYPERVISOR_##x * sizeof(hypercall_page[0]))

#ifdef CONFIG_X86_32
#define __HYPERCALL_RETREG      "eax"
#define __HYPERCALL_ARG1REG     "ebx"
#define __HYPERCALL_ARG2REG     "ecx"
#define __HYPERCALL_ARG3REG     "edx"
#define __HYPERCALL_ARG4REG     "esi"
#define __HYPERCALL_ARG5REG     "edi"
#else
#define __HYPERCALL_RETREG      "rax"
#define __HYPERCALL_ARG1REG     "rdi"
#define __HYPERCALL_ARG2REG     "rsi"
#define __HYPERCALL_ARG3REG     "rdx"
#define __HYPERCALL_ARG4REG     "r10"
#define __HYPERCALL_ARG5REG     "r8"
#endif

#define __HYPERCALL_DECLS                                               \
        register unsigned long __res  asm(__HYPERCALL_RETREG);          \
        register unsigned long __arg1 asm(__HYPERCALL_ARG1REG) = __arg1; \
        register unsigned long __arg2 asm(__HYPERCALL_ARG2REG) = __arg2; \
        register unsigned long __arg3 asm(__HYPERCALL_ARG3REG) = __arg3; \
        register unsigned long __arg4 asm(__HYPERCALL_ARG4REG) = __arg4; \
        register unsigned long __arg5 asm(__HYPERCALL_ARG5REG) = __arg5;

#define __HYPERCALL_0PARAM      "=r" (__res)
#define __HYPERCALL_1PARAM      __HYPERCALL_0PARAM, "+r" (__arg1)
#define __HYPERCALL_2PARAM      __HYPERCALL_1PARAM, "+r" (__arg2)
#define __HYPERCALL_3PARAM      __HYPERCALL_2PARAM, "+r" (__arg3)
#define __HYPERCALL_4PARAM      __HYPERCALL_3PARAM, "+r" (__arg4)
#define __HYPERCALL_5PARAM      __HYPERCALL_4PARAM, "+r" (__arg5)

#define __HYPERCALL_0ARG()
#define __HYPERCALL_1ARG(a1)                                            \
        __HYPERCALL_0ARG()              __arg1 = (unsigned long)(a1);
#define __HYPERCALL_2ARG(a1,a2)                                         \
        __HYPERCALL_1ARG(a1)            __arg2 = (unsigned long)(a2);
#define __HYPERCALL_3ARG(a1,a2,a3)                                      \
        __HYPERCALL_2ARG(a1,a2)         __arg3 = (unsigned long)(a3);
#define __HYPERCALL_4ARG(a1,a2,a3,a4)                                   \
        __HYPERCALL_3ARG(a1,a2,a3)      __arg4 = (unsigned long)(a4);
#define __HYPERCALL_5ARG(a1,a2,a3,a4,a5)                                \
        __HYPERCALL_4ARG(a1,a2,a3,a4)   __arg5 = (unsigned long)(a5);
        
#define __HYPERCALL_CLOBBER5    "memory"
#define __HYPERCALL_CLOBBER4    __HYPERCALL_CLOBBER5, __HYPERCALL_ARG5REG
#define __HYPERCALL_CLOBBER3    __HYPERCALL_CLOBBER4, __HYPERCALL_ARG4REG
#define __HYPERCALL_CLOBBER2    __HYPERCALL_CLOBBER3, __HYPERCALL_ARG3REG
#define __HYPERCALL_CLOBBER1    __HYPERCALL_CLOBBER2, __HYPERCALL_ARG2REG
#define __HYPERCALL_CLOBBER0    __HYPERCALL_CLOBBER1, __HYPERCALL_ARG1REG

こんな感じのがいっぱいあるんだけど、これはなに?
たぶん、ハイパーコール呼んだ時のジャンプ先を管理してるhypercall_pageがあって、どのハイパーコールが呼ばれたかによってうまいことやってるんだろう。
hypercall_pageは

.pushsection .text
        .balign PAGE_SIZE
ENTRY(hypercall_page)
#define NEXT_HYPERCALL(x) \
        ENTRY(xen_hypercall_##x) \
        .skip 32

NEXT_HYPERCALL(set_trap_table)
NEXT_HYPERCALL(mmu_update)
NEXT_HYPERCALL(set_gdt)
NEXT_HYPERCALL(stack_switch)
NEXT_HYPERCALL(set_callbacks)
NEXT_HYPERCALL(fpu_taskswitch)
NEXT_HYPERCALL(sched_op_compat)
NEXT_HYPERCALL(platform_op)
NEXT_HYPERCALL(set_debugreg)
NEXT_HYPERCALL(get_debugreg)
NEXT_HYPERCALL(update_descriptor)
NEXT_HYPERCALL(ni)
NEXT_HYPERCALL(memory_op)
NEXT_HYPERCALL(multicall)
NEXT_HYPERCALL(update_va_mapping)
NEXT_HYPERCALL(set_timer_op)
NEXT_HYPERCALL(event_channel_op_compat)
NEXT_HYPERCALL(xen_version)
NEXT_HYPERCALL(console_io)
NEXT_HYPERCALL(physdev_op_compat)
NEXT_HYPERCALL(grant_table_op)
NEXT_HYPERCALL(vm_assist)
NEXT_HYPERCALL(update_va_mapping_otherdomain)
NEXT_HYPERCALL(iret)
NEXT_HYPERCALL(vcpu_op)
NEXT_HYPERCALL(set_segment_base)
NEXT_HYPERCALL(mmuext_op)
NEXT_HYPERCALL(xsm_op)
NEXT_HYPERCALL(nmi_op)
NEXT_HYPERCALL(sched_op)
NEXT_HYPERCALL(callback_op)
NEXT_HYPERCALL(xenoprof_op)
NEXT_HYPERCALL(event_channel_op)
NEXT_HYPERCALL(physdev_op)
NEXT_HYPERCALL(hvm_op)
NEXT_HYPERCALL(sysctl)
NEXT_HYPERCALL(domctl)
NEXT_HYPERCALL(kexec_op)
NEXT_HYPERCALL(tmem_op) /* 38 */
ENTRY(xen_hypercall_rsvr)
        .skip 320
NEXT_HYPERCALL(mca) /* 48 */
NEXT_HYPERCALL(arch_1)
NEXT_HYPERCALL(arch_2)
NEXT_HYPERCALL(arch_3)
NEXT_HYPERCALL(arch_4)
NEXT_HYPERCALL(arch_5)
NEXT_HYPERCALL(arch_6)
        .balign PAGE_SIZE
.popsection

たぶんここで値が設定されているんだろう。きっと。
これみると、xen_hypercall_hvm_opってのがどこかにありそうなんだけど、どこにあるのかわからなかった。

というかこのあたり全体的になにしてるのかわからん。

で、ここからlinuxカーネルからxenハイパーバイザに移る。
カーネルからどうなって呼ばれてるのかはよくわからなかったけど

typedef unsigned long hvm_hypercall_t(
    unsigned long, unsigned long, unsigned long, unsigned long, unsigned long,
    unsigned long);

#define HYPERCALL(x)                                        \
    [ __HYPERVISOR_ ## x ] = (hvm_hypercall_t *) do_ ## x


static hvm_hypercall_t *const hvm_hypercall64_table[NR_hypercalls] = {
    [ __HYPERVISOR_memory_op ] = (hvm_hypercall_t *)hvm_memory_op,
    [ __HYPERVISOR_grant_table_op ] = (hvm_hypercall_t *)hvm_grant_table_op,
    [ __HYPERVISOR_vcpu_op ] = (hvm_hypercall_t *)hvm_vcpu_op,
    [ __HYPERVISOR_physdev_op ] = (hvm_hypercall_t *)hvm_physdev_op,
    HYPERCALL(xen_version),
    HYPERCALL(console_io),
    HYPERCALL(event_channel_op),
    HYPERCALL(sched_op),
    HYPERCALL(set_timer_op),
    HYPERCALL(xsm_op),
    HYPERCALL(hvm_op),
    HYPERCALL(sysctl),
    HYPERCALL(domctl),
    HYPERCALL(tmem_op)
};

なんかこんなのがあって、これでどのハイパーコールが実行されたかによって分岐してるっぽく見える。
hvm_opのときはdo_hvm_opっていう関数が呼ばれるはず。

long do_hvm_op(unsigned long op, XEN_GUEST_HANDLE_PARAM(void) arg)

{
    struct domain *curr_d = current->domain;
    long rc = 0;

    switch ( op )
    {
    case HVMOP_set_param:
    case HVMOP_get_param:

...

    case HVMOP_set_isa_irq_level:
        rc = hvmop_set_isa_irq_level(
            guest_handle_cast(arg, xen_hvm_set_isa_irq_level_t));
        break;

...

    default:
    {       
        gdprintk(XENLOG_DEBUG, "Bad HVM op %ld.\n", op);
        rc = -ENOSYS;
        break;
    }
    }       
    
    if ( rc == -EAGAIN )
        rc = hypercall_create_continuation(
            __HYPERVISOR_hvm_op, "lh", op, arg);

    return rc;
}

関数はこんな感じ。指定されたオペレーションによって分岐してそれぞれ処理してる。
今の場合は、HVMOP_set_isa_irq_levelになってるので

static int hvmop_set_isa_irq_level(
    XEN_GUEST_HANDLE_PARAM(xen_hvm_set_isa_irq_level_t) uop)
{       
    struct xen_hvm_set_isa_irq_level op;
    struct domain *d;
    int rc;
        
    if ( copy_from_guest(&op, uop, 1) )
        return -EFAULT;

    if ( op.isa_irq > 15 )
        return -EINVAL;
            
    rc = rcu_lock_remote_domain_by_id(op.domid, &d);
    if ( rc != 0 )
        return rc;
            
    rc = -EINVAL;
    if ( !is_hvm_domain(d) )
        goto out;
            
    rc = xsm_hvm_set_isa_irq_level(XSM_DM_PRIV, d);
    if ( rc )
        goto out;
            
    rc = 0; 
    switch ( op.level )
    {
    case 0:
        hvm_isa_irq_deassert(d, op.isa_irq);
        break;
    case 1:
        hvm_isa_irq_assert(d, op.isa_irq);
        break;
    default:
        rc = -EINVAL;
        break;
    }

 out:
    rcu_unlock_domain(d);
    return rc;
}

これが呼ばれるはず。

libxenctrlからちょっと追いかけてみたけどやっぱり難しいな。
雰囲気がなんとなくわかった感じがしただけだった。