xenomai4的dovetail学习(2)——oob和中断管理


OUT-OF-BAND启用和禁用

在将中断送到带外处理程序之前,需要启用oob中断。enable_oob_stage用于在所有CPU上设置带外中断阶段,并启用该阶段。

int enable_oob_stage(const char *name)
{
	struct irq_event_map *map;
	struct irq_stage_data *p;
	int cpu, ret;
	
	// 检查是否已有带外中断阶段
	if (oob_stage_present()) 
		return -EBUSY;

	/* Set up the out-of-band interrupt stage on all CPUs. */
	/* 遍历所有可能的CPU,初始化每个CPU的带外中断阶段数据 */
	for_each_possible_cpu(cpu) {
		p = &per_cpu(irq_pipeline.stages, cpu)[1];
		map = p->log.map; /* save/restore after memset(). */
		memset(p, 0, sizeof(*p));
		p->stage = &oob_stage;
		memset(map, 0, sizeof(struct irq_event_map));
		p->log.map = map;
#ifdef CONFIG_DEBUG_IRQ_PIPELINE
		p->cpu = cpu;
#endif
	}
	
	/* 调用架构特定的函数 arch_enable_oob_stage 启用带外中断阶段。*/
	ret = arch_enable_oob_stage();
	if (ret)
		return ret;
	
	/*设置带外中断阶段的名称和索引,并打印信息。*/
	oob_stage.name = name;
	smp_wmb();
	oob_stage.index = 1;

	pr_info("IRQ pipeline: high-priority %s stage added.\n", name);

	return 0;
}

disable_oob_stage()用于禁用带外(OOB)阶段,通常是因为evl和pipeline启用失败后调用:

void disable_oob_stage(void)
{
	const char *name = oob_stage.name;

	WARN_ON(!running_inband() || !oob_stage_present());

	oob_stage.index = 0;
	smp_wmb();

	pr_info("IRQ pipeline: %s stage removed.\n", name);
}

stage升级

为了运行特定的函数,需要从inband升级到oob,调用run_oob_call函数。run_oob_call()是一个轻量级操作,在调用期间将CPU切换到带外中断阶段,不切换上下文:

  • 保存中断状态并切换到OOB阶段;
  • 执行回调函数;
  • 根据入口和出口状态,同步所有或当前阶段;
  • 恢复中断状态并返回回调结果。
/**
*      run_oob_call - escalate function call to the oob stage
*      @fn:    address of routine
*      @arg:   routine argument
*
*      Make the specified function run on the oob stage, switching
*      the current stage accordingly if needed. The escalated call is
*      allowed to perform a stage migration in the process.
*/
int notrace run_oob_call(int (*fn)(void *arg), void *arg)
{
   struct irq_stage_data *p, *old;
   struct irq_stage *oob;
   unsigned long flags;
   int ret, s;

   flags = hard_local_irq_save();

   /* Switch to the oob stage if not current. */
   p = this_oob_staged();
   oob = p->stage;
   old = current_irq_staged;
   if (old != p)
   	switch_oob(p);

   s = test_and_stall_oob();
   barrier();
   ret = fn(arg);
   hard_local_irq_disable();
   if (!s)
   	unstall_oob();

   /*
    * The exit logic is as follows:
    *
    *    ON-ENTRY  AFTER-CALL  EPILOGUE
    *
    *    oob       oob         sync current stage if !stalled
    *    inband    oob         switch to inband + sync all stages
    *    oob       inband      sync all stages
    *    inband    inband      sync all stages
    *
    * Each path which has stalled the oob stage while running on
    * the inband stage at some point during the escalation
    * process must synchronize all stages of the pipeline on
    * exit. Otherwise, we may restrict the synchronization scope
    * to the current stage when the whole sequence ran on the oob
    * stage.
    */
   p = this_oob_staged();
   if (likely(current_irq_staged == p)) {
   	if (old->stage == oob) {
   		if (!s && stage_irqs_pending(p))
   			sync_current_irq_stage();
   		goto out;
   	}
   	switch_inband(this_inband_staged());
   }

   sync_irq_stage(oob);
out:
   hard_local_irq_restore(flags);

   return ret;
}

