[OHOS - STM32MP157] 2 深挖OpenHarmony之ioremap 原创 精华

心往北方
发布于 2022-3-2 13:30
浏览
1收藏

[OHOS - STM32MP157] 2 深挖OpenHarmony之ioremap

1 . openHarmony是如何控制外设的?

1.1 裸机程序控制硬件示例 – GPIO点灯编程

 [OHOS - STM32MP157] 2 深挖OpenHarmony之ioremap-鸿蒙开发者社区
 [OHOS - STM32MP157] 2 深挖OpenHarmony之ioremap-鸿蒙开发者社区

  • 寄存器设置

    由小熊派Micro原理图可以看出,其LED灯连接到芯片的PA13管脚上,并且以低电平方式点亮。为了实现控制 LED 灯的目的, 首先使能对应 GPIO 时钟, 需要通过配置 MODER 寄存器将对应的端口配置成输出模式,通过OTYPER 设置输出类型,然后可以通过 ODR 寄存器实现 LED 灯的点亮与熄灭。

  • 程序编写

    void mydelay_ms(int ms)
    {
        volatile int i = 0, j = 0;
        while (ms--)
        {
            for (i = 0; i < 1000; i++)
                for (j = 0; j < 1000; j++)
                    ;
        }
    }
    int main()
    {
        RCC_MP_AHB5ENSETR = (RCC_MP_AHB4ENSETR &= ~(0xff)) | 0x01;
        GPIOA.MODER = (GPIOA.MODER &= ~(0x3 << 10)) | 0x1 << 10; //设置为输出模式
        GPIOA.OTYPER &= ~(0x1 << 13);                             //推娩输出
        GPIOA.OSPEEDR &= ~(0x3 << 10);                           //低速
        while (1)                                                //闪烁
        {
            GPIOA.ODR = (GPIOA.ODR &= ~(0x1 << 13)) | 0x1 << 13;
            mydelay_ms(30);
            GPIOZ.ODR &= ~(0x1 << 13);
            mydelay_ms(30);
        }
        return 0;
    }
    

    1.2 OpenHarmony控制硬件示例 – GPIO点灯编程

​ 【参考:applications/BearPi/BearPi-HM_Micro/docs/device-dev/编写一个点亮LED灯程序.md · 小熊派开源社区/BearPi-HM_Micro_small - Gitee.com

​ 【补充】:OpenHarmony LiteOS-A内核架构图

 [OHOS - STM32MP157] 2 深挖OpenHarmony之ioremap-鸿蒙开发者社区

由上图可知,OpenHarmony LiteOS-A内核区分了用户空间和内核空间,用户空间和内核空间的数据传递我们之后在讲,先看内核空间是如何控制LED灯的。下面是大佬对HDF驱动框架的分析,请移步观看:OpenHarmony HDF 驱动框架介绍和驱动加载过程分析-OpenHarmony技术社区-51CTO.COM

读完后,是不是对HDF驱动框架有一定的了解了呢,下面我们就仔细看看这个LED到底是怎么能够点亮的吧。

首先我们先看LED驱动注册方法:

// 定义驱动入口的对象,必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量
struct HdfDriverEntry g_ledDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "HDF_LED",
    .Bind = HdfLedDriverBind,
    .Init = HdfLedDriverInit,
    .Release = HdfLedDriverRelease,
};

// 调用HDF_INIT将驱动入口注册到HDF框架中
HDF_INIT(g_ledDriverEntry);

驱动加载时会自动调用HdfLedDriverBind,HdfLedDriverInit函数,下面我们看HdfLedDriverInit函数

// 驱动自身业务初始的接口
int32_t HdfLedDriverInit(struct HdfDeviceObject *device)
{
    struct Stm32Mp1ILed *led = &g_Stm32Mp1ILed;
    int32_t ret;

    if (device == NULL || device->property == NULL) {
        HDF_LOGE("%s: device or property NULL!", __func__);
        return HDF_ERR_INVALID_OBJECT;
    }
    /************************************************************************
    *补充hcs文件内容
    root {
    LedDriverConfig {
        led_gpio_num = 13;
        match_attr = "st_stm32mp157_led";   //该字段的值必须和device_info.hcs中的deviceMatchAttr值一致
    	}
	}
    我们重点关注led_gpio_num这个属性,具体计算方法参考上一篇文章
    *
    ************************************************************************/
    
    /* 读取hcs私有属性值 */
    ret = Stm32LedReadDrs(led, device->property);
 /************************************************
 * 这是Stm32LedReadDrs函数里面的最重要的实现,获取LED灯的控制管脚号
   读取led.hcs里面led_gpio_num的值
    ret = drsOps->GetUint32(node, "led_gpio_num", &led->gpioNum, 0); 
 */
     
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%s: get led device resource fail:%d", __func__, ret);
        return ret;
    }
    /* 将GPIO管脚配置为输出 */
    ret = GpioSetDir(led->gpioNum, GPIO_DIR_OUT);
    if (ret != 0)
    {
        HDF_LOGE("GpioSerDir: failed, ret %d\n", ret);
        return ret;
    }
    HDF_LOGD("Led driver Init success");
    return HDF_SUCCESS;
}

