0%

spinlock结构

1
2
3
4
5
6
7
8
9
10
11
arch_spinlock_t
typedef union {
unsigned int lock;
// union结构, 总共占用一个word
struct {
// 正在服务的号 bit 0-15
unsigned short serving_now;
// 等待取的号 bit 16-31
unsigned short ticket;
} h;
} arch_spinlock_t;

api

arch_spin_is_locked

1
2
3
4
5
6
static inline int arch_spin_is_locked(arch_spinlock_t *lock)
{
unsigned int counters = ACCESS_ONCE(lock->lock);
// 取高16位和低16位做异或运算, 如果其他的 thread 已经获取了该 lock,那么返回非零值,即ticket 和 serving_now不相同时, 返回非0, 否则返回 0, 返回0时表明没上锁或者是本thread获取了锁.
return ((counters >> 16) ^ counters) & 0xffff;
}

自旋锁的变体

接口 API 的类型 spinlock 中的定义 raw_spinlock 的定义
定义 spin lock 并初始化 DEFINE_SPINLOCK DEFINE_RAW_SPINLOCK
动态初始化 spin lock spin_lock_init raw_spin_lock_init
获取指定的 spin lock spin_lock raw_spin_lock
获取指定的 spin lock 同时 disable 本 CPU 中断 spin_lock_irq raw_spin_lock_irq
保存本 CPU 当前的 irq 状态,disable 本 CPU 中断并获取指定的 spin lock spin_lock_irqsave raw_spin_lock_irqsave
获取指定的 spin lock 同时 disable 本 CPU 的 bottom half spin_lock_bh raw_spin_lock_bh
释放指定的 spin lock spin_unlock raw_spin_unlock
释放指定的 spin lock 同时 enable 本 CPU 中断 spin_unlock_irq raw_spin_unock_irq
释放指定的 spin lock 同时恢复本 CPU 的中断状态 spin_unlock_irqstore raw_spin_unlock_irqstore
获取指定的 spin lock 同时 enable 本 CPU 的 bottom half spin_unlock_bh raw_spin_unlock_bh
尝试去获取 spin lock,如果失败,不会 spin,而是返回非零值 spin_trylock raw_spin_trylock
判断 spin lock 是否是 locked,如果其他的 thread 已经获取了该 lock,那么返回非零值,否则返回 0 spin_is_locked raw_spin_is_locked

mips 体系加锁分析

arch_spin_lock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static inline void arch_spin_lock(arch_spinlock_t *lock)
{
int my_ticket;
int tmp;
int inc = 0x10000;
__asm__ __volatile__ (
" .set push # arch_spin_lock \n"
" .set noreorder \n"
" \n"
"1: ll %[ticket], %[ticket_ptr] \n" # ticket_ptr(lock->lock), 取出lock->lock 到 ticket(tmp) ll 原子操作, 取出一个word
" addu %[my_ticket], %[ticket], %[inc] \n" # my_ticket <- ticket(tmp) + 0x10000 这里应该是ticket + 1的意思, ticket自增,即当前的调用取的号+1,
" sc %[my_ticket], %[ticket_ptr] \n" # lock->lock <- my_ticket # 把ticket + 1后存回到 lock->ticket, 这个代表更新当前发的所有号, 来了一个新调用, 说明这个调用要抢资源, 给它分一个号
" beqz %[my_ticket], 1b \n" # sc失败, 返回结果0, 跳转回1b, 说明有其他cpu或中断在该指令执行前偷偷调用sc 把 tick_ptr给修改了, 这时sc会失败, 返回1b 重新来这个操作
" srl %[my_ticket], %[ticket], 16 \n" # my_ticket <- ticket(tmp) >> 16 my_ticket <- lock->ticket 换算ticket + 1
" andi %[ticket], %[ticket], 0xffff \n" # ticket(tmp) = ticket(tmp) & 0xffff tmp<- lock->serving_now
" bne %[ticket], %[my_ticket], 4f \n" # lock->ticket 与 lock->serving_now 不相等, 调转到 4f, 说明不是本上下文上的锁, 需要等待, 如相等,往下走 !!!!
" subu %[ticket], %[my_ticket], %[ticket] \n" # tmp <- lock->serving_now - lock->ticket, 相等的, tmp变成0了, 因为在延迟槽里,不相等的话 tmp <- lock->serving_now - lock->ticket
"2: \n"
" .subsection 2 \n" # # 程序后半段, !!!! 拼在函数jr ra 后面,正常情况下走不到这里, 注意跳转前 subu %[ticket], %[my_ticket], %[ticket] 在延迟槽里, 总是会执行
"4: andi %[ticket], %[ticket], 0x1fff \n" # lock->ticket 与 lock->serving_now 不相等时,走到这, tmp <- tmp & 0x1fff 取(lock->serving_now)0-12位
" sll %[ticket], 5 \n" # tmp << 5
" \n"
"6: bnez %[ticket], 6b \n" # tmp 不为0 , 循环tmp-1, 直到tmp 变成0
" subu %[ticket], 1 \n" # 循环体, 每次tmp-1
" \n"
" lhu %[ticket], %[serving_now_ptr] \n" # tmp <- lock->h.serving_now, 这个地方会从内存中更新serving_now, 别的调用方可能会放锁,导致serving_now变化, 因此这里需要更新
" beq %[ticket], %[my_ticket], 2b \n" # my_ticket 与 lock->serving_now 相等,则跳到2b, 调到2b后,函数就可以马上出去了
" subu %[ticket], %[my_ticket], %[ticket] \n" # tmp <- lock->h.serving_now - lock->ticket
" b 4b \n" # 回到4b, 重新等
" subu %[ticket], %[ticket], 1 \n" # tmp - 1
" .previous \n"
" .set pop \n"
: [ticket_ptr] "+m" (lock->lock),
[serving_now_ptr] "+m" (lock->h.serving_now),
[ticket] "=&r" (tmp),
[my_ticket] "=&r" (my_ticket)
: [inc] "r" (inc));
smp_llsc_mb(); // 调用sync. 内存屏障, 保证cache同步
}
阅读全文 »

