Il giorno sabato 18 marzo 2017 09:32:19 UTC+1, Jan Kiszka ha scritto:
> On 2017-03-17 14:42, [email protected] wrote:
> > Il giorno venerdì 17 marzo 2017 13:43:32 UTC+1, J. Kiszka ha scritto:
> >> On 2017-03-17 13:06, Claudio Scordino wrote:
> >>> Dear all,
> >>>
> >>> we are facing an unexpected exception when running the apic timer to
> >>> drive a GPIO as a software PWM.
> >>>
> >>> The platform is x86. The software runs in a bare-metal cell. The PWM
> >>> frequency is 5 KHz.
> >>>
> >>> When the duty cycle is very high or very low (i.e., two subsequent
> >>> interrupts get closer) we face the following unexpected exception:
> >>>
> >>> FATAL: Unhandled VM-Exit, reason 2
> >>> qualification 0
> >>> vectoring info: 0 interrupt info: 0
> >>> RIP: 0x00000000000f15d6 RSP: 0x00000000000dff08 FLAGS: 10002
> >>
> >> "objdump -dS inmate-linked.o" can tell you which instruction at RIP
> >> causes this fault. It's a triple fault, likely started off by a general
> >> protection or page fault.
> >>
> >> Jan
> > 
> > Hi I'm Errico, Claudio's coworker, and I'm actually playing with this issue.
> > 
> > The fault happens when we re-arm the apic timer
> > 
> > 00000000000f15b5 <apic_timer_set>:
> > 
> > void apic_timer_set(unsigned long timeout_ns)
> > {
> >     unsigned long long ticks =
> >             (unsigned long long)timeout_ns * divided_apic_freq;
> >     write_msr(X2APIC_TMICT, ticks / NS_PER_SEC);
> >    f15b5:   48 89 f8                mov    %rdi,%rax
> >    f15b8:   b9 00 ca 9a 3b          mov    $0x3b9aca00,%ecx
> >    f15bd:   31 d2                   xor    %edx,%edx
> >    f15bf:   48 0f af 05 f1 10 ff    imul   -0xef0f(%rip),%rax        # 
> > e26b8 <divided_apic_freq>
> >    f15c6:   ff 
> >    f15c7:   48 f7 f1                div    %rcx
> >    f15ca:   b9 38 08 00 00          mov    $0x838,%ecx
> >    f15cf:   48 89 c2                mov    %rax,%rdx
> >    f15d2:   48 c1 ea 20             shr    $0x20,%rdx
> >    f15d6:   0f 30                   wrmsr  
> >    f15d8:   c3                      retq   
> > 
> > It is the *wrmsr* inside the apic_timer_set to generate the fault.
> > Since I'm not expert of x86 (I'm more an embedded guy), I'm asking for tips 
> > and ideas.
> 
> Interesting. This writes to a 32-bit x2APIC register. The manual states:
> "The upper 32-bits of all x2APIC MSRs (except for the ICR) are
> reserved." But the timer value calculation let EDX (lower part of RDX)
> become non-zero.
> 
> > RAX: 0x000000044b82f9d8 RBX: 0x00000000000f060f RCX: 0x0000000000000838
> > RDX: 0x0000000000000004 RSI: 0x0000000000000a36 RDI: 0xffffffffffffe134
> 
> Never tested if hardware actually explodes over this, but it would have
> the right to do so. Simple check: confine ticks / NS_PER_SEC to 32 bits
> and see if that resolves the crash.
> 
> But that may cause issues regarding the desired timeout. A careful
> analysis of what happens here /wrt timeout calculation will be needed.
> E.g. what is the timeout_ns value in those cases?
> 
> As you copied from apic-demo and use the inmates library, those may
> share the issue.
> 
> Jan

Thank You,

I was able to fix the previous issue.
Moreover I chaged the APIC Timer configuration, actually I'm using it as 
TSC-Deadline, getting better frequency stability in PWM generation.

But adding features at my demo I discovered what I think be a race error that 
could happen when there's a concurrence with a "Instruction Trap" (like the one 
needed to handle In/Out instruction) and a local IRQ (the one generated by APIC 
Timer).