上面是HdfLedDriverInit方法,具体操作硬件的方法请参考上一篇文章。

1.3 裸机驱动LED与OpenHarmony操作系统驱动LED的区别到底在哪?

可以看到,裸机驱动LED的方式是直接控制GPIO的寄存器,而操作系统驱动LED需要经过层层封装去控制硬件,直接操作寄存器点亮 LED 和通过驱动程序点亮 LED 最本质的区别就是有无使用操作系统。有操作系统的存在则大大降低了应用软件与硬件平台的耦合度,它充当了我们硬件与应用软件之间的纽带,使得应用软件只需要调用驱动程序接口 API 就可以让硬件去完成要求的开发,而应用软件则不需要关心硬件到底是如何工作的。这将大大提高我们应用程序的可移植性和开发效率。

1.4 操作系统是怎么控制具体的硬件外设的?

openHarmony环境直接访问物理内存是很危险的,如果用户不小心修改了内存中的数据,很有可能造成错误甚至系统崩溃。为了解决这些问题内核便引入了 MMU, MMU 为编程提供了方便统一的内存空间抽象,其实我们的程序中所写的变量地址是虚拟内存当中的地址,倘若处理器想要访问这个地址的时候, MMU 便会将此虚拟地址(Virtual Address)翻译成实际的物理地址(Physical Address),之后处理器才去操作实际的物理地址。 MMU 是一个实际的硬件,并不是一个软件程序。他的主要作用是将虚拟地址翻译成真实的物理地址同时管理和保护内存,不同的进程有各自的虚拟地址空间,某个进程中的程序不能修改另外一个进程所使用的物理地址,以此使得进程之间互不干扰,相互隔离。

  • 到底什么是虚拟地址什么是物理地址?

    当没有启用 MMU 的时候, CPU 在读取指令或者访问内存时便会将地址直接输出到芯片的引脚上,此地址直接被内存接收,这段地址称为物理地址,如下图所示。

 [OHOS - STM32MP157] 2 深挖OpenHarmony之ioremap-鸿蒙开发者社区

物理地址与硬件平台统一,不同的硬件平台对应的地址几乎都不一样,因此,操作系统要实现通用性,就必须把物理地址进行统一管理,因此便诞生了虚拟地址的概念,简单来说,当 CPU 开启了 MMU 时, CPU 发出的地址将被送入到 MMU,被送入到 MMU 的这段地址称为虚拟地址,之后 MMU 会根据去访问页表地址寄存器然后去内存中找到页表(假设只有一级页表)的条目,从而翻译出实际的物理地址。最后总结一下,就是MMU是物理地址与虚拟地址之间转换的桥梁。

下面我们看一下对于小熊派这款mp157开发板的虚拟地址与物理地址之间的映射关系

// 256MB
#ifdef LOSCFG_TEE_ENABLE
#define DDR_MEM_ADDR            0xC1000000
#define DDR_MEM_SIZE            0xf000000
#else
#define DDR_MEM_ADDR            0xBF000000
#define DDR_MEM_SIZE            0x11000000
#endif

#define SYS_MEM_BASE            DDR_MEM_ADDR
#define SYS_MEM_SIZE_DEFAULT    0x07f00000
#define SYS_MEM_END             (SYS_MEM_BASE + SYS_MEM_SIZE_DEFAULT)

/* Peripheral register address base and size */
#define PERIPH_PMM_BASE         0x40000000
#define PERIPH_PMM_SIZE         0x20000000

/* ddr ramfs */
#define DDR_RAMFS_VBASE         (PERIPH_UNCACHED_BASE + PERIPH_UNCACHED_SIZE)
#define DDR_RAMFS_ADDR          (DDR_MEM_ADDR + DDR_MEM_SIZE)
#define DDR_RAMFS_SIZE          (0x4000000)  /* 64M ramfs */
#define GIC_VIRT_SIZE           U32_C(GIC_PHY_SIZE)
#define DDR_RAMFS_REAL_SIZE     (0xa00000) 	/* ramfs real size */

/* kernel load address */
#define KERNEL_LOAD_ADDRESS     (0xC0100000)