FreeRTOS有两种方法触发任务切换:

  • 执行系统调用,比如普通任务可以使用taskYIELD()强制任务切换,中断服务程序中使用portYIELD_FROM_ISR()强制任务切换;
  • 系统节拍时钟中断

PendSV 中断

对于第一种任务切换方法,不管是使用taskYIELD()还是portYIELD_FROM_ISR(),最终都会执行宏portYIELD()

1
2
3
4
#define portYIELD()      do { \
// status pending sw0 mask 位置1, 与cause 协处理器中的pending位配合, 即触发sw0 上的中断
mips_biscr( SR_SINT0 ); \
} while (0)

SysTick 中断, 系统时钟节拍

对于第二种任务切换方法,在系统节拍时钟中断服务函数中,首先会更新tick计数器的值、查看是否有任务解除阻塞,如果有任务解除阻塞的话,则使能PandSV中断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 上一章中调度器创建中讲过 xPortStartScheduler 会初始化timer
__attribute__(( weak )) void vApplicationSetupTickTimerInterrupt( void )
{
/* Install interrupt handler */
// 安装tick中断处理函数 vPortTickInterruptHandler
pvPortInstallISR( HW5, vPortTickInterruptHandler );

/* Set Compare to Count as a starting point */ // 初始化compare
mips_setcompare( mips_getcount() );

/* Set the interrupt to fire 1us from now */ //systick + 1
prvSetNextTick( 1 );
}
// 来了tick中断, 会进入 vPortTickInterruptHandler
vPortTickInterruptHandler:
// 保存任务上下文 保存寄存器
portSAVE_CONTEXT
// 跳转到 vPortIncrementTick
jal vPortIncrementTick
nop
void vPortIncrementTick( void )
{
UBaseType_t uxSavedStatus;
// 暂存status寄存器, 清 status 的 8-15 的mask
uxSavedStatus = uxPortSetInterruptMaskFromISR();
{
// 任务delay到期或者任务的时间片用完了, 这个函数会返回true
if( xTaskIncrementTick() != pdFALSE )
{
// 触发pendsv 中断, 因为前面清了mask,因此这里只有sw0的mask置位了
portYIELD();
}
}
// 恢复status寄存器
vPortClearInterruptMaskFromISR( uxSavedStatus );

/* Look for the ISR stack getting near or past its limit. */
portCHECK_ISR_STACK();

/* Clear timer interrupt. */ // 清除timer cause hw5 pending位
configCLEAR_TICK_TIMER_INTERRUPT();
}