Whe this scenario happens seams that the Context of main function is not 
correctly restored (volatile registers are corrupted, in particular %EDX 
register used as source register for In instruction is zeroed).

Exception Message:

FATAL: Invalid PIO read, port: 0 size: 1
RIP: 0x00000000000f0228 RSP: 0x00000000000dffd0 FLAGS: 246
RAX: 0x0000000000000000 RBX: 0x00000000000f05a8 RCX: 0x0000000000000000
RDX: 0x0000000000000000 RSI: 0x0000000000000a35 RDI: 0x0000000000000a36
CS: 10 BASE: 0x0000000000000000 AR-BYTES: a09b EFER.LMA 1
CR0: 0x0000000080010031 CR3: 0x00000000000f3000 CR4: 0x0000000000002020
EFER: 0x0000000000000500
Parking CPU 3 (Cell: "pwm-demo")
Closing cell "pwm-demo"
Page pool usage after cell destruction: mem 4316/16327, remap 16459/131072
CPU 3 received SIPI, vector 98

Faulty Code:

static inline u8 inb(u16 port)
{
   f021a:       89 f8                   mov    %edi,%eax
   f021c:       66 89 44 24 ec          mov    %ax,-0x14(%rsp)
        u8 v;
        asm volatile("inb %1,%0" : "=a" (v) : "dN" (port));
   f0221:       0f b7 44 24 ec          movzwl -0x14(%rsp),%eax
   f0226:       89 c2                   mov    %eax,%edx
   f0228:       ec                      in     (%dx),%al
   f0229:       88 44 24 ff             mov    %al,-0x1(%rsp)
        return v;
   f022d:       0f b6 44 24 ff          movzbl -0x1(%rsp),%eax
}
   f0232:       c3 

Faulty Code Caller:
#include <inmate.h>

#define GPIO_PORT_COMMAND           0xA35U
#define GPIO_PORT_DATA              0xA36U
#define GPIO_COMMAND_READ_ALL       0xA2U

/** Return all I/O flags. */
static inline u8 gpio_get_pins(void) {
    outb(GPIO_COMMAND_READ_ALL, GPIO_PORT_COMMAND);
    return inb(GPIO_PORT_DATA);
}


