User Interrupt#

Intel UINTR#

Intro.#

User interrupts are delivered to software operating in 64-bit mode with CPL=3 without any change to segmentation state. Different user interrupts are distinguished by a 6-bit user-interrupt vector. Based on x86, the UINTR state is configured by new supervisor-managed state, including new MSRs.

UPID : user posted-interrupt descriptor. User interrupts for an OS-managed thread can be posted in the UPID associated with that thread after receipt of an ordinary interrupt called a user-interrupt notification.

  • Outstanding notification (0): For one or more user interrupts in PIR (as |pir_bits).

  • Suppress notification (1): If set, agents should not send notifications when posting user interrupts in this descriptor.

  • Notification vector (23:16): Used by SENDUIPI.

  • Notification destination (63:32): Target physical APIC ID.

  • Posted-interrupt requests (PIR) (127:64): One bit for each user-interrupt vector. See UIRR.

Setting CR4[25] (as CR4.UINTR) enables user-interrupt delivery, user-interrupt notification identification and the user-interrupt instructions (UIRET, SENDUIPI).

User-interrupt State :

  • UIRR : If UIRR[i] = 1, a user interrupt with vector i is requesting service.

  • UIF : If UIF = 0, user-interrupt delivery is blocked; if UIF = 1, user interrupts may be delivered. User-interrupt delivery clears UIF, and the new UIRET instruction sets it.

  • UIHANDLER : The linear address of the user-interrupt handler.

  • UISTACKADJUST : This value controls adjustment to the stack pointer prior to user-interrupt delivery.

  • UINV : The vector of those ordinary interrupts that are treated as user-interrupt notifications.

  • UPIDADDR : The linear address of the UPID that the logical processor consults upon receiving an ordinary interrupt with vector UINV.

  • UITTADDR : The linear address of user-interrupt target table, which the logical processor consults when software invokes the SENDUIPI instruction.

  • UITTSZ : The highest index of a valid entry in the UITT.

User-interrupt MSRs :

  • IA32_UINTR_RR -> UIRR

  • IA32_UINTR_HANDLER -> UIHANDLER

  • IA32_UINTR_STACKADJUST -> UISTACKADJUST

  • IA32_UINTR_MISC -> {24’b0, UINV[7:0], UITTSZ[31:0]}

  • IA32_UINTR_PD -> UPIDADDR

  • IA32_UINTR_TT -> UITTADDR

User-interrupt notification identification : How to identify an ordinary interrupt as user-interrupt notification? The local APIC is acknowledged; move to the next step if the processor get an interrupt vector V = UINV. The processor writes zero to the EOI register in the local APIC; this dismiss the interrupt with vector V = UINV from the local APIC.

APIC

Advanced Programmable Interrupt Controller. Intended to solve interrupt routing efficiency issues in computer systems. In a APIC-based system, each CPU is made of a core and a local APIC. In addition, there is an I/O APIC that is part of the chipset and provides multi-processor interrupt management. Each interrupt pin is individually programmable as either edge or level triggered. The interrupt vector and interrupt steering information can be specified per interrupt.

User-interrupt notification processing :

  • The logical processor clears the outstanding-notification bit in the UPID. This is done atomically so as to leave the remainder of the descriptor unmodified.

  • The logical processor reads PIR into a temporary register and writes all zeros to PIR. This is done atomically so as to ensure that each bit cleared is set in the temporary register.

  • If any bit is set in the temporary register, the logical processor sets in UIRR each bit corresponding to a bit set in the temporary register and recognize a pending user interrupt.

Impl. in Linux#

UPID and UITT are managed by kernel.

Instructions:

  • SENDUIPI <index>: send a user IPI to a target task based on the UITT index.

  • CLUI: Mask user interrupts by clearing UIF.

  • STUI: Unmask user interrupts by setting UIF.

  • TESTUI: Test current value of UIF.

  • UIRET: return from a user interrupt handler.

