SylixOS interrupt delay queue

Posted by techite on Tue, 14 Dec 2021 03:02:35 +0100

concept

When the kernel processes interrupt requests, it is required to process as many interrupts as possible in unit time, that is, the system requires the throughput of processing interrupts to be as large as possible. This requires that the interrupt handler should be as short and concise as possible, and there should be no time-consuming operations. However, most interrupt handlers are very complex and difficult to complete in a short time. In order to improve the responsiveness and concurrency of the system, it is necessary to solve the problems of short time requirements and large workload of balancing interrupt handler. SylixOS divides interrupt processing into two stages, namely, the top half and the bottom half:

  • The top half is generally an emergency hardware operation, which generally includes reading the interrupt status in the register, clearing the interrupt flag, and hanging the bottom half processing program in the execution queue of the bottom half;
  • The bottom half performs most of the time-consuming operations and can be interrupted by new interrupts.

If the interrupt processing program is simple enough, it does not need to be divided into top half and bottom half, but can be completed directly in the top half. The mechanism of the bottom half of SylixOS implementation is interrupt delay work queue (InterDefer).

Interface

Interfaceexplain
PLW_JOB_QUEUE API_InterDeferGet(ULONG ulCPUId);Obtain the interrupt delay queue of the corresponding CPU
ULONG API_InterDeferJobAdd(PLW_JOB_QUEUE pjobq, VOIDFUNCPTR pfunc, PVOID pvArg);Add a task to the interrupt delay processing queue
ULONG API_InterDeferJobDelete(PLW_JOB_QUEUE pjobq, BOOL bMatchArg, VOIDFUNCPTR pfunc, PVOID pvArg);Delete task from interrupt delay processing queue

realization

The interrupt delay work queue is implemented based on the kernel work queue, and an execution thread with priority 0 (the highest priority) is created. For multi-core processors, you can choose that each core has its own independent work queue, or you can use the single work queue mode like a single core.