I added an inmate code that re-generate the problem (I'm working on Industrial 
PC with an I/O port, I'm trying to generate the same problem using a "standard 
In port" for x86).

I added the following lines on apic-demo.c cell to make the code works

/* I need to access 0xa35 and 0xa36 only, but I have to open 0xa37 too,
   don't know why... (mask should be ~0x30, not ~0x70).
   Watchdog ports (0xa15, 0xa16, 0xa1a) are ok.
*/
        .pio_bitmap = {
                [     0/8 ...  0x3f7/8] = -1,
                [ 0x3f8/8 ...  0x3ff/8] = 0,
                [ 0x400/8 ...  0xa0f/8] = -1,
                [ 0xa10/8 ...  0xa17/8] = ~0x60,
                [ 0xa18/8 ...  0xa1f/8] = ~0x4,
                [ 0xa20/8 ...  0xa2f/8] = -1,
                [ 0xa30/8 ...  0xa37/8] = ~0x70,
                [ 0xa38/8 ... 0xe00f/8] = -1,
                [0xe010/8 ... 0xe017/8] = 0,
                [0xe018/8 ... 0xffff/8] = -1,
        },

-- 
You received this message because you are subscribed to the Google Groups 
"Jailhouse" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.
#include <inmate.h>

#define APIC_TIMER_VECTOR   (32U)

#define KILO (1000U)
#define MEGA (1000000U)

#define TICKS_TO_NANO(TICKS, REF_FREQ_HZ)       \
  (((TICKS) / KILO)?                            \
    ((TICKS) * KILO) / ((REF_FREQ_HZ) / MEGA):  \
    ((TICKS) * MEGA) / ((REF_FREQ_HZ) / KILO))

/*==============================================================================
                                GPIO Drv
 =============================================================================*/
#define GPIO_PORT_COMMAND           0xA35U
#define GPIO_PORT_DATA              0xA36U

#define GPIO_COMMAND_READ_ALL       0xA2U
#define GPIO_COMMAND_READ_IGNITION  0xF2U
#define GPIO_COMMAND_WRITE_ALL      0xA1U

#define GPIO_PIN_0                  0x0U
#define GPIO_PIN_1                  0x1U
#define GPIO_PIN_2                  0x2U
#define GPIO_PIN_3                  0x3U
#define GPIO_PIN_4                  0x4U
#define GPIO_PIN_5                  0x5U
#define GPIO_PIN_6                  0x6U
#define GPIO_PIN_7                  0x7U

#define GPIO_PIN_0_MASK             0x1U
#define GPIO_PIN_1_MASK             0x2U
#define GPIO_PIN_2_MASK             0x4U
#define GPIO_PIN_3_MASK             0x8U
#define GPIO_PIN_4_MASK             0x10U
#define GPIO_PIN_5_MASK             0x20U
#define GPIO_PIN_6_MASK             0x40U
#define GPIO_PIN_7_MASK             0x80U

#define GPIO_PIN_INPUT_0            GPIO_PIN_0
#define GPIO_PIN_INPUT_1            GPIO_PIN_1
#define GPIO_PIN_INPUT_2            GPIO_PIN_2
#define GPIO_PIN_INPUT_3            GPIO_PIN_3
#define GPIO_PIN_OUTPUT_0           GPIO_PIN_4
#define GPIO_PIN_OUTPUT_1           GPIO_PIN_5
#define GPIO_PIN_OUTPUT_2           GPIO_PIN_6
#define GPIO_PIN_OUTPUT_3           GPIO_PIN_7

#define GPIO_PIN_IGNITION           GPIO_PIN_INPUT_3

#define WATCHDOG_PORT_DATA          0xA16U
#define WATCHDOG_PORT_COMMAND       0xA15U
#define WATCHDOG_PORT_PME           0xA1AU
#define WATCHDOG_VALUE_ENABLE       0x20U
#define WATCHDOG_VALUE_PME_ENABLE   0x40U
#define WATCHDOG_VALUE_DISABLE      0x0U
#define WATCHDOG_TIME_SECONDS       0x0U
#define WATCHDOG_TIME_MINUTES       0x1U

/** Return all I/O flags. */
static inline u8 gpio_get_pins(void) {
    outb(GPIO_COMMAND_READ_ALL, GPIO_PORT_COMMAND);
    return inb(GPIO_PORT_DATA);
}

/** Write \b all I/O flags. */
static inline void gpio_set_pins(u8 value) {
    outb(GPIO_COMMAND_WRITE_ALL, GPIO_PORT_COMMAND);
    outb(value, GPIO_PORT_DATA);
}

/**
 * \brief Read pin pin from flags.
 * \param[in] pin The pin \b index, [0-7].
 * \return The bit status, 0 or 1.
 * \note It is recommended to use GPIO_PIN_X or GPIO_PIN_INPUT/OUTPUT_X macros.
 */
static inline u8 gpio_get_pin(u8 pin) {
    return (gpio_get_pins() >> pin) & 1;
}

/** Return ignition status, 0 or 1. */
static inline u8 gpio_get_ignition(void) {
    outb(GPIO_COMMAND_READ_IGNITION, GPIO_PORT_COMMAND);
    return (gpio_get_pins() >> GPIO_PIN_IGNITION) & 1;
}

/**
 * \brief Write the value value to pin pin.
 * \param[in] pin The pin \b index, [0-7].
 * \param[in] value The value of the pin to write, must be 0 or 1.
 * \note It is recommended to use GPIO_PIN_X or GPIO_PIN_INPUT/OUTPUT_X macros.
 */
static inline void gpio_set_pin(u8 pin, u8 value) {
    u8 pins = gpio_get_pins();
    pins &= ~(1 << pin);
    pins |= ((value & 1) << pin);
    gpio_set_pins(pins);
}

/**
 * \brief Toggle the pin bit.
 * \param[in] pin The pin \b index, [0-7].
 * \note It is recommended to use GPIO_PIN_X or GPIO_PIN_INPUT/OUTPUT_X macros.
 */
static inline void gpio_toggle_pin(u8 pin) {
    gpio_set_pins(gpio_get_pins() ^ (1 << pin));
}

/**
 * \brief Toggle flags with mask mask.
 * \param[in] mask The mask to xor old flags value with.
 */
static inline void gpio_toggle_pins(u8 mask) {
    gpio_set_pins(gpio_get_pins() ^ mask);
}

/*==============================================================================
                                 Application Flag
 =============================================================================*/
bool volatile app_terminate;

/*==============================================================================
                                BEGIN PWM Driver
 =============================================================================*/
#define PIN_PWM GPIO_PIN_OUTPUT_1
#define PWM_MSK (1 << PIN_PWM)

static u64 tsc_freq_hz;

static volatile u64 jitter_cnt;
static volatile u64 jitter_max;
static volatile u64 jitter_avg;
static volatile u64 jitter_tot;
static volatile u64 jitter_min = ((u64)-1);

#define PWM_FREQUENCY_HZ      (6000U)
#define DATA_XCHG_FREQ_HZ     (20U)
#define DATA_XCHG_TICKS       \
  ((PWM_FREQUENCY_HZ*2U)/DATA_XCHG_FREQ_HZ)
#define ENCODER_FREQ_HZ       (PWM_FREQUENCY_HZ)
#define ENCODER_TICKS         \
  ((PWM_FREQUENCY_HZ*2U)/ENCODER_FREQ_HZ)

/* 10s */
#define PWM_DUTY_SLICES           (100U)

#define PWM_MIN_DUTY_SLICES (1U)

#define PWM_STARTING_DUTY         (70U)
//#define PWM_STARTING_DUTY         PWM_MIN_DUTY_SLICES

static volatile u16       pwm_duty;
static volatile u64       pwm_expected_ticks;

enum pwm_front {
  PWM_ZERO_TO_ONE_FRONT = 0U,
  PWM_ONE_TO_ZERO_FRONT
};

static u64 pwm_get_time_next_front(enum pwm_front front) {
  u64 pwm_time_next_front;
  u16 front_duty;
  if (front == PWM_ONE_TO_ZERO_FRONT) {
    front_duty = pwm_duty;
  } else {
    front_duty = (PWM_DUTY_SLICES - pwm_duty);
  }
  pwm_time_next_front = ((tsc_freq_hz * front_duty) +
    ((PWM_FREQUENCY_HZ * PWM_DUTY_SLICES)/2U)) /
      (PWM_FREQUENCY_HZ * PWM_DUTY_SLICES);
  return pwm_time_next_front;
}

static u64 rdtsc(void)
{
#ifdef __x86_64__
	u32 lo, hi;

	asm volatile("rdtsc" : "=a" (lo), "=d" (hi));
	return (u64)lo | (((u64)hi) << 32);
#else
	u64 v;

	asm volatile("rdtsc" : "=A" (v));
	return v;
#endif
}

#define X2APIC_LVTT   (0x832U)
#define X2APIC_TSC_DL (0x6E0U)

static inline void apic_timer_tsc_dl_init(unsigned int vector) {
  /* We set APIC Timer as TSC-Deadline  (2U << 17U) + vector */
  write_msr(X2APIC_LVTT, (2U << 17U) | vector);
}

static inline void apic_timer_tsc_dl(u64 dl)
{
  write_msr(X2APIC_TSC_DL, dl);
}

static inline void pwm_setup_timer(u64 pwm_time_next_front) {
  pwm_expected_ticks = rdtsc() + pwm_time_next_front;
  apic_timer_tsc_dl(pwm_expected_ticks);
}

static void pwm_start ( u16 duty ) {
  if ( duty > (PWM_DUTY_SLICES - PWM_MIN_DUTY_SLICES) ) {
    duty = (PWM_DUTY_SLICES - PWM_MIN_DUTY_SLICES);
  } if ( duty < PWM_MIN_DUTY_SLICES ) {
    duty = PWM_MIN_DUTY_SLICES;
  }
  /* Set-up configuration */
  pwm_duty      = duty;

  /* Assure that output is zero */
  gpio_set_pin(PIN_PWM, 0);
  /* Configure APIC Timer as TSC-Deadline */
  apic_timer_tsc_dl_init(APIC_TIMER_VECTOR);
  pwm_expected_ticks = rdtsc();
  /* Start with a Zero-To-One Front */
  pwm_setup_timer(pwm_get_time_next_front(PWM_ZERO_TO_ONE_FRONT));
}

static void pwm_front_irq( void )
{
  u8 out;
  u64 pwm_time_next_front;
  /* Get Jitter measurement */
  u64 const delta = rdtsc() - pwm_expected_ticks;

  /* Handle Output */
  /* Read OUT register */
  out = gpio_get_pins();
  if ( out & PWM_MSK ) {
    out &= ~PWM_MSK;
    pwm_time_next_front = pwm_get_time_next_front(PWM_ZERO_TO_ONE_FRONT);
  } else {
    out |= PWM_MSK;
    pwm_time_next_front = pwm_get_time_next_front(PWM_ONE_TO_ZERO_FRONT);
  }
  /* Commit Changes */
  gpio_set_pins(out);

  /* Save min/MAX Jitter */
  if (delta < jitter_min) {
    jitter_min = delta;
  }
  if (delta > jitter_max) {
    jitter_max = delta;
  }
  /* Increment the counter value (I DO NOT HANDLE WRAP AROUND,
     I hope that in 8-10 hours do not wrap!!!) */
  ++jitter_cnt;
  jitter_tot += delta;
  jitter_avg = jitter_tot / jitter_cnt;

  if (!app_terminate) {
    pwm_setup_timer(pwm_time_next_front);
  } else {
    /* If the application is terminated: Assure that output is zero */
    gpio_set_pin(PIN_PWM, 0);
  }
}
/*==============================================================================
                                END PWM Driver
 =============================================================================*/

void inmate_main(void)
{
  comm_region->cell_state = JAILHOUSE_CELL_RUNNING_LOCKED;

  tsc_freq_hz = tsc_init();
  printk("Calibrated TSC frequency: %lu.%03u kHz\n", tsc_freq_hz / 1000,
    tsc_freq_hz % 1000);

  int_init();
  int_set_handler(APIC_TIMER_VECTOR, pwm_front_irq);

  pwm_start(PWM_STARTING_DUTY);

  asm volatile("sti");

  while (!app_terminate) {
    u8 volatile gpio_in;
    asm volatile("hlt");
    /* Read Multiple Times GPIO => Issue Multiple TRAPS, to enanche the
       chance of the RACE with local APIC Timer IRQ... */
    gpio_in = gpio_get_pins();
    gpio_in = gpio_get_pins();
    gpio_in = gpio_get_pins();
    gpio_in = gpio_get_pins();
    gpio_in = gpio_get_pins();
    gpio_in = gpio_get_pins();
    gpio_in = gpio_get_pins();
    gpio_in = gpio_get_pins();
    gpio_in = gpio_get_pins();
    gpio_in = gpio_get_pins();
    ((void)gpio_in);
    {
      static u32 decimator;
      if (++decimator == DATA_XCHG_TICKS) {
        printk(
            "PWM parameters, duty:<%ld>\n"
            "PWM timer jitter, avg:<%6ld> ns, min:<%6ld> ns, max:<%6ld> ns\n"
            "PWM AVG on:<%d> Samples\n",
            pwm_duty,
            jitter_avg,
            jitter_min,
            jitter_max,
            jitter_cnt
        );
        if ( ++pwm_duty >= PWM_DUTY_SLICES - PWM_MIN_DUTY_SLICES ) {
            pwm_duty = PWM_MIN_DUTY_SLICES;
        }
        decimator = 0U;
      }
    }

    switch (comm_region->msg_to_cell) {
      case JAILHOUSE_MSG_SHUTDOWN_REQUEST:
        gpio_set_pin(PIN_PWM, 0);
        app_terminate = true;
        break;
      default:
        jailhouse_send_reply_from_cell(comm_region, JAILHOUSE_MSG_UNKNOWN);
        break;
    }
  }

  printk("PWM demo Stopped\n");
  comm_region->cell_state = JAILHOUSE_CELL_SHUT_DOWN;
}

Reply via email to