irq_switch_oob()函数开启或关闭指定中断切换到oob阶段处理。当一个中断已经被请求,但没有指定 IRQF_OOB 标志时,可以使用 irq_switch_oob() 来手动启用或禁用带外交付。这在某些情况下非常有用,例如,当系统需要动态调整中断处理的优先级时。

irq_switch_oob(DEVICE_IRQ, true); // 启用带外交付
irq_switch_oob(DEVICE_IRQ, false); // 禁用带外交付

int irq_switch_oob(unsigned int irq, bool on)
{
	struct irq_desc *desc;
	unsigned long flags;
	int ret = 0;

	desc = irq_get_desc_lock(irq, &flags, 0);
	if (!desc)
		return -EINVAL;

	if (!desc->action)
		ret = -EINVAL;
	else if (on)
		irq_settings_set_oob(desc);
	else
		irq_settings_clr_oob(desc);

	irq_put_desc_unlock(desc, flags);

	return ret;
}

oob中断

Dovetail引入新的中断类型标志,带有该标志的IRQ在带外阶段处理:

/* IRQF_OOB - Interrupt is attached to an out-of-band handler living
 -            on the heading stage of the interrupt pipeline
 -            (CONFIG_IRQ_PIPELINE).  It may be delivered to the
 -            handler any time interrupts are enabled in the CPU,
 -            regardless of the (virtualized) interrupt state
 -            maintained by local_irq_save/disable().
 */
#define IRQF_OOB		0x00200000

oob中断同样使用linux kernel里的中断注册和注销函数。
linux kernel中常见的三种中断注册函数:

  • setup_irq():用于早期注册特殊中断,通常在内核初始化阶段,用于注册那些需要在系统启动时就初始化的中断。
  • request_irq() :用于注册设备中断,适用于大多数设备驱动程序。
  • __request_percpu_irq():用于注册每个CPU中断,适用于那些需要为每个 CPU 分配独立中断处理程序的场景。

常见的两种注销中断函数:free_irq()和free_percpu_irq()。
若oob中断是共享的,同一中断通道上的所有其他处理程序都需要具有IRQF_OOB标志。

以下是注册oob中断流程:

#include <linux/interrupt.h>

static irqreturn_t oob_interrupt_handler(int irq, void *dev_id)
{
	...
	return IRQ_HANDLED;
}

init __init driver_init_routine(void)
{
	int ret;

	...
	ret = request_irq(DEVICE_IRQ, oob_interrupt_handler,
			  IRQF_OOB, "Out-of-band device IRQ",
			  device_data);
	if (ret)
		goto fail;

	return 0;
fail:
	/* Unwind upon error. */
	...
}

通知oob irq进入/退出evl

中断发生和退出时,需要通知evl。evl要阻止中断结束时任何进一步的任务重新调度,由evl来进行调度。
当 Dovetail 收到硬件中断时,会在进入中断处理流程时调用 irq_enter_pipeline(),在退出中断处理流程时调用 irq_exit_pipeline()。这些函数作为钩子,允许evl对中断事件做出反应。例如,evl可能会在中断上下文中阻止进一步的任务调度,以确保实时任务的可预测性。

static inline void irq_enter_pipeline(void)
{
#ifdef CONFIG_EVL
	evl_enter_irq();
#endif
}

static inline void irq_exit_pipeline(void)
{
#ifdef CONFIG_EVL
	evl_exit_irq();
#endif
}

禁用/启用CPU中断

由于Dovetail将linux的中断虚拟化了,所以要将对应的中断控制函数进行修改。当pipeline启用时,常规的 local_irq_*() 内核 API 只能控制带内(in-band)阶段的中断禁用, **hard_local_irq_*()**控制实际的硬件中断。具体函数如下:
在这里插入图片描述

虽然inband中断被虚拟化,但在开关带内中断时依然需要关闭硬件中断,然后开启inband中断后检查是否有挂起的中断且不在管道中,如果有则同步当前中断阶段并恢复中断,否则直接恢复中断。

