本章使用gpio中断来实现按键驱动,重点在于理解HDF gpio框架
一、驱动代码
参考上一章led驱动程序的编写来实现本章的驱动。
可以按上一章led驱动程序的编写步骤重复做一遍。button驱动与led驱动的区别在于GPIO管脚以及初始化代码、中断相关代码等:
1.1、button驱动
在按键驱动程序button.c中添加gpio的头文件:
#include "gpio_if.h"
在初始化函数中,通过读取button_config.hcs来获取按键的gpio号。然后调用gpio_if.h提供的接口,设置中断回调函数 MyCallBackFunc,最后使能中断。
int32_t HdfButtonDriverInit(struct HdfDeviceObject *device)
{
int32_t ret;
struct Stm32Mp1Button *btn = &g_Stm32Mp1IButton;
Stm32ButtonReadDrs(btn,device->property);
ret = GpioSetIrq(g_Stm32Mp1IButton.gpioNum, OSAL_IRQF_TRIGGER_RISING, MyCallBackFunc, (void *)device);
if (ret != 0) {
HDF_LOGE("GpioSetIrq: failed, ret %d\n", ret);
return HDF_FAILURE;
}
ret = GpioEnableIrq(btn->gpioNum);
if (ret != 0) {
HDF_LOGE("GpioEnableIrq: failed, ret %d\n", ret);
return HDF_FAILURE;
}
return HDF_SUCCESS;
}
中断回调函数:
在中断回调函数中统计按键按下次数,然后将次数发送到应用层。这里通过HDF的消息管理机制给应用层发送消息,这一部分后面再聊。
当 HdfDeviceSendEvent()成功执行后,应用程序的监听函数就会被执行,在回调函数中翻转led。
int32_t MyCallBackFunc(uint16_t gpio, void *data)
{
global_data++;
struct HdfDeviceObject *deviceObject = (struct HdfDeviceObject *)data;
struct HdfSBuf *send_buf = HdfSBufObtainDefaultSize();
HdfSbufWriteUint16(send_buf,global_data);
HdfDeviceSendEvent(deviceObject,520,send_buf);
HdfSBufRecycle(send_buf);
return 0;
}
1.2 gpio驱动
1.2.1、gpio核心层
以上GPIO的操作函数都是由gpio_if.h提供,而gpio_if.h只是对gpio_core.c的一层封装,屏蔽了gpio控制器
例如下面的函数实际上是调用 gpio_core.c 中的 GpioCntlrSetIrq()。
int32_t GpioSetIrq(uint16_t gpio, uint16_t mode, GpioIrqFunc func, void *arg)
{
return GpioCntlrSetIrq(GpioGetCntlr(gpio), GpioToLocal(gpio), mode, func, arg);
}
而 gpio_core.c 就是对硬件gpio控制器的抽象,通过gpio控制器可以配置具体的gpio电平、输入输出模式、中断等。
struct GpioCntlr {
struct IDeviceIoService service;
struct HdfDeviceObject *device;
struct GpioMethod *ops;
struct DListHead list;
OsalSpinlock spin;
uint16_t start;
uint16_t count;
struct GpioInfo *ginfos;
void *priv;
};
gpio_core.c 中的函数就是调用控制器的各种方法:
int32_t GpioCntlrWrite(struct GpioCntlr *cntlr, uint16_t local, uint16_t val)
{
......
return cntlr->ops->write(cntlr, local, val);
}
int32_t GpioCntlrRead(struct GpioCntlr *cntlr, uint16_t local, uint16_t *val)
{
......
return cntlr->ops->read(cntlr, local, val);
}
int32_t GpioCntlrSetDir(struct GpioCntlr *cntlr, uint16_t local, uint16_t dir)
{
......
return cntlr->ops->setDir(cntlr, local, dir);
}
int32_t GpioCntlrGetDir(struct GpioCntlr *cntlr, uint16_t local, uint16_t *dir)
{
......
return cntlr->ops->getDir(cntlr, local, dir);
}
需要注意的地方是:每一个中断控制器都有一个ginfos的数组,其定义如下:
struct GpioInfo {
GpioIrqFunc irqFunc;
void *irqData;
};
我们调用GpioSetIrq()设置的MyCallBackFunc()函数就保存在这个数组的特定位置。当gpio管脚的中断产生时,中断函数就会根据gpio控制器找到ginfo数组,再从数组中取出MyCallBackFunc()来执行。所以ginfo数组里的函数就叫中断回调函数。
以下函数就是gpio_core.c提供的回调接口,中断函数需要调用该函数以实现回调 MyCallBackFunc(),例如在2.2.2中的 IrqHandleNoShare()
void GpioCntlrIrqCallback(struct GpioCntlr *cntlr, uint16_t local)
{
struct GpioInfo *ginfo = NULL;
if (cntlr != NULL && local < cntlr->count && cntlr->ginfos != NULL) {
ginfo = &cntlr->ginfos[local];
if (ginfo != NULL && ginfo->irqFunc != NULL) {
(void)ginfo->irqFunc(local, ginfo->irqData);
} else {
HDF_LOGW("GpioCntlrIrqCallback: ginfo or irqFunc is NULL!");
}
} else {
HDF_LOGW("GpioCntlrIrqCallback: invalid cntlr(ginfos) or loal num:%u!", local);
}
}
GpioCntlrSetIrq()源码注释如下:其实现的就是两个功能:
- 设置回调函数。
- 调用gpio驱动、初始化中断相关的硬件配置:cntlr->ops->setIrq(cntlr, local, mode, theFunc, theData);
int32_t GpioCntlrSetIrq(struct GpioCntlr *cntlr, uint16_t local, uint16_t mode, GpioIrqFunc func, void *arg)
{
int32_t ret;
uint32_t flags;
GpioIrqFunc theFunc = func;
void *theData = arg;
struct GpioIrqBridge *bridge = NULL;
void *oldFunc = NULL;
void *oldData = NULL;
if (cntlr == NULL || cntlr->ginfos == NULL) {
return HDF_ERR_INVALID_OBJECT;
}
if (local >= cntlr->count) {
return HDF_ERR_INVALID_PARAM;
}
if (cntlr->ops == NULL || cntlr->ops->setIrq == NULL) {
return HDF_ERR_NOT_SUPPORT;
}
if ((mode & GPIO_IRQ_USING_THREAD) != 0) {
bridge = GpioIrqBridgeCreate(cntlr, local, func, arg);
if (bridge != NULL) {
theData = bridge;
theFunc = GpioIrqBridgeFunc;
}
if (bridge == NULL) {
return HDF_FAILURE;
}
}
(void)OsalSpinLockIrqSave(&cntlr->spin, &flags);
oldFunc = cntlr->ginfos[local].irqFunc;
oldData = cntlr->ginfos[local].irqData;
cntlr->ginfos[local].irqFunc = theFunc;
cntlr->ginfos[local].irqData = theData;
ret = cntlr->ops->setIrq(cntlr, local, mode, theFunc, theData);
if (ret == HDF_SUCCESS) {
if (oldFunc == GpioIrqBridgeFunc) {
GpioIrqBridgeDestroy((struct GpioIrqBridge *)oldData);
}
} else {
cntlr->ginfos[local].irqFunc = oldFunc;
cntlr->ginfos[local].irqData = oldData;
if (bridge != NULL) {
GpioIrqBridgeDestroy(bridge);
bridge = NULL;
}
}
(void)OsalSpinUnlockIrqRestore(&cntlr->spin, &flags);
return ret;
}
1.2.2、gpio驱动
gpio_core.c中只是提供了gpio控制器的一个”模型“,具体的gpio控制器需要由驱动开发者根据芯片平台实现,在此例如stm32mp157,由小熊派官方已经实现。在/device/st/drivers/gpio/目录下:
stm32mp1_gpio.c的实现参考了[openharmony gpio驱动开发指南](zh-cn/device-dev/driver/driver-platform-gpio-develop.md · OpenHarmony/docs - Gitee.com)。读者最好先阅读该指南。
首先认识stm32的gpio控制器结构体:
该结构体是驱动开发者自定义的,必须包含struct GpioCntlr 成员。
struct Stm32GpioCntlr {
struct GpioCntlr cntlr;
volatile unsigned char *regBase;
EXTI_TypeDef *exitBase;
uint32_t gpioPhyBase;
uint32_t gpioRegStep;
uint32_t irqPhyBase;
uint32_t iqrRegStep;
uint16_t groupNum;
uint16_t bitNum;
struct GpioGroup *groups;
};
struct GpioGroup {
volatile unsigned char *regBase;
EXTI_TypeDef *exitBase;
unsigned int index;
OsalIRQHandle irqFunc;
OsalSpinlock lock;
};
由上面的结构体可知,stm32mp1_gpio.c中除了要实现 核心层的struct GpioCntlr cntlr 之外,还需要获得寄存器地址等硬件配置。
这一点在gpio驱动初始化函数中能得到体现:
static int32_t GpioDriverInit(struct HdfDeviceObject *device)
{
int32_t ret;
struct Stm32GpioCntlr *stm32gpio = &g_Stm32GpioCntlr;
dprintf("%s: Enter", __func__);
if (device == NULL || device->property == NULL) {
HDF_LOGE("%s: device or property NULL!", __func__);
return HDF_ERR_INVALID_OBJECT;
}
ret = Stm32GpioReadDrs(stm32gpio, device->property);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: get gpio device resource fail:%d", __func__, ret);
return ret;
}
if (stm32gpio->groupNum > GROUP_MAX || stm32gpio->groupNum <= 0 || stm32gpio->bitNum > BIT_MAX ||
stm32gpio->bitNum <= 0) {
HDF_LOGE("%s: invalid groupNum:%u or bitNum:%u", __func__, stm32gpio->groupNum,
stm32gpio->bitNum);
return HDF_ERR_INVALID_PARAM;
}
stm32gpio->regBase = OsalIoRemap(stm32gpio->gpioPhyBase, stm32gpio->groupNum * stm32gpio->gpioRegStep);
if (stm32gpio->regBase == NULL) {
HDF_LOGE("%s: err remap phy:0x%x", __func__, stm32gpio->gpioPhyBase);
return HDF_ERR_IO;
}
stm32gpio->exitBase = OsalIoRemap(stm32gpio->irqPhyBase, stm32gpio->iqrRegStep);
if (stm32gpio->exitBase == NULL) {
dprintf("%s: OsalIoRemap fail!", __func__);
return -1;
}
ret = InitGpioCntlrMem(stm32gpio);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: err init cntlr mem:%d", __func__, ret);
OsalIoUnmap((void *)stm32gpio->regBase);
stm32gpio->regBase = NULL;
return ret;
}
stm32gpio->cntlr.count = stm32gpio->groupNum * stm32gpio->bitNum;
stm32gpio->cntlr.priv = (void *)device->property;
stm32gpio->cntlr.device = device;
stm32gpio->cntlr.ops = &g_GpioMethod;
ret = GpioCntlrAdd(&stm32gpio->cntlr);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: err add controller: %d", __func__, ret);
return ret;
}
HDF_LOGE("%s: dev service:%s init success!", __func__, HdfDeviceGetServiceName(device));
return ret;
}
在stm32mp1_gpio.c中还有一个重要的结构体:它是所有gpio操作的具体实现,我们以Stm32Mp157GpioWrite和Stm32Mp157GpioSetIrq为例子,驱动是如何操作硬件的。
struct GpioMethod g_GpioMethod = {
.request = NULL,
.release = NULL,
.write = Stm32Mp157GpioWrite,
.read = Stm32Mp157GpioRead,
.setDir = Stm32Mp157GpioSetDir,
.getDir = Stm32Mp157GpioGetDir,
.toIrq = NULL,
.setIrq = Stm32Mp157GpioSetIrq,
.unsetIrq = Stm32Mp157GpioUnsetIrq,
.enableIrq = Stm32Mp157GpioEnableIrq,
.disableIrq = Stm32Mp157GpioDisableIrq,
};
Stm32Mp157GpioWrite:
static int32_t Stm32Mp157GpioWrite(struct GpioCntlr *cntlr, uint16_t gpio, uint16_t val)
{
int32_t ret;
uint32_t irqSave;
unsigned int valCur;
unsigned int bitNum = Stm32ToBitNum(gpio);
volatile unsigned char *addr = NULL;
struct GpioGroup *group = NULL;
ret = Stm32GetGroupByGpioNum(cntlr, gpio, &group);
if (ret != HDF_SUCCESS) {
return ret;
}
if (OsalSpinLockIrqSave(&group->lock, &irqSave) != HDF_SUCCESS) {
return HDF_ERR_DEVICE_BUSY;
}
addr = STM32MP15X_GPIO_DATA(group->regBase);
valCur = OSAL_READL(addr);
if (val == GPIO_VAL_LOW) {
valCur &= ~(0x1 << bitNum);
valCur |= (0x1 << (bitNum+16));
} else {
valCur |= (0x1 << bitNum);
}
OSAL_WRITEL(valCur, addr);
(void)OsalSpinUnlockIrqRestore(&group->lock, &irqSave);
return HDF_SUCCESS;
}
Stm32Mp157GpioSetIrq:
static int32_t Stm32Mp157GpioSetIrq(struct GpioCntlr *cntlr, uint16_t gpio, uint16_t mode, GpioIrqFunc func, void *arg)
{
int32_t ret = HDF_SUCCESS;
uint32_t irqSave;
struct GpioGroup *group = NULL;
unsigned int bitNum = Stm32ToBitNum(gpio);
(void)func;
(void)arg;
ret = Stm32GetGroupByGpioNum(cntlr, gpio, &group);
if (ret != HDF_SUCCESS) {
return ret;
}
if (OsalSpinLockIrqSave(&group->lock, &irqSave) != HDF_SUCCESS) {
return HDF_ERR_DEVICE_BUSY;
}
EXTI_ConfigTypeDef EXTI_ConfigStructure;
EXTI_HandleTypeDef hexti;
Stm32Mp157GpioSetDir(cntlr,gpio,GPIO_DIR_IN);
EXTI_ConfigStructure.Line = EXTI_GPIO | EXTI_EVENT | EXTI_REG1 |bitNum;
EXTI_ConfigStructure.Trigger = EXTI_TRIGGER_FALLING;
EXTI_ConfigStructure.GPIOSel = Stm32ToGroupNum(gpio);
EXTI_ConfigStructure.Mode = EXTI_MODE_C1_INTERRUPT;
HAL_EXTI_SetConfigLine(&hexti, &EXTI_ConfigStructure);
GpioClearIrqUnsafe(group, bitNum);
if (group->irqFunc != NULL) {
(void)OsalSpinUnlockIrqRestore(&group->lock, &irqSave);
HDF_LOGI("%s: group irq(%p) already registered!", __func__, group->irqFunc);
return HDF_SUCCESS;
}
ret = GpioRegisterGroupIrqUnsafe(bitNum, group);
(void)OsalSpinUnlockIrqRestore(&group->lock, &irqSave);
HDF_LOGI("%s: group irq(%p) registered!", __func__, group->irqFunc);
return ret;
}
stm32mp1将全部gpio的中断服务函数都统一设置为 IrqHandleNoShare(),再在IrqHandleNoShare()中去执行GpioCntlrIrqCallback,这个函数在2.2.1中已经分析过,GpioCntlrIrqCallback会去区分具体是哪个GPIO管脚的中断回调函数需要执行。
所以stm32mp1是偷了一个懒,把事儿都丢给了gpio核心层去做。:p
static int32_t GpioRegisterGroupIrqUnsafe(uint16_t pinNum, struct GpioGroup *group)
{
int ret;
ret = OsalRegisterIrq(GetGpioIrqNum(pinNum), 0, IrqHandleNoShare, "GPIO", group);
if (ret != 0) {
(void)OsalUnregisterIrq(GetGpioIrqNum(pinNum), group);
ret = OsalRegisterIrq(GetGpioIrqNum(pinNum), 0, IrqHandleNoShare, "GPIO", group);
}
if (ret != 0) {
HDF_LOGE("%s: irq reg fail:%d!", __func__, ret);
return HDF_FAILURE;
}
ret = OsalEnableIrq(GetGpioIrqNum(pinNum));
if (ret != 0) {
HDF_LOGE("%s: irq enable fail:%d!", __func__, ret);
(void)OsalUnregisterIrq(GetGpioIrqNum(pinNum), group);
return HDF_FAILURE;
}
group->irqFunc = IrqHandleNoShare;
return HDF_SUCCESS;
}
static uint32_t IrqHandleNoShare(uint32_t irq, void *data)
{
unsigned int i;
struct GpioGroup *group = (struct GpioGroup *)data;
if (data == NULL) {
HDF_LOGW("%s: data is NULL!", __func__);
return HDF_ERR_INVALID_PARAM;
}
for (i = 0; i < g_Stm32GpioCntlr.bitNum; i++) {
if(__HAL_GPIO_EXTI_GET_IT(1<<i,group->exitBase) != 0)
{
__HAL_GPIO_EXTI_CLEAR_IT(1<<i,group->exitBase);
GpioCntlrIrqCallback(&g_Stm32GpioCntlr.cntlr, Stm32ToGpioNum(group->index, i));
}
}
return HDF_SUCCESS;
}
二、中断处理过程
本节介绍中断触发源到中断回调函数MyCallBackFunc()执行的过程,如图所示,中断信息的传递经过以下6个模块:
gpio外设的中断会经过NVIC中断控制器,触发ARM的普通中断,CPU就会去中断向量表的OSIrqHandler执行:
OSIrqHandler的代码在kernel/liteos_a/arch/arm/arm/src/los_dispatch.S,这一部分的代码可以参考以下博客:
鸿蒙研究站 | 每天死磕一点点 | 2022.01.28 更新 (weharmony.github.io)
OsIrqHandler:
......
BLX HalIrqHandler
......
HalIrqHandler 的工作就是读取NVIC寄存器,得到中断号,然后调用OsInterrupt()去执行对应的中断服务程序。
VOID HalIrqHandler(VOID)
{
UINT32 iar = GiccGetIar();
UINT32 vector = iar & 0x3FFU;
if (vector >= OS_HWI_MAX_NUM) {
return;
}
g_curIrqNum = vector;
OsInterrupt(vector);
GiccSetEoir(vector);
}
OsInterrupt():
VOID OsInterrupt(UINT32 intNum)
{
......
hwiForm = (&g_hwiForm[intNum]);
if (hwiForm->uwParam) {
HWI_PROC_FUNC2 func = (HWI_PROC_FUNC2)hwiForm->pfnHook;
if (func != NULL) {
UINTPTR *param = (UINTPTR *)(hwiForm->uwParam);
func((INT32)(*param), (VOID *)(*(param + 1)));
}
} else {
HWI_PROC_FUNC0 func = (HWI_PROC_FUNC0)hwiForm->pfnHook;
if (func != NULL) {
func();
}
}
.......
}
IrqHandleNoShare()在上一节已经分析过,最终它会调用MyCalllBackFunc()来执行我们的中断回调函数。
三、小结
ARM普通中断会使cpu执行中断向量表中的特定的函数,在这个函数中就需要判断是具体哪个外设的中断,这是通过读取NVIC的寄存器得知的。
由于内核负责管理中断,所以由内核实现对外设中断的处理。在liteos_a中,使用一个全局数组来保存所有的外设中断服务函数。
以上是外设中断处理的common情况,而具体到gpio中断,则需要gpio core层的介入。gpio core层也通过一个全局数组来保存所有gpio引脚的回调函数。
为什么需要gpio core这一层呢?我想大概是为了更加方便的管理gpio,以中断为例子,不同的gpio有不同的中断处理程序,但他们 都有一些共性,就是需要清除中断标志位,gpio core将这些共同的处理放在统一的NoShare函数,自己创建一个中断向量表来管理所有的中断。
总之gpio core是一个承上启下的作用,驱动开发者按照gpio core的规定编写完成驱动程序(如gpio控制器),上层应用就能通过 gpio_if正确地使用驱动程序,所以作为驱动开发者,就需要熟悉gpio core,以及其他类型外设的核心层,从而开发正确的驱动程序。
|