UITT entry:

  • (0): V, a valid bit

  • (7:1): reserved

  • (15:8): UV, the user-interrupt vector (15:14 is wired to zero)

  • (63:16): reserved

  • (127:64): UPIDADDR (64-byte aligned, so 69:64 is wired to zero)

See implementations in uintr_linux .

 1/* Vector for User interrupt notifications */
 2#define UINTR_NOTIFICATION_VECTOR       0xec
 3#define UINTR_KERNEL_VECTOR                 0xeb
 4
 5int do_uintr_register_handler(u64 handler)
 6{
 7    struct uintr_receiver *ui_recv;
 8    struct uintr_upid *upid;
 9    struct task_struct *t = current;
10    struct fpu *fpu = &t->thread.fpu;
11    u64 misc_msr;
12    int cpu;
13
14    if (is_uintr_receiver(t))
15        return -EBUSY;
16
17    /* Alloc new uintr receiver with UPID */
18    ui_recv = kzalloc(sizeof(*ui_recv), GFP_KERNEL);
19    ui_recv->upid_ctx = alloc_upid();
20
21    /*
22     * UPID and ui_recv will be referenced during context switch. Need to
23     * disable preemption while modifying the MSRs, UPID and ui_recv thread
24     * struct.
25     */
26    fpregs_lock();
27
28    /* Find the receiver and set the notification vector and destination */
29    cpu = smp_processor_id();
30    upid = ui_recv->upid_ctx->upid;
31    upid->nc.nv = UINTR_NOTIFICATION_VECTOR;
32    upid->nc.ndst = cpu_to_ndst(cpu);
33
34    t->thread.ui_recv = ui_recv;
35
36    if (fpregs_state_valid(fpu, cpu)) {
37        wrmsrl(MSR_IA32_UINTR_HANDLER, handler);
38        wrmsrl(MSR_IA32_UINTR_PD, (u64)ui_recv->upid_ctx->upid);
39
40        /* Set value as size of ABI redzone */
41        wrmsrl(MSR_IA32_UINTR_STACKADJUST, 128);
42
43        /* Modify only the relevant bits of the MISC MSR */
44        rdmsrl(MSR_IA32_UINTR_MISC, misc_msr);
45        misc_msr |= (u64)UINTR_NOTIFICATION_VECTOR << 32;
46        wrmsrl(MSR_IA32_UINTR_MISC, misc_msr);
47    } else {
48        struct xregs_state *xsave;
49        struct uintr_state *p;
50
51        xsave = &fpu->state.xsave;
52        xsave->header.xfeatures |= XFEATURE_MASK_UINTR;
53        p = get_xsave_addr(&fpu->state.xsave, XFEATURE_UINTR);
54        if (p) {
55            p->handler = handler;
56            p->upid_addr = (u64)ui_recv->upid_ctx->upid;
57            p->stack_adjust = 128;
58            p->uinv = UINTR_NOTIFICATION_VECTOR;
59        }
60    }
61
62    fpregs_unlock();
63
64    return 0;
65}
66
67int do_uintr_register_vector(struct uintr_receiver_info *r_info)
68{
69    struct uintr_receiver *ui_recv;
70    struct task_struct *t = current;
71    /*
72    * A vector should only be registered by a task that
73    * has an interrupt handler registered.
74    */
75    if (!is_uintr_receiver(t)) return -EINVAL;
76    if (r_info->uvec >= UINTR_MAX_UVEC_NR) return -ENOSPC;
77    ui_recv = t->thread.ui_recv;
78    if (ui_recv->uvec_mask & BIT_ULL(r_info->uvec)) return -EBUSY;
79    ui_recv->uvec_mask |= BIT_ULL(r_info->uvec);
80    r_info->upid_ctx = get_upid_ref(ui_recv->upid_ctx);
81    return 0;
82}

