电源管理入门5 armscmi和mailbox核间通信

2023年 10月 14日 43.6k 0

image.png

上篇介绍了电源管理入门-4子系统reset,提到子系统reset的执行为了安全可以到SCP里面去执行,但是怎么把这个消息传递过去呢,答案就是mailbox。

Mailbox是核间通信软硬件的统称。

  • 在软件上可以使用SCMI协议+共享内存报文头,
  • 在硬件上可以使用PL320或者MHU。

1. 整体架构介绍

image.png

Reset系统架构框图

上图以NPU子模块的服务为例子,Mailbox的硬件使用PL320,整体流程如下:

  • Reset consumer模块执行devm_reset_control_get()获取npu_reset复位句柄,然后通过reset_control_reset()触发复位
  • Linux系统reset framework找到复位驱动并执行ops->reset()回调函数
  • scmi-reset驱动里面提供.reset函数的实现scmi_reset_deassert(),并执行
  • arm-scmi里面提供scmi reset协议的实现模块reset,里面提供reset函数scmi_reset_domain_reset()
  • arm-scmi里面提供scmi协议收发的框架driver,提供do_xfer()
  • arm-scmi里面提供mailbox的接口函数mailbox_send_message()
  • arm-scmi里面提供共享内存的操作函数shmem_tx_prepare()
  • mailbox驱动里面提供硬件PL320的寄存器操作实现pl320_mbox_send_data()
  • SCP中PL320驱动模块接收mailbox中断
  • SCP中SMT模块从共享内存中读取SCMI报文数据
  • SCP中SCMI模块对SCMI协议报文进行解析,并进行分发处理
  • SCP中SCMI RESET DOMAIN协议模块对报文进行功能处理
  • SCP中RESET DOMAIN模块屏蔽硬件差异实现统一API
  • SCP中JUNO RESET DOMAIN模块提供具体硬件CRU寄存器操作实现
  • 2 Linux中reset模块

    image.png

    2.1 Reset consumer

    之前的文章电源管理入门-4子系统reset介绍了怎么使用Linux的reset子系统,这里我们就直接使用,需要在DTS中修改即可。

    reset使用Linux自带的reset框架,假定consumer-firmware-npu这个驱动要使用NPU的reset,定义在DTS中有reset consumer的说明:consumer-firmware-npu。

    / {
            consumer_firmware@0x0 {
                    compatible = "consumer-firmware-npu";
                    reg = ;
                    resets = ;
                    reset-names = "npu_reset";
            };
    };
    

    drivers/firmware/consumer/consumer.c中驱动需要使用reset功能。

    static struct platform_driver consumer_firmware_driver = {
            .driver = {
                    .name = "consumer_firmware",
                    .of_match_table = consumer_firmware_of_match,
            },
            .probe = consumer_firmware_probe,
            .remove = consumer_firmware_remove,
    };
    
    consumer_firmware_probe
    --》devm_reset_control_get //获取"npu_reset"复位句柄
    --》consumer_fw_firmware_cb 
      --》consumer_fw_memcpy //拷贝镜像
      --》consumer_control_reset //通知reset驱动进行reset
    

    这样DTS探测到consumer_firmware的时候就会触发reset操作。

    reset_control_reset
        rstc->rcdev->ops->reset(rstc->rcdev, rstc->id);
    

    reset的provider驱动使用compatible = "scmi-reset";驱动,详细见后面2.2 reset provider中分析。
    当reset时在drivers/reset/reset-scmi.c中实现

    static int
    scmi_reset_reset(struct reset_controller_dev *rcdev, unsigned long id)
    {
            const struct scmi_protocol_handle *ph = to_scmi_handle(rcdev);
    
            return reset_ops->reset(ph, id);
    }
    
    static const struct reset_control_ops scmi_reset_ops = {
            .assert                = scmi_reset_assert,
            .deassert        = scmi_reset_deassert,
            .reset                = scmi_reset_reset,
    };
    

    reset_ops在scmi_reset_probe的时候会赋值

            reset_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_RESET, &ph);
    

    handle->devm_protocol_get为drivers/firmware/arm_scmi/driver.c中scmi_devm_protocol_get,

        pi = scmi_get_protocol_instance(handle, protocol_id);
        return pi->proto->ops;
    

    在scmi协议初始化的时候,scmi_reset_register会注册0x16的回调函数,详细分析见2.2.1 SCMI reset协议初始化内容。
    在drivers/firmware/arm_scmi/reset.c中

    static const struct scmi_reset_proto_ops reset_proto_ops = {
            .num_domains_get = scmi_reset_num_domains_get,
            .name_get = scmi_reset_name_get,
            .latency_get = scmi_reset_latency_get,
            .reset = scmi_reset_domain_reset,
            .assert = scmi_reset_domain_assert,
            .deassert = scmi_reset_domain_deassert,
    };
    

    scmi_reset_domain_reset--》scmi_domain_reset
    ret = ph->xops->do_xfer(ph, t);
    do_xfer在drivers/firmware/arm_scmi/driver.c中实现

    do_xfer(ph, xfer);
    ret = info->desc->ops->send_message(cinfo, xfer);
    

    send_message在drivers/firmware/arm_scmi/mailbox.c中定义

    static const struct scmi_transport_ops scmi_mailbox_ops = {
            .chan_available = mailbox_chan_available,
            .chan_setup = mailbox_chan_setup,
            .chan_free = mailbox_chan_free,
            .send_message = mailbox_send_message,
            .mark_txdone = mailbox_mark_txdone,
            .fetch_response = mailbox_fetch_response,
            .fetch_notification = mailbox_fetch_notification,
            .clear_channel = mailbox_clear_channel,
            .poll_done = mailbox_poll_done,
    };
    

    mailbox_send_message见3.2中分析

    2.2 Reset provider

    reset的provider是scmi-reset驱动,DTS中设置如下:

        scmi_reset: protocol@16 {
                reg = ;
                #reset-cells = ;
        };
    

    代码位置在:drivers/reset/reset-scmi.c

    static struct scmi_driver scmi_reset_driver = {
            .name = "scmi-reset",
            .probe = scmi_reset_probe,
            .id_table = scmi_id_table,
    };
    module_scmi_driver(scmi_reset_driver);
    

    scmi_reset_probe的定义如下:

    static int scmi_reset_probe(struct scmi_device *sdev)
    {
            reset_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_RESET, &ph);
    
            data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
            data->rcdev.ops = &scmi_reset_ops;
            data->rcdev.owner = THIS_MODULE;
            data->rcdev.of_node = np;
            data->rcdev.nr_resets = reset_ops->num_domains_get(ph);
            data->ph = ph;
    
            return devm_reset_controller_register(dev, &data->rcdev);//进行驱动注册
    }
    

    handle->devm_protocol_get为scmi_devm_protocol_get,这里面发送了三条0x16的scmi消息
    scmi_devm_protocol_get->scmi_get_protocol_instance

    scmi_get_protocol_instance里面发送了三条0x16的scmi消息,来获取reset的版本号,支持那些devices等信息

    3. Linux SCMI reset通信

    image.png

    如上图中,Linux通过非安全通道跟SCP交互。

    3.1 SCMI reset协议初始化

    系统初始化的时候会执行subsys_initcall(scmi_driver_init);在drivers/firmware/arm_scmi/driver.c中:

    static int __init scmi_driver_init(void)
    {
            int ret;
    
            /* Bail out if no SCMI transport was configured */
            if (WARN_ON(!IS_ENABLED(CONFIG_ARM_SCMI_HAVE_TRANSPORT)))
                    return -EINVAL;
    
            scmi_bus_init();
    
            /* Initialize any compiled-in transport which provided an init/exit */
            ret = scmi_transports_init();
            if (ret)
                    return ret;
    
            scmi_base_register();
    
            scmi_clock_register();
            scmi_perf_register();
            scmi_power_register();
            scmi_reset_register();
            scmi_sensors_register();
            scmi_voltage_register();
            scmi_system_register();
    
            return platform_driver_register(&scmi_driver);
    }
    

    scmi_driver的定义为:

    static struct platform_driver scmi_driver = {
            .driver = {
                       .name = "arm-scmi",
                       .suppress_bind_attrs = true,
                       .of_match_table = scmi_of_match,
                       .dev_groups = versions_groups,
                       },
            .probe = scmi_probe,
            .remove = scmi_remove,
    };
    

    drivers/firmware/arm_scmi/driver.c中scmi_probe函数

    static int scmi_probe(struct platform_device *pdev)
    {
        ret = scmi_txrx_setup(info, dev, SCMI_PROTOCOL_BASE);
        
        ret = scmi_xfer_info_init(info);、
        
        ret = scmi_protocol_acquire(handle, SCMI_PROTOCOL_BASE);
    

    scmi_txrx_setup中会调用mailbox_chan_setup函数

        size = resource_size(&res);
        smbox->shmem = devm_ioremap(dev, res.start, size);
        smbox->chan = mbox_request_channel(cl, tx ? 0 : 1);
    

    scmi_protocol_acquire()函数

    int scmi_protocol_acquire(const struct scmi_handle *handle, u8 protocol_id)
    {
            return PTR_ERR_OR_ZERO(scmi_get_protocol_instance(handle, protocol_id));
    }
    scmi_get_protocol_instance
        scmi_alloc_init_protocol_instance(info, proto);
            ret = pi->proto->instance_init(&pi->ph);
    

    drivers/firmware/arm_scmi/base.c中定义了instance_init

    static int scmi_base_protocol_init(const struct scmi_protocol_handle *ph)
        ret = ph->xops->version_get(ph, &version);
    
    static const struct scmi_xfer_ops xfer_ops = {
            .version_get = version_get,
            .xfer_get_init = xfer_get_init,
            .reset_rx_to_maxsz = reset_rx_to_maxsz,
            .do_xfer = do_xfer,
            .do_xfer_with_response = do_xfer_with_response,
            .xfer_put = xfer_put,
    };
    

    version_get中进行scmi的发送

    static int version_get(const struct scmi_protocol_handle *ph, u32 *version)
    {
            ret = xfer_get_init(ph, PROTOCOL_VERSION, 0, sizeof(*version), &t);
            ret = do_xfer(ph, t);
            xfer_put(ph, t);
    

    xfer_get_init中进行了赋值xfer->hdr.id = msg_id;
    do_xfer进行了发送操作,之后等待回复

        ret = info->desc->ops->send_message(cinfo, xfer);
    
        /* And we wait for the response. */
        timeout = msecs_to_jiffies(info->desc->max_rx_timeout_ms);
        if (!wait_for_completion_timeout(&xfer->done, timeout)) {
                dev_err(dev, "timed out in resp(caller: %pS)n",
                        (void *)_RET_IP_);
                ret = -ETIMEDOUT;
        }
    

    send_message在drivers/firmware/arm_scmi/mailbox.c中定义

    static const struct scmi_transport_ops scmi_mailbox_ops = {
            .chan_available = mailbox_chan_available,
            .chan_setup = mailbox_chan_setup,
            .chan_free = mailbox_chan_free,
            .send_message = mailbox_send_message,
            .mark_txdone = mailbox_mark_txdone,
            .fetch_response = mailbox_fetch_response,
            .fetch_notification = mailbox_fetch_notification,
            .clear_channel = mailbox_clear_channel,
            .poll_done = mailbox_poll_done,
    };
    

    mbox_send_message就是mailbox提供的发消息接口函数,详细介绍见3.2中

    初始化的时候不仅初始化了scmi协议还调用了scmi_reset_register();注册了0x16的scmi reset协议
    在drivers/firmware/arm_scmi/reset.c中

    static const struct scmi_protocol scmi_reset = {
            .id = SCMI_PROTOCOL_RESET,
            .owner = THIS_MODULE,
            .instance_init = &scmi_reset_protocol_init,
            .ops = &reset_proto_ops,
            .events = &reset_protocol_events,
    };
    
    DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(reset, scmi_reset)
    
    #define DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(name, proto)        
    static const struct scmi_protocol *__this_proto = &(proto);        
                                                                    
    int __init scmi_##name##_register(void)                                
    {                                                                
            return scmi_protocol_register(__this_proto);                
    }                
    

    3.2 SCMI reset消息收发

    image.png

  • A核先往某个指定共享内存空间buffer写数据,然后写入共享内存空间的地址信息到相应通道数据寄存器,mailbox触发中断给R核;
  • M核(SCP)通过得到mailbox中断,获取共享内存相应offset,读取buffer数据;
  • M核(SCP)通过mailbox触发中断通知A核接收消息完毕。
  • PL320和MHU硬件的区别?

    PL320带传输数据和中断功能,但是数据量比较小7*32bit。对于新的SoC来说数据传输基本都使用共享内存,PL320自带的数据传输基本用不上了,所以其算过时了。新的MHU只保留了中断功能,并且是1对1的集成,核间通信时成对出现,用几个加几个更加的灵活,PL320是一次32个通道集成进SoC的,也可能浪费。

    我们以PL320为例,只使用其中断,数据还是通过共享内存传输,驱动跟MHU原理差不多。关于PL320,可以参考ARM官网的文档,后面会专门写一个核间通信的专题介绍下。

    在drivers/mailbox/mailbox.c中,mailbox_send_message发消息的时候会调用mbox_send_message

    mailbox_send_message
    --》mbox_send_message
      --》msg_submit
      
    static void msg_submit(struct mbox_chan *chan)
    {
            data = chan->msg_data[idx];
            if (chan->cl->tx_prepare)
                    chan->cl->tx_prepare(chan->cl, data);
            err = chan->mbox->ops->send_data(chan, data);
    }
    

    tx_prepare-》shmem_tx_prepare会往共享内存里面存入数据,在drivers/firmware/arm_scmi/shmem.c中

    void shmem_tx_prepare(struct scmi_shared_mem __iomem *shmem,
                          struct scmi_xfer *xfer)
    {
            spin_until_cond(ioread32(&shmem->channel_status) &
                            SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE);
            iowrite32(0x0, &shmem->channel_status);
            iowrite32(xfer->hdr.poll_completion ? 0 : SCMI_SHMEM_FLAG_INTR_ENABLED,
                      &shmem->flags);
            iowrite32(sizeof(shmem->msg_header) + xfer->tx.len, &shmem->length);
            iowrite32(pack_scmi_header(&xfer->hdr), &shmem->msg_header);
            pr_info("#### shmem_tx_prepare shmem->msg_header=0x%xn", shmem->msg_header);
            if (xfer->tx.buf){
                    memcpy_toio(shmem->msg_payload, xfer->tx.buf, xfer->tx.len);
            pr_info("#### shmem_tx_prepare shmem->msg_payload[0]=0x%xn", (int)shmem->msg_payload[0]);
            }
    }
    

    发消息drivers/mailbox/pl320-ipc.c中pl320_mbox_send_data函数

    static const struct mbox_chan_ops pl320_mbox_ops = {
            .send_data        = pl320_mbox_send_data,
    };
    

    ops->send_data-》pl320_mbox_send_data-》__ipc_send(pl320_id, ch, buf);会触发中断

    static void __ipc_send(int pl320_id, int mbox, u32 *data)
    {
            ipc_base = get_ipc_base(pl320_id);
    
            for (i = 0; i < MBOX_MSG_LEN; i++)
                    writel_relaxed(data[i], ipc_base + IPCMxDR(mbox, i));
    
            if (mbox % 2 == 0)
                    writel_relaxed(0x1, ipc_base + IPCMxSEND(mbox));
            else
                    writel_relaxed(0x2, ipc_base + IPCMxSEND(mbox));
    }
    

    收消息,drivers/mailbox/pl320-ipc.c中ipc_handler

            for (idx = 0; idx id), 0), &protocol_api);
            //使用拿到的api获取scmi协议id号
            status = protocol_api->get_scmi_protocol_id(protocol->id,
                                                        &scmi_protocol_id);
            FWK_LOG_INFO("[SCMI] Support scmi_protocol_id:0x%x", scmi_protocol_id));
    
            scmi_ctx.scmi_protocol_id_to_idx[scmi_protocol_id] =
                (uint8_t)(protocol_idx + PROTOCOL_TABLE_RESERVED_ENTRIES_COUNT);
            protocol->message_handler = protocol_api->message_handler;
        }
    

    protocol 是scmi协议的module,首先绑定这个module拿到两个api

    static struct mod_scmi_to_protocol_api scmi_reset_mod_scmi_to_protocol_api = {
        .get_scmi_protocol_id = scmi_reset_get_scmi_protocol_id,
        .message_handler = scmi_reset_message_handler
    };
    scmi_reset_get_scmi_protocol_id为获取协议id
    /*!
     * brief SCMI Reset Domain Protocol
     */
    #define MOD_SCMI_PROTOCOL_ID_RESET_DOMAIN UINT32_C(0x16)
    
    static int scmi_reset_get_scmi_protocol_id(fwk_id_t protocol_id,
                                               uint8_t *scmi_protocol_id)
    {
        *scmi_protocol_id = MOD_SCMI_PROTOCOL_ID_RESET_DOMAIN;
    
        return FWK_SUCCESS;
    }
    

    4.3 scmi_reset_domain消息处理

    协议模块负责处理reset相关的所有协议子命令,对于scmi_reset_domain一共支持6个子命令,如下:

    enum scmi_command_id {
        MOD_SCMI_PROTOCOL_VERSION = 0x000,
        MOD_SCMI_PROTOCOL_ATTRIBUTES = 0x001,
        MOD_SCMI_PROTOCOL_MESSAGE_ATTRIBUTES = 0x002
    };
    enum scmi_reset_domain_command_id {
        MOD_SCMI_RESET_DOMAIN_ATTRIBUTES = 0x03,
        MOD_SCMI_RESET_REQUEST = 0x04,
        MOD_SCMI_RESET_NOTIFY = 0x05,
        MOD_SCMI_RESET_COMMAND_COUNT,
    };
    

    我们需要在协议模块scmi_reset_domain中,给这些命令设计处理函数如下:

    static int (*msg_handler_table[])(fwk_id_t, const uint32_t *) = {
        [MOD_SCMI_PROTOCOL_VERSION] = protocol_version_handler,
        [MOD_SCMI_PROTOCOL_ATTRIBUTES] = protocol_attributes_handler,
        [MOD_SCMI_PROTOCOL_MESSAGE_ATTRIBUTES] =
             protocol_message_attributes_handler,
        [MOD_SCMI_RESET_DOMAIN_ATTRIBUTES] = reset_attributes_handler,
        [MOD_SCMI_RESET_REQUEST] = reset_request_handler,
    #ifdef BUILD_HAS_SCMI_NOTIFICATIONS
        [MOD_SCMI_RESET_NOTIFY] = reset_notify_handler,
    #endif
    };
    

    我们以为reset_request_handler例,进行说明

    image.png

    4.3.1 scmi_reset_domain中处理

    static struct mod_scmi_to_protocol_api scmi_reset_mod_scmi_to_protocol_api = {
        .get_scmi_protocol_id = scmi_reset_get_scmi_protocol_id,
        .message_handler = scmi_reset_message_handler
    };
    

    scmi_reset_message_handler()函数中会根据命令id找到处理函数执行
    msg_handler_table[message_id](service_id, payload);
    message_id就是cmd id,payload就是协议携带的数据部分。

    [MOD_SCMI_RESET_REQUEST] = reset_request_handler,
    

    reset_request_handler中会解析payload,并对payload的数据大小进行校验,然后进行解析

    struct scmi_reset_domain_request_a2p {
        uint32_t domain_id;
        uint32_t flags;
        uint32_t reset_state;
    };
    
       params = *(const struct scmi_reset_domain_request_a2p *)payload;
       status = get_reset_device(service_id, params.domain_id, &reset_device);
        status = scmi_reset_domain_reset_request_policy(&policy_status,
            &mode, &reset_state, agent_id, params.domain_id);
    
        status = reset_api->set_reset_state(reset_device->element_id,
                                            mode,
                                            reset_state,
                                            (uintptr_t)agent_id);
    

    reset_api->set_reset_state是从HAL层拿到的api

    4.3.2 reset-domain HAL层

    /* HAL API */
    static const struct mod_reset_domain_drv_api reset_api = {
        .set_reset_state = set_reset_state,
    };
    
    static int set_reset_state(fwk_id_t reset_dev_id,
                               enum mod_reset_domain_mode mode,
                               uint32_t reset_state,
                               uintptr_t cookie)
    {
        struct rd_dev_ctx *reset_ctx;
        unsigned int reset_domain_idx = fwk_id_get_element_idx(reset_dev_id);
        FWK_LOG_INFO("[RESET DOMAIN] set_reset_state");
    
        reset_ctx = &module_reset_ctx.dev_ctx_table[reset_domain_idx];
    
        return reset_ctx->driver_api->set_reset_state(reset_ctx->config->driver_id,
                                                      mode, reset_state, cookie);
    }
    

    从driver层拿到api进行处理

    4.3.3 juno-reset-domain驱动层

    static struct mod_reset_domain_drv_api juno_reset_domain_drv_api = {
        .set_reset_state = juno_set_reset_state,
    };
    
    static int juno_set_reset_state(
        fwk_id_t dev_id,
        enum mod_reset_domain_mode mode,
        uint32_t reset_state,
        uintptr_t cookie)
    {
        unsigned int domain_idx = fwk_id_get_element_idx(dev_id);
        dev_ctx = &module_juno_reset_ctx.dev_ctx_table[domain_idx];
        
        if (domain_idx == juno_RESET_DOMAIN_IDX_NPU) {
            status = handle_dev_reset_set_state(dev_ctx);
            if (status != FWK_SUCCESS) {
                return status;
            }
        } 
    
        return FWK_SUCCESS;
    }
    

    handle_dev_reset_set_state里面处理具体的硬件寄存器操作:

    /* Helper functions */
    static int handle_dev_reset_set_state(struct juno_reset_dev_ctx *dev_ctx)
    {
        /* Reset device */
        dev_ctx->reset_state = DEVICE_STATE_RESET;
        *dev_config->reset_reg = 0;
        for (int j = 0; j reset_reg = 1;
    
        *dev_config->clkctl_reg = 1;
    
        dev_ctx->reset_state = DEVICE_STATE_NORMAL;
    

    根据2.4章节中CRU硬件,这里需要对reset寄存器和clk寄存器进行写操作来实现其他硬件模块的reset功能。

    5. 硬件CRU设计

    CRU(Clock & Reset Unit)位于SCP子系统中,受SCP软件控制,然后硬件信号会连接到其他子系统上,我们的SCP固件一般运行在ARM的M核心上,还有一堆外围的器件,例如NXP的imx8qm里面:

    image.png

    将clock gate信号和reset信号送给多个子系统(有时钟控制、reset控制等需求的子系统)。通过SCP软件来操作CRU的相关寄存器,从而实现对子系统时钟和复位信号的控制,

    image.png

    芯片手册会给出控制CRU的reset及clk的寄存器配置,操作响应的寄存器即可。

    后记:

    本小节介绍比较详细,其实很多知识点都是相通的,例如SCMI、SCP、Mailbox、DTS这些东西,早晚都需要掌握,但是通过一个业务流程或者场景就可以学习到,本文就是一个了解这些知识的机会。

    “啥都懂一点,啥都不精通,

    干啥都能干,干啥啥不是,

    专业入门劝退,堪称程序员杂家”。

    后续会继续更新,纯干货分析,欢迎分享给朋友,欢迎评论交流!

    相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论