xTaskIncrementTick

tick中断到的时候, 什么条件下要触发pendsv中断的问题:

阅读全文 »

概览

FreeRTOS的信号量包括二进制信号量、计数信号量、互斥信号量(以后简称互斥量)和递归互斥信号量(以后简称递归互斥量)
FreeRTOS的信号量是使用队列机制实现的,数据结构也完全是队列的那一套。
除此之外,还有一种简单的任务间的同步方式: 任务通知
它的数据结构嵌在任务TCB中的,并且数据结构十分简单,涉及到任务TCB的两个字段,我们将它单独列出来:

1
2
1.  volatile uint32_t ulNotifiedValue; 	/*任务通知值*/  
2. volatile uint8_t ucNotifyState; /*任务通知状态,标识任务是否在等待通知等*/

本文只讨论信号量的部分

api

0cad59aa-bb2a-41b1-a65a-dc30706f90b8
创建队列与其他几个不同,暂且不表

二进制信号量

创建

vSemaphoreCreateBinary xSemaphoreCreateBinary
类型为 queueQUEUE_TYPE_BINARY_SEMAPHORE

1
2
3
4
5
6
7
8
9
10
#define vSemaphoreCreateBinary( xSemaphore )																							\
{ \
( xSemaphore ) = xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE ); \
if( ( xSemaphore ) != NULL ) \
{ \
( void ) xSemaphoreGive( ( xSemaphore ) ); \
} \
}

#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
阅读全文 »

任务创建

1
2
3
4
5
6
BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,  //指向任务函数的入口。任务永远不会返回(位于死循环内)
const char * const pcName, // 任务描述。主要用于调试。字符串的最大长度(包括字符串结束字符)由宏configMAX_TASK_NAME_LEN指定
const configSTACK_DEPTH_TYPE usStackDepth, //任务栈大小
void * const pvParameters, // 传给任务入口的参数
UBaseType_t uxPriority, //任务初始优先级
TaskHandle_t * const pxCreatedTask ) // 用于回传一个句柄(ID),创建任务后可以使用这个句柄引用任务。

任务结构体的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; // 指向当前栈的栈顶 , pxTopOfStack 总是指向最后一个入栈的项目

ListItem_t xStateListItem; // 状态列表项, ready blocked suspended 状态轮转
ListItem_t xEventListItem; // 事件列表项
UBaseType_t uxPriority; /* 任务优先级, 0是最低的优先级 */
StackType_t *pxStack; /* 任务栈的起始地址*/
char pcTaskName[ configMAX_TASK_NAME_LEN ]; // 任务名字

#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
StackType_t *pxEndOfStack; /* 任务栈的 结束地址*/
#endif

#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /* 任务嵌套深度*/
#endif

#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. 用于任务优先级继承的机制*/
UBaseType_t uxMutexesHeld;
#endif

#if( configUSE_TASK_NOTIFICATIONS == 1 ) // 任务通知机制, 用于简洁的任务通信
volatile uint32_t ulNotifiedValue;
volatile uint8_t ucNotifyState;
#endif

/* See the comments above the definition of
tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
uint8_t ucStaticallyAllocated; /* true 标识任务栈是静态分配的, 不需要free*/
#endif

} tskTCB;

这个函数就是创建TCB结构体, 并进行初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{
StackType_t *pxStack;
/* malloc 一个栈 , 也可以通过别的函数静态指定 */
pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
// 指针pxStack指向堆栈的起始位置 任务创建时会分配指定数目的任务堆栈,申请堆栈内存函数返回的指针就被赋给该变量
if( pxStack != NULL )
{
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e961 MISRA exception as the casts are only redundant for some paths. */

if( pxNewTCB != NULL )
{
pxNewTCB->pxStack = pxStack;
}
}
}