LosArchMmuInitMapping g_archMmuInitMapping[] = {
    {
        .phys = SYS_MEM_BASE,
        .virt = KERNEL_VMM_BASE,
        .size = KERNEL_VMM_SIZE,
        .flags = MMU_DESCRIPTOR_KERNEL_L1_PTE_FLAGS,
        .name = "KernelCached",
    },
    {
        .phys = SYS_MEM_BASE,
        .virt = UNCACHED_VMM_BASE,
        .size = UNCACHED_VMM_SIZE,
        .flags = MMU_INITIAL_MAP_NORMAL_NOCACHE,
        .name = "KernelUncached",
    },
    {
        .phys = PERIPH_PMM_BASE,
        .virt = PERIPH_DEVICE_BASE,
        .size = PERIPH_DEVICE_SIZE,
        .flags = MMU_INITIAL_MAP_DEVICE,
        .name = "PeriphDevice",
    },
    {
        .phys = PERIPH_PMM_BASE,
        .virt = PERIPH_CACHED_BASE,
        .size = PERIPH_CACHED_SIZE,
        .flags = MMU_DESCRIPTOR_KERNEL_L1_PTE_FLAGS,
        .name = "PeriphCached",
    },
    {
        .phys = PERIPH_PMM_BASE,
        .virt = PERIPH_UNCACHED_BASE,
        .size = PERIPH_UNCACHED_SIZE,
        .flags = MMU_INITIAL_MAP_STRONGLY_ORDERED,
        .name = "PeriphStronglyOrdered",
    },
    {
        .phys = GIC_PHY_BASE,
        .virt = GIC_VIRT_BASE,
        .size = GIC_VIRT_SIZE,
        .flags = MMU_INITIAL_MAP_DEVICE,
        .name = "GIC",
    },
    {
        .phys = DDR_RAMFS_ADDR,
        .virt = DDR_RAMFS_VBASE,
        .size = DDR_RAMFS_SIZE,
        .flags = MMU_INITIAL_MAP_DEVICE,
        .name = "Sbull",
    },
    {
        .phys = FB_PHY_BASE,
        .virt = FB_VIRT_BASE,
        .size = FB_SIZE,
        .flags = MMU_INITIAL_MAP_DEVICE,
        .name = "FB",
    },
    {0}
};

我们挑其中一段代码进行分析,详细解析我会单独开一篇文章研究MMU,暂且不关心

    {
        .phys = PERIPH_PMM_BASE,
        .virt = PERIPH_DEVICE_BASE,
        .size = PERIPH_DEVICE_SIZE,
        .flags = MMU_INITIAL_MAP_DEVICE,
        .name = "PeriphDevice",
    },

其中 phys 表示具体的物理地址,0x40000000

​ virt 表示映射的虚拟地址空间,被内核引用

​ size 表示地址段大连续长度,0x20000000

​ flags 表示映射方式,name表示地址段名称

当我们内核驱动需要访问外设时,必须通过虚拟地址进行访问,mmu会根据虚拟地址去操作实际的物理地址,因此,openHarmony控制硬件的具体流程如下:

    1. 根据数据手册找到具体的物理地址,该地址为固定的地址,与数据手册息息相关,板卡移植openHarmony时,必须提供对应的地址映射表给内核。
    2. 内核根据物理地址调用OsalIoRemap方法将物理地址转换为虚拟地址
    3. 内核只能对物理地址进行操作

2. OsalIoRemap函数解析

话不多说,直接看源码

/**
 * @brief Remaps an I/O physical address to its virtual address.
 *
 * @param phys_addr Indicates the I/O physical address.
 * @param size Indicates the size of the physical address to remap.
 * @return Returns the virtual address.
 *
 * @since 1.0
 * @version 1.0
 */
static inline void *OsalIoRemap(unsigned long phys_addr, unsigned long size)
{
    return ioremap(phys_addr, size);
}

可以看到,该函数可以将实际地址转换为内核能使用的虚拟地址。

@param phys_addr :物理地址

@param size :长度

@return 是一个void型的指针,即虚拟地址

使用实例

   //寄存器地址映射
    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;
    }
    /* OsalIoRemap: remap registers */
    stm32gpio->exitBase = OsalIoRemap(stm32gpio->irqPhyBase, stm32gpio->iqrRegStep);
    if (stm32gpio->exitBase == NULL) {
        dprintf("%s: OsalIoRemap fail!", __func__);
        return -1;
    }

使用OsalIoRemap方法将物理地址转换为虚拟地址后,内核就可以尽情控制硬件了。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
1
收藏 1
回复
举报
1条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

赞,感谢楼主耐心分析

回复
2022-3-2 14:12:25
回复
    相关推荐