/*********************************************************************************************************
**
**                                    China software open source organization
**
**                                   Embedded real-time operating system
**
**                                SylixOS(TM)  LW : long wing
**
**                               Copyright All Rights Reserved
**
**--------------File information--------------------------------------------------------------------------------
**
** File name: interdefer c
**
** Created by: Han Hui (Han Hui)
**
** Document creation date: May 9, 2016
**
** Description: interrupt delay queue processing
*********************************************************************************************************/
#define  __SYLIXOS_KERNEL
#include "../SylixOS/kernel/include/k_kernel.h"
/*********************************************************************************************************
  Crop configuration
*********************************************************************************************************/
#if LW_CFG_ISR_DEFER_EN > 0
/*********************************************************************************************************
  Der ISR queue per CPU
*********************************************************************************************************/
#if (LW_CFG_SMP_EN > 0) && (LW_CFG_ISR_DEFER_PER_CPU > 0)
static LW_JOB_QUEUE     _K_jobqIsrDefer[LW_CFG_MAX_PROCESSORS];
static LW_JOB_MSG       _K_jobmsgIsrDefer[LW_CFG_MAX_PROCESSORS][LW_CFG_ISR_DEFER_SIZE];
#else
static LW_JOB_QUEUE     _K_jobqIsrDefer[1];
static LW_JOB_MSG       _K_jobmsgIsrDefer[LW_CFG_ISR_DEFER_SIZE];
#endif                                                                  /*  LW_CFG_SMP_EN > 0           */
/*********************************************************************************************************
** Function name:__ interDeferTask
** Function Description: get interrupt stack usage
** Input: ulcpuid CPU number
**           pstFreeByteSize               Free stack size (can be LW_NULL)
**           pstUsedByteSize               Use stack size (can be LW_NULL)
** Output: 
** Global variables: 
** Calling module: 
*********************************************************************************************************/
static PVOID  _interDeferTask (PVOID  pvArg)
{
    PLW_JOB_QUEUE   pjobq = (PLW_JOB_QUEUE)pvArg;
    
    for (;;) {
        _jobQueueExec(pjobq, LW_OPTION_WAIT_INFINITE);
    }
    
    return  (LW_NULL);
}
/*********************************************************************************************************
** Function name:_ interDeferInit
** Function Description: initialize interrupt delay processing
** Input: NONE
** Output: NONE
** Global variables: 
** Calling module: 
*********************************************************************************************************/
VOID  _interDeferInit (VOID)
{
    CHAR                  cDefer[LW_CFG_OBJECT_NAME_SIZE] = "t_isrdefer";
    LW_CLASS_THREADATTR   threadattr;
    LW_OBJECT_HANDLE      ulId;
    
#if (LW_CFG_SMP_EN > 0) && (LW_CFG_ISR_DEFER_PER_CPU > 0)
    INT                   i;
    LW_CLASS_CPUSET       cpuset;
    
    LW_CPU_ZERO(&cpuset);
    
    API_ThreadAttrBuild(&threadattr, 
                        LW_CFG_THREAD_DEFER_STK_SIZE, 
                        LW_CFG_ISR_DEFER_PRIO, 
                        (LW_OPTION_THREAD_STK_CHK | 
                        LW_OPTION_THREAD_SAFE | 
                        LW_OPTION_OBJECT_GLOBAL |
                        LW_OPTION_THREAD_DETACHED |
                        LW_OPTION_THREAD_AFFINITY_ALWAYS), 
                        LW_NULL);
    
    LW_CPU_FOREACH (i) {
        if (_jobQueueInit(&_K_jobqIsrDefer[i], 
                          &_K_jobmsgIsrDefer[i][0], 
                          LW_CFG_ISR_DEFER_SIZE, LW_FALSE)) {
            _DebugHandle(__ERRORMESSAGE_LEVEL, "can not create ISR defer queue.\r\n");
            return;
        }
        
        lib_itoa(i, &cDefer[10], 10);
        API_ThreadAttrSetArg(&threadattr, &_K_jobqIsrDefer[i]);
        ulId = API_ThreadInit(cDefer, _interDeferTask, &threadattr, LW_NULL);
        if (ulId == LW_OBJECT_HANDLE_INVALID) {
            _DebugHandle(__ERRORMESSAGE_LEVEL, "can not create ISR defer task.\r\n");
            return;
        }
        
        LW_CPU_SET(i, &cpuset);
        API_ThreadSetAffinity(ulId, sizeof(LW_CLASS_CPUSET), &cpuset);  /*  Lock to specified CPU              */
        LW_CPU_CLR(i, &cpuset);
        
        API_ThreadStart(ulId);
    }
    
#else
    if (_jobQueueInit(&_K_jobqIsrDefer[0], 
                      &_K_jobmsgIsrDefer[0], 
                      LW_CFG_ISR_DEFER_SIZE, LW_FALSE)) {
        _DebugHandle(__ERRORMESSAGE_LEVEL, "can not create ISR defer queue.\r\n");
        return;
    }
    
    API_ThreadAttrBuild(&threadattr, 
                        LW_CFG_THREAD_DEFER_STK_SIZE, 
                        LW_CFG_ISR_DEFER_PRIO, 
                        (LW_OPTION_THREAD_STK_CHK | 
                        LW_OPTION_THREAD_SAFE | 
                        LW_OPTION_OBJECT_GLOBAL |
                        LW_OPTION_THREAD_DETACHED |
                        LW_OPTION_THREAD_AFFINITY_ALWAYS), 
                        &_K_jobqIsrDefer[0]);
    
    ulId = API_ThreadInit(cDefer, _interDeferTask, &threadattr, LW_NULL);
    if (ulId == LW_OBJECT_HANDLE_INVALID) {
        _DebugHandle(__ERRORMESSAGE_LEVEL, "can not create ISR defer task.\r\n");
        return;
    }
    
    API_ThreadStart(ulId);
#endif                                                                  /*  LW_CFG_SMP_EN > 0           */ 
}                                                                       /*  LW_CFG_ISR_DEFER_PER_CPU    */
/*********************************************************************************************************
** Function name: API_InterDeferGet
** Function Description: obtain the interrupt delay queue of the corresponding CPU
** Input: ulcpuid CPU number
** Output: interrupt delay queue
** Global variables: 
** Calling module: 
                                           API function
*********************************************************************************************************/
LW_API  
PLW_JOB_QUEUE  API_InterDeferGet (ULONG  ulCPUId)
{
    if (ulCPUId >= LW_NCPUS) {
        _ErrorHandle(ERANGE);
        return  (LW_NULL);
    }
    
#if (LW_CFG_SMP_EN > 0) && (LW_CFG_ISR_DEFER_PER_CPU > 0)
    return  (&_K_jobqIsrDefer[ulCPUId]);
#else
    return  (&_K_jobqIsrDefer[0]);
#endif                                                                  /*  LW_CFG_SMP_EN > 0           */ 
}                                                                       /*  LW_CFG_ISR_DEFER_PER_CPU    */
/*********************************************************************************************************
** Function name: API_InterDeferJobAdd
** Function Description: add a task to the interrupt delay processing queue
** Input: pjobq queue
**           pfunc         Processing function
**           pvArg         Processing parameters
** Output: ERROR CODE
** Global variables: 
** Calling module: 
                                           API function
*********************************************************************************************************/
LW_API  
ULONG  API_InterDeferJobAdd (PLW_JOB_QUEUE  pjobq, VOIDFUNCPTR  pfunc, PVOID  pvArg)
{
    if (!pjobq) {
        _ErrorHandle(EINVAL);
        return  (EINVAL);
    }

    return  (_jobQueueAdd(pjobq, pfunc, pvArg, LW_NULL, LW_NULL, LW_NULL, LW_NULL, LW_NULL));
}
/*********************************************************************************************************
** Function name: API_InterDeferJobDelete
** Function Description: delete task from interrupt delay processing queue
** Input: pjobq queue
**           bMatchArg     Judge whether to match parameters
**           pfunc         Processing function
**           pvArg         Processing parameters
** Output: NONE
** Global variables: 
** Calling module: 
                                           API function
*********************************************************************************************************/
LW_API  
ULONG  API_InterDeferJobDelete (PLW_JOB_QUEUE  pjobq, BOOL  bMatchArg, VOIDFUNCPTR  pfunc, PVOID  pvArg)
{
    if (!pjobq) {
        _ErrorHandle(EINVAL);
        return  (EINVAL);
    }
    
    _jobQueueDel(pjobq, (bMatchArg) ? 1 : 0,
                 pfunc, pvArg, LW_NULL, LW_NULL, LW_NULL, LW_NULL, LW_NULL);

    return  (ERROR_NONE);
}