#define local_irq_enable()	do { raw_local_irq_enable(); } while (0)
#define raw_local_irq_enable()		arch_local_irq_enable()
static inline notrace void arch_local_irq_enable(void)
{
	barrier();
	inband_irq_enable(); // 启用虚拟中断
}
/**
 *	inband_irq_enable - enable interrupts for the inband stage
 *
 *	Enable interrupts for the inband stage, allowing interrupts to
 *	preempt the in-band code. If in-band IRQs are pending for the
 *	inband stage in the per-CPU log at the time of this call, they
 *	are played back.
 *
 *      The caller is expected to tell the tracer about the change, by
 *      calling trace_hardirqs_on().
 */
notrace void inband_irq_enable(void)
{
	/*
	 * We are NOT supposed to enter this code with hard IRQs off.
	 * If we do, then the caller might be wrongly assuming that
	 * invoking local_irq_enable() implies enabling hard
	 * interrupts like the legacy I-pipe did, which is not the
	 * case anymore. Relax this requirement when oopsing, since
	 * the kernel may be in a weird state.
	 */
	WARN_ON_ONCE(irq_pipeline_debug() && hard_irqs_disabled());
	__inband_irq_enable();
}

static void __inband_irq_enable(void)
{
	struct irq_stage_data *p;
	unsigned long flags;

	check_inband_stage();

	flags = hard_local_irq_save();

	unstall_inband_nocheck();

	p = this_inband_staged();
	if (unlikely(stage_irqs_pending(p) && !in_pipeline())) {
		sync_current_irq_stage();
		hard_local_irq_restore(flags);
		preempt_check_resched();
	} else {
		hard_local_irq_restore(flags);
	}
}

禁用/启用oob中断

与inband中断类似,oob中断也有一个stall位标志。

  • 当stall=1时,带外阶段的事件日志中可能挂起的中断不会被处理;
  • 当stall=0时,挂起的带外处理程序会被触发。
static __always_inline void oob_irq_disable(void)
{
	hard_local_irq_disable(); // 禁用硬件中断
	stall_oob(); // 设置stall位
}

在这里插入图片描述

oob的IPI(Inter-Processor Interrupt)

带外 IPI 是一种特殊的中断机制,用于在多处理器系统中通知远程 CPU 执行特定的操作。这些操作通常与任务调度、定时器管理等实时性要求较高的任务相关。带外 IPI 与常规的中断处理不同,它们通过一个独立的通道发送,以确保高优先级的任务能够及时得到处理。

  • RESCHEDULE_OOB_IPI:用于跨 CPU 的任务重新调度请求。例如,一个在CPU #1上休眠的任务可能会被从CPU #0发出的系统调用解除阻塞:在这种情况下,运行在CPU #0上的调度程序代码应该告诉CPU #1它应该重新调度。通常,EVL核心通过test_resched()进行IPI调度。
/* hard irqs off. */
static __always_inline bool test_resched(struct evl_rq *this_rq)
{
	bool need_resched = evl_need_resched(this_rq);

#ifdef CONFIG_SMP
	/* Send resched IPI to remote CPU(s). */
	if (unlikely(!cpumask_empty(&this_rq->resched_cpus))) {
		irq_send_oob_ipi(RESCHEDULE_OOB_IPI, &this_rq->resched_cpus);
		cpumask_clear(&this_rq->resched_cpus);
		this_rq->local_flags &= ~RQ_SCHED;
	}
#endif
	if (need_resched)
		this_rq->flags &= ~RQ_SCHED;

	return need_resched;
}
  • TIMER_OOB_IPI:用于跨 CPU 的定时器重新调度请求。当某个 CPU 上的定时器状态发生变化(例如,停止定时器或迁移定时器中断)时,可以通过发送 TIMER_OOB_IPI 通知其他 CPU 更新其硬件定时器设置。
  • CALL_FUNCTION_OOB_IPI:用于在带外阶段调用smp_call_function_oob()执行特定的回调函数。这与常规的 smp_call_function_single() 函数类似,但回调函数在带外阶段执行。

使用irq_send_oob_ipi()发送带外 IPI 的函数,参数irq只能是 RESCHEDULE_OOB_IPI、TIMER_OOB_IPI 或 CALL_FUNCTION_OOB_IPI类型的IPI,cpumask指定哪些 CPU 应该接收该 IPI。为了接收这些 IPI,必须为它们设置带外处理程序,并指定 IRQF_OOB 标志。irq_send_oob_ipi() 函数在内部对调用者进行了序列化处理,因此可以从带内或带外阶段调用。