if( pxNewTCB != NULL )
{
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
{
/* Tasks can be created statically or dynamically, so note this
task was created dynamically in case it is later deleted. */
pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
// 挂入ready列表中
prvAddNewTaskToReadyList( pxNewTCB );
xReturn = pdPASS;
}
else
{
//false
xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
return xReturn;
  • pxTopOfStack指向当前堆栈栈顶,随着进栈出栈,pxTopOfStack指向的位置是会变化的;
  • pxStack指向当前堆栈的起始位置,一经分配后,堆栈起始位置就固定了,不会被改变了。
  • 那么为什么需要pxStack变量呢,这是因为随着任务的运行,堆栈可能会溢出,在堆栈向下增长的系统中,这个变量可用于检查堆栈是否溢出;
  • 如果在堆栈向上增长的系统中,要想确定堆栈是否溢出,还需要另外一个变量pxEndOfStack来辅助诊断是否堆栈溢出.

嵌入式系统上大多都是向上增长的, 因此需要pxEndOfStack来检测是否溢出

prvInitialiseNewTask

初始化TCB

阅读全文 »

FreeRTOS内核调度大量使用了列表(list)和列表项(list item)数据结构。
列表被FreeRTOS调度器使用,用于跟踪任务,处于就绪、挂起、延时的任务,都会被挂接到各自的列表中。用户程序如果有需要,也可以使用列表。
FreeRTOS列表使用指针指向列表项(item)。一个列表(list)下面可能有很多个列表项(list item),每个列表项都有一个指针指向列表。


列表项有两种形式,全功能版的列表项xLIST_ITEM和迷你版的列表项xMINI_LIST_ITEM。我们来看一下它们具体的定义,先看全功能版。

全功能版的列表项 xLIST_ITEM

1
2
3
4
5
6
7
8
9
10
struct xLIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /*用于检测列表项数据是否完整*/
configLIST_VOLATILE TickType_t xItemValue; /*列表项值*/
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
void * pvOwner; /*指向一个任务TCB */
void * configLIST_VOLATILE pvContainer; /*指向包含该列表项的列表 */
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE /*用于检测列表项数据是否完整*/
};

xItemValue是列表项值,通常是一个被跟踪的任务优先级或是一个调度事件的计数器值。
如果任务因为等待从队列取数据而进入阻塞状态,则 任务的事件列表项 的列表项值保存任务优先级有关信息,状态列表项的列表项值保存阻塞时间有关的信息

迷你版的列表项 xMINI_LIST_ITEM

1
2
3
4
5
6
7
8
struct xMINI_LIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
configLIST_VOLATILE TickType_t xItemValue;
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

和全功能xLIST_ITEM对比少了pvContainer, pvOwner

列表结构体需要一个列表项成员,但又不需要列表项中的所有字段,所以才有了迷你版列表项

列表 xLIST

阅读全文 »

FreeRTOS提供了多种任务间通讯方式, 包括:

  • 任务通知
  • 队列
  • 二进制信号量
  • 计数信号量
  • 互斥量
  • 递归互斥量

其中, 二进制信号量\计数信号量\互斥量和递归互斥量都是使用Queue 队列来实现的
需要先看下Queue的实现方式
Queue是freertos最重要的任务间通讯方式, 可以在

  • 任务与任务间
  • 中断和任务间传送信息。

发送到队列的消息是通过拷贝实现的,这意味着队列存储的数据是原数据,而不是原数据的引用。先看一下队列的数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
typedef struct QueueDefinition
{
int8_t *pcHead; /* 指向队列存储区起始位置*/
int8_t *pcTail; /* 指向队列存储区结束后的下一个字节 */
int8_t *pcWriteTo; /* 指向队列存储区的下一个空闲位置 *

union /* Use of a union is an exception to the coding standard to ensure two mutually exclusive structure members don't appear simultaneously (wasting RAM). */
{
int8_t *pcReadFrom; /* 当结构体用于队列时,这个字段指向出队项目中的最后一个. */
UBaseType_t uxRecursiveCallCount;/* 当结构体用于互斥量时,用作计数器,保存递归互斥量被"获取"的次数. */
} u;

List_t xTasksWaitingToSend; /*< List of tasks that are blocked waiting to post onto this queue. Stored in priority order. */
// 等待入Queue而阻塞的任务, 以优先级升序排列*/
List_t xTasksWaitingToReceive; // 等待出Queue而阻塞的任务列表, 以优先级升序排列*/

volatile UBaseType_t uxMessagesWaiting; //当前Queue的队列项数目
UBaseType_t uxLength; // 队列项的数目 ? //TODO:
UBaseType_t uxItemSize; // 每个队列项的大小

volatile int8_t cRxLock; /* Queue上锁后,存储从队列收到的队列项数目(就是队列上锁时从队列中remove出去的队列项数目),如果队列没有上锁,设置为queueUNLOCKED */
volatile int8_t cTxLock; /* Queue上锁后,存储发送到队列的队列项数目(就是队列上锁时加到该队列的队列项数目),如果队列没有上锁,设置为queueUNLOCKED */

#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated;
#endif

} xQUEUE;

初看这个结构, 有好几个意义不是很明确, 比如 xTasksWaitingToSend xTasksWaitingToReceive 是干啥用的, uxMessagesWaitinguxLength说的是啥, cRxLock``cTxLock表示的是啥意思
都比较懵, 需要结合代码看一下.

队列创建函数 xQueueCreate

真正被执行的函数是xQueueGenericCreate(),通用队列创建函数。 我们来分析一下xQueueGenericCreate()函数,函数原型为:

1
2
#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType ) PRIVILEGED_FUNCTION;
阅读全文 »

ecu 固件恶意修改防护, 固件加密防护防止恶意破解访问或数字签名防护恶意篡改, 固件安全升级防护

ECU上网络服务器的使用应仅限于其必要的功能,服务端口应加以保护以防止未经授权的使用

使用分段和隔离技术:特权分离与边界控制能够提升系统安全性。逻辑和物理隔离技术应用于将处理器、车辆网络和外部连接分开,以限制和控制外部威胁到车辆内部的路径。

边界控制技术,如严格的基于白名单的段间消息过滤机制,应被用于保障接口访问安全

debug调试机制限制, 诊断操作需要尽可能施加较多的限制措施,例如限制某个诊断操作的影响范围或时间

车辆内部通讯控制: 安全关键的消息尽量通过硬件隔离的安全总线传输

证书管理: 后台服务器访问控制:加密措施应用于任何基于IP的外部服务器和车辆之间的业务通信过程中。此类网络连接不应接受无效证书

阅读全文 »

打开或者关闭加密

1
2
3
4
5
examples/usb/usb_mic/CMakeLists.txt
# 打开加密
set(BL_ENCRYPTING yes)
# 关闭加密
set(BL_ENCRYPTING no)

打开加密时的依赖项

烧入efuse key

image-20211022162941641

BouffaloLabDevCube -> View-> MCU -> Efuse Key

三个需要改的地方, AES MODE 选 None !!!

  • ef_key_slot2(hex) ce043c3e22ad711d4033c548970b0a7b Write Lock Read Lock 打勾

  • ef_key_slot3(hex) 0710340a96e1f0d3bbc355b2261b4c2b Write Lock Read Lock 打勾

改完后, 点击软件上的Create按钮, 生成efuse数据文件

板子和pc 使用typec 连接, 先按boot 按键0.5s, boot按键不要松开, 再接着另一个手指按reset按键, 两个按键一起按3s后, 两个按键同时松开, 此时按软件上的Refresh按钮, COM Port 中出现COM3, 选择COM3.

阅读全文 »

分区

^b2c15f

nand

1
2
3
4
5
6
7
8
9
10
11
[storageinfo]
mediumtype=nand
capacity=256MB
[partition]
# 分区名 偏移量 大小 设备名
item1=uboot,0x0,0x100000,mtdblock0
item2=nv,0x100000,0x100000,mtdblock1
item3=kernel,0x200000,0x800000,mtdblock2
item4=recovery,0xa00000,0x1000000,mtdblock3
item5=system,0x1a00000,0x5000000,mtdblock4
item6=userdata,0x6a00000,0x9300000,mtdblock5

升级主程序

也叫recovery

全局配置文件

1
2
3
4
5
6
7
struct global_data g_data = {
.public_key_path = "/usr/data/ota_res/key/key.pub",
.configure_file_path = "/usr/data/ota_res/recovery.conf",
.font_path = "/usr/data/ota_res/image/font.png",
.audio_path = "/usr/data/ota_res/tips",
.has_fb = 1,
};

配置文件示例:
recovery.conf

1
2
3
4
5
6
7
{
"server":{
"ip":"192.168.1.105",
"url":"https://192.168.1.105",
"rpt_url":""
}
}
阅读全文 »