#endif                                                                  /*  LW_CFG_ISR_DEFER_EN > 0     */
/*********************************************************************************************************
  END
*********************************************************************************************************/

usage

The following code shows the use of the bottom half of the SylixOS interrupt in driver development. When the kernel module is loaded, it creates a SylixOS work queue without delayed execution function and a thread. The thread is used to set the interrupt processing function of a GPIO and enable the interrupt. When the corresponding key interrupt is generated, the interrupt processing function will clear the interrupt, Insert the time-consuming operation into the work queue. When uninstalling the kernel module, clear the relevant settings of the GPIO and delete the work queue.

#define __SYLIXOS_STDIO
#define __SYLIXOS_KERNEL
#include <SylixOS.h>
#include <module.h>

#define KEY_NUM 36

PVOID _G_pvWorkQueue;
static INT _G_iIrqNum;

static VOID __workHandler(VOID)
{
 	printk("work handler function start.\n");
 	API_TimeSSleep(5);
 	printk("work handler function stop.\n");
}

static irqreturn_t GpioIsr (INT iGpioNum, ULONG ulVector)
{
	 API_GpioClearIrq(iGpioNum);
	 API_WorkQueueInsert(_G_pvWorkQueue,
						 0,
						 __workHandler,
						 LW_NULL,
						 LW_NULL,
						 LW_NULL,
						 LW_NULL,
						 LW_NULL,
						 LW_NULL);
	 return (ERROR_NONE);
}
	
static PVOID __keyThread (PVOID pvArg)
{
	 INT iError;
	 
	 iError = API_GpioRequestOne(KEY_NUM, LW_GPIOF_IN, "KEY");
	 if (iError != ERROR_NONE) {
	 		printk("failed to request gpio %d!\n", KEY_NUM);
	 		return (NULL);
	 }
	 _G_iIrqNum = API_GpioSetupIrq(KEY_NUM, LW_FALSE, 0);
	 if (_G_iIrqNum == PX_ERROR) {
	 		printk("failed to setup gpio %d irq!\n", KEY_NUM);
	 		return (NULL);
	}
 	iError = API_InterVectorConnect((ULONG)_G_iIrqNum,
                                    (PINT_SVR_ROUTINE)GpioIsr,
                                    (PVOID)KEY_NUM,
                                    "GpioIsr");
	 if (iError != ERROR_NONE) {
	 	printk("failed to connect GpioIsr!\n");
	 	return (NULL);
	 }
	 API_InterVectorEnable(_G_iIrqNum);
	 
	 return (NULL);
}

void module_init (void)
{
	LW_CLASS_THREADATTR threadattr;
	
	printk("interrupt_module init!\n");
	API_ThreadAttrBuild(&threadattr,
	                    4 * LW_CFG_KB_SIZE,
	                    LW_PRIO_NORMAL,
	                    LW_OPTION_THREAD_STK_CHK,
	                    LW_NULL);
	_G_pvWorkQueue = API_WorkQueueCreate("t_workqueue",
	                                     10,
	                                     FALSE,
	                                     0,
	                                     &threadattr);
	if (_G_pvWorkQueue == LW_NULL) {
		printk("WorkQueue create failed.\n");
		return;
	}
	API_ThreadCreate("t_key",
	                 (PTHREAD_START_ROUTINE)__keyThread,
	                 LW_NULL,
	                 LW_NULL);
}

void module_exit (void)
{
	API_InterVectorDisconnect((ULONG)_G_iIrqNum,
	                          (PINT_SVR_ROUTINE)GpioIsr,
	                          (PVOID)KEY_NUM);
	API_GpioFree(KEY_NUM);
	API_WorkQueueDelete(_G_pvWorkQueue);
	printk("interrupt_module exit!\n");
}

Load the module under the SylixOS Shell, and then press the specified key to trigger the interrupt:

#insmod ./interrupt.ko
interrupt_module init!
module interrupt.ko register ok, handle: 0x13338f0
work handler function start.
work handler function stop.

Uninstall the module under the SylixOS Shell:

#rmmod interrupt.ko
interrupt_module exit!
module /lib/modules/interrupt.ko unregister ok.

Topics: SylixOS