// arm架构下的irq_send_oob_ipi实现
void irq_send_oob_ipi(unsigned int irq,
		const struct cpumask *cpumask)
{
	unsigned int sgi = irq - ipi_irq_base;

	if (WARN_ON(irq_pipeline_debug() &&
		    (sgi < OOB_IPI_OFFSET ||
		     sgi >= OOB_IPI_OFFSET + OOB_NR_IPI)))
		return;

	/* Out-of-band IPI (SGI1-2). */
	__smp_cross_call(cpumask, sgi);
}

注入IRQ

irq_inject_pipeline() 函数用于在当前 CPU 上注入一个 IRQ 事件,模拟硬件中断的发生。这在某些特定情况下非常有用,例如需要在软件中触发中断处理程序以处理某些紧急任务。需要确保硬件中断禁用,以避免在记录过程中被其他中断打断。

除了模拟硬件中断外,还可以直接将 IRQ 事件记录到中断日志中进行处理。irq_post_inband()irq_post_oob()用于直接记录 IRQ 事件的函数,在特定情况下直接将IRQ 事件标记为挂起,而不需要通过完整的中断处理流程。

  • irq_post_inband:当前阶段是带外阶段,事件需要推迟到带内阶段处理时。当前阶段是带内阶段但关中断,事件需要标记为挂起,直到开中断时再处理。
  • irq_post_oob:当前阶段是带外阶段但关中断,将 IRQ 事件直接标记为挂起在当前 CPU 的带外阶段日志中。

拓展 IRQ work API

由于不能直接在oob阶段调用inband函数,所以使用被pipeline拓展的irq_work_queue()irq_work_queue_on()存储这些函数。当切换到inband后,从队列中取出执行。
pipeline将这种irq引入,称为
合成中断(Synthetic IRQs)
。这种中断完全是软件产生的,不涉及架构和硬件。由于通用管道流程适用于合成中断,因此可以将此类中断附加到带外(out-of-band)和/或带内(in-band)处理程序,就像设备中断一样。
合成中断软中断(softirqs)本质上是不同的:后者仅存在于带内上下文中,因此无法触发带外活动。
合成中断向量从 synthetic_irq_domain 分配,使用 irq_create_direct_mapping() 函数。

void __init irq_pipeline_init(void)
{
...
/*
	 * So far, internally we need one sirq for the tick proxy and
	 * another one to relay the inband work. The companion core
	 * may need a few of them as well. Assume 1024 is (more than)
	 * enough system-wide.
	 *
	 * CAVEAT: __irq_domain_add() is a bit fragile, max_irq must
	 * not translate to a negative value when coerced to a signed
	 * int, otherwise the domain creation would fail.
	 */
synthetic_irq_domain = irq_domain_add_nomap(NULL, 1024,
						    &sirq_domain_ops,
						    NULL);
...
}

可以安装合成中断处理程序,用于根据来自带外环境的调度请求在带内上运行,如下所示:

#include <linux/irq_pipeline.h>

static irqreturn_t sirq_handler(int sirq, void *dev_id)
{
	do_in_band_work();

	return IRQ_HANDLED;
}

static struct irqaction sirq_action = {
        .handler = sirq_handler,
        .name = "In-band synthetic interrupt",
        .flags = IRQF_NO_THREAD,
};

unsigned int alloc_sirq(void)
{
	unsigned int sirq;

	sirq = irq_create_direct_mapping(synthetic_irq_domain);
	if (!sirq)
		return 0;
	
	setup_percpu_irq(sirq, &sirq_action);

	return sirq;
}

也可以安装合成中断处理程序,用于在带内环境触发时从带外阶段运行,如下所示:

static irqreturn_t sirq_oob_handler(int sirq, void *dev_id)
{
	do_out_of_band_work();

	return IRQ_HANDLED;
}