int do_uintr_register_handler(u64 handler) allocates new user-interrupt context and UPID for current task. It disables preemption (the ability of the operating system to preempt or stop a currently scheduled task in favour of a higher priority task) during modifying task states. Then it sets the destination as target APIC id. It writes MSRs finally (or xsave states) to activate hardware process.

 1int do_uintr_register_sender(struct uintr_receiver_info *r_info,
 2                         struct uintr_sender_info *s_info)
 3{
 4    struct uintr_uitt_entry *uitte = NULL;
 5    struct uintr_sender *ui_send;
 6    struct task_struct *t = current;
 7    unsigned long flags;
 8    int entry;
 9    int ret;
10
11    /*
12    * Only a static check. Receiver could exit anytime after this check.
13    * This check only prevents connections using uintr_fd after the
14    * receiver has already exited/unregistered.
15    */
16    if (!uintr_is_receiver_active(r_info))
17        return -ESHUTDOWN;
18
19    if (is_uintr_sender(t)) {
20        entry = find_first_zero_bit((unsigned long *)t->thread.ui_send->uitt_mask,
21                        UINTR_MAX_UITT_NR);
22        if (entry >= UINTR_MAX_UITT_NR)
23            return -ENOSPC;
24    } else {
25        BUILD_BUG_ON(UINTR_MAX_UITT_NR < 1);
26        entry = 0;
27        ret = init_uitt();
28        if (ret)
29            return ret;
30    }
31
32    ui_send = t->thread.ui_send;
33
34    set_bit(entry, (unsigned long *)ui_send->uitt_mask);
35
36    spin_lock_irqsave(&ui_send->uitt_ctx->uitt_lock, flags);
37    uitte = &ui_send->uitt_ctx->uitt[entry];
38    pr_debug("send: sender=%d receiver=%d UITTE entry %d address %px\n",
39        current->pid, r_info->upid_ctx->task->pid, entry, uitte);
40
41    uitte->user_vec = r_info->uvec;
42    uitte->target_upid_addr = (u64)r_info->upid_ctx->upid;
43    uitte->valid = 1;
44    spin_unlock_irqrestore(&ui_send->uitt_ctx->uitt_lock, flags);
45
46    s_info->r_upid_ctx = get_upid_ref(r_info->upid_ctx);
47    s_info->uitt_ctx = get_uitt_ref(ui_send->uitt_ctx);
48    s_info->task = get_task_struct(current);
49    s_info->uitt_index = entry;
50
51    return 0;
52}
 1int uintr_receiver_wait(void)
 2{
 3    struct uintr_upid_ctx *upid_ctx;
 4    unsigned long flags;
 5
 6    if (!is_uintr_receiver(current))
 7        return -EOPNOTSUPP;
 8
 9    upid_ctx = current->thread.ui_recv->upid_ctx;
10    upid_ctx->upid->nc.nv = UINTR_KERNEL_VECTOR;
11    upid_ctx->waiting = true;
12    spin_lock_irqsave(&uintr_wait_lock, flags);
13    list_add(&upid_ctx->node, &uintr_wait_list);
14    spin_unlock_irqrestore(&uintr_wait_lock, flags);
15
16    set_current_state(TASK_INTERRUPTIBLE);
17    schedule();
18
19    return -EINTR;
20}
21
22/*
23 * Runs in interrupt context.
24 * Scan through all UPIDs to check if any interrupt is on going.
25 */
26void uintr_wake_up_process(void)
27{
28    struct uintr_upid_ctx *upid_ctx, *tmp;
29    unsigned long flags;
30
31    spin_lock_irqsave(&uintr_wait_lock, flags);
32    list_for_each_entry_safe(upid_ctx, tmp, &uintr_wait_list, node) {
33            if (test_bit(UPID_ON, (unsigned long *)&upid_ctx->upid->nc.status)) {
34                    set_bit(UPID_SN, (unsigned long *)&upid_ctx->upid->nc.status);
35                    upid_ctx->upid->nc.nv = UINTR_NOTIFICATION_VECTOR;
36                    upid_ctx->waiting = false;
37                    wake_up_process(upid_ctx->task);
38                    list_del(&upid_ctx->node);
39            }
40    }
41    spin_unlock_irqrestore(&uintr_wait_lock, flags);
42}