unsigned int alloc_sirq(void)
{
	unsigned int sirq;

	sirq  = irq_create_direct_mapping(synthetic_irq_domain);
	if (!sirq)
		return 0;
     
	ret = __request_percpu_irq(sirq, sirq_oob_handler,
                                   IRQF_OOB,
                                   "Out-of-band synthetic interrupt",
                                   dev_id);
	if (ret) {
        	irq_dispose_mapping(sirq);
		return 0;
	}

	return sirq;
}

可以从带外上下文以两种不同的方式调度(或发布)sirq_handler() 在带内上下文中的执行:

  • 使用通用注入服务:
irq_inject_pipeline(sirq);
  • 使用轻量级注入方法(需要在 CPU 中禁用中断):
unsigned long flags = hard_local_irqsave();
irq_post_inband(sirq);
hard_local_irqrestore(flags);

类似的,从带外阶段触发 SIRQ 也有上述两种方式。但如果使用irq_post_oob,并不会立即触发oob中断,因为只是将irq挂起在带外日志中,需要同步中断日志后才会执行。但irq_inject_pipeline模拟硬件中断,因此可以立即执行

### Xenomai 4 on AMD Platform Installation and Configuration Guide For the installation of Xenomai 4 on an AMD platform, it is important to ensure that both hardware compatibility and software requirements are met. The process involves setting up a real-time kernel alongside configuring specific parameters tailored for AMD processors. #### Prerequisites Before proceeding with the installation, verify that the system meets these prerequisites: - A compatible version of Linux distribution such as Ubuntu or Fedora. - An AMD processor supporting SVM (Secure Virtual Machine) technology which enhances virtualization performance[^1]. #### Kernel Preparation To achieve optimal results when running Xenomai applications on AMD platforms, prepare a custom-built kernel configured specifically for this purpose. This includes enabling certain options within the `.config` file during compilation like `CONFIG_X86_64`, ensuring support for 64-bit architecture common in modern AMD CPUs. ```bash make menuconfig ``` Ensure configurations related to preemption models (`PREEMPT_RT`) are selected appropriately since they play a crucial role in achieving low latency required by many embedded systems using Xenomai framework. #### Installing Dependencies Install necessary dependencies before building from source code. On Debian-based distributions including Ubuntu, use apt-get package manager commands similar to those below: ```bash sudo apt update && sudo apt install build-essential git cmake libtool autoconf pkg-config ``` These tools facilitate compiling third-party libraries essential for developing under Xenomai environment especially targeting AMD architectures where some unique features might require additional packages not present out-of-the-box. #### Building Xenomai Framework Clone official repository containing latest stable release branch suitable for your target machine's specifications then follow standard procedure outlined hereafter but pay attention particularly towards patches applicable only if you're working directly off master branches rather than tagged releases intended primarily for production environments: ```bash git clone https://github.com/xenomai/xenomai.git -b xenomai-3.x-stable cd xenomai/ ./bootstrap ./configure --enable-smp --with-core=cobalt make all sudo make install ``` Note how SMP capability has been explicitly enabled through configure script invocation; multi-threaded execution becomes increasingly relevant given today’s multicore nature prevalent among contemporary desktop/workstation class AMD Ryzen series processors. #### Post-installation Setup After successful completion of above steps, proceed further into post-install setup phase involving module loading along with verifying proper functioning via simple test programs provided inside examples directory shipped together with core sources downloaded earlier. ```bash sudo modprobe cobalt xnstat -r ``` This command sequence ensures Cobalt skin—the default choice—is active while also checking statistics about internal operations performed internally thus far after booting up modified OS image incorporating Xenomai enhancements aimed at improving determinism characteristics critical across various industrial automation scenarios leveraging advanced computing capabilities offered nowadays even outside traditional Intel territory thanks largely due competitive offerings brought forth recently by AMD itself. --related questions-- 1. What modifications should be made to optimize Xenomai 4 performance specifically for AMD EPYC servers? 2. How does one troubleshoot issues encountered during Xenomai installations on AMD-based machines? 3. Can you provide guidance on writing efficient real-time applications utilizing Xenomai APIs targeted toward AMD GPUs instead of just CPU cores? 4. Are there any known limitations or challenges associated with deploying Xenomai over AMD Threadripper workstations compared to other consumer-grade alternatives available currently?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值