高通与At指令:ATFWD解析
背景
本章的内容是适用于AP侧AT指令开发调试的有关人员。
主要是介绍高通实现的ATFWD框架。在这需要说明一下的是,或许你对AT Command很了解了,但是却貌似都不知道ATFWD,这很正常,严格来说,ATFWD都不算属于AT Command框架的一部分,只是高通对扩展的at命令做的一个扩展实现。
我们之前说到,ATCommands以处理方式可以分有两类,一类是直接在modem下进行处理的,还有一部分是在AP侧进行处理更加方便有效的。
而对于APSide的AT命令,高通也提供了一套框架进行实现,在这我们就这一块做详细的学习。
同样的,AT命令通过ATCoP从串口传来并被解析,而对于APSide的AT命令我们会通过allow_list[]
数组注册,这个时候modem会判断传来的命令是不是AP相关的,如果是,通过qmi通讯将AT命令传到AP侧进行处理,而在AP侧的流程便是通过ATFWD框架实现的。
因此,只要有ATCoP,那么有关的指令就需要注册到ATCoP中
在某个新基线上移植AT指令,发现有问题,因此收集了这个系列的 文章 作为 这方面的知识补充。
原文作者:laozhuxinlu,本文有删改。
AT指令在产线中是一类比较重要的问题, 一天没来得及解决,则会拖延生产的有关进度。
ATFWD 主要是与 含有 modem 的有关异构处理器有关的。
如果 对应的高通平台 没有 modem 处理器(例如 SDM845),则使用 port-bridge 的方式进行 AT 实现,后续我们会讲到。
代码解析
在vendor下,一般是在vendor/qcom/proprietary/telephony-apps/ATFWD-daemon
目录下,我们能看到ATFWD的具体实现:
有时候也可能存在于
vendor/qcom/proprietary/data/ATFWD-daemon
下
- Android.mk:编译一个主进程(ATFWD-daemon)到system/bin下面做实时监听从modem下传来的AT命令。
- atfwd_daemon.c:主进程的main函数定义,做AT命令的注册已经QMI(Qualcom Message Interface 高通信息接口 )初始化,并循环监听传来的AT命令并处理返回。
- sendcmd.cpp:初始化获取binder服务,以此实现将AT命令传到实际处理的地方。
- IAtCmdFwd.cpp:对binder服务的定义。
main
ATFWD-daemon进程的入口是main()函数
/*=========================================================================
FUNCTION: main
===========================================================================*/
/*!
@brief
Initialize the QMI connection and register the ATFWD event listener.
argv[1] if provided, gives the name of the qmi port to open.
Default is "rmnet_sdio0".
*/
/*=========================================================================*/
int main (int argc, char **argv)
{
AtCmdResponse *response;
int i, connectionResult, initType;
userHandle = userHandleSMD = -1;
i = connectionResult = 0;
printf("*** Starting ATFWD-daemon *** \n");
(void) getTargetFromSysProperty();
if ( !is_supported_qcci() )
{
if (!strncmp(ATFWD_DATA_TARGET_APQ, target,
strlen(target))) {
printf("APQ baseband : Explicitly stopping ATFWD service....\n");
stopSelf();
return -1;
}
if (argc >= 2) {
qmiPort = argv[1];
} else {
qmiPort = getDefaultPort();
if( NULL == qmiPort ) {
qmiPort = DEFAULT_QMI_PORT;
}
}
if (argc >= 3) {
secondaryPort = argv[2];
} else if (!strncmp(ATFWD_DATA_TARGET_SVLTE2A, target, strlen(target))) {
/* For SVLTE type II targets, Modem currently exposes two ATCOP ports.
* One bridged from USB to SDIO, directly talking to 9k modem
* Another bridged from USB to SMD, directly talking to 8k
* Therefore given this modem architecture, ATFWD-daemon needs to
* listen to both the modems( 8k & 9K).
* Register with 8k modem
*/
secondaryPort = DEFAULT_SMD_PORT;
} else if (!strncmp(ATFWD_DATA_TARGET_SGLTE, target, strlen(target))) {
// For SGLTE targets, Register with the SMUX port.
secondaryPort = QMI_PORT_RMNET_SMUX_0;
}
}
printf("init all signals\n");
signalInit();
pthread_mutexattr_t attr;
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&ctrMutex, &attr);
pthread_cond_init(&ctrCond, NULL);
printf("Explicitly disbling qmux \n");
qmi_cci_qmux_xport_unregister(QMI_CLIENT_QMUX_RMNET_INSTANCE_0);
qmi_cci_qmux_xport_unregister(QMI_CLIENT_QMUX_RMNET_USB_INSTANCE_0);
qmi_cci_qmux_xport_unregister(QMI_CLIENT_QMUX_RMNET_SMUX_INSTANCE_0);
qmi_cci_qmux_xport_unregister(QMI_CLIENT_QMUX_RMNET_MHI_INSTANCE_0);
printf("Disabling QMUX complete...\n");
//Get QMI service object
printf("getting at svc obj for access terminal QMI svc\n");
qmi_at_svc_obj = at_get_service_object_v01();
for (initType = INIT_QMI; initType != INIT_MAX; initType++) {
connectionResult = 0;
tryInit (initType, &connectionResult);
printf(" tryinit complete with connectresult: %d\n", connectionResult);
if (connectionResult < 0)
{
if ( !is_supported_qcci() )
{
if (qmiHandle >= 0) {
qmi_release(qmiHandle);
}
}
else
{
printf("Release qmi_client...\n");
qmi_client_release(qmi_at_svc_client);
qmi_at_svc_client = NULL;
}
stopSelf();
return -1;
}
}
else
{
if (!registerATCommands())
{
stopSelf();
return -1;
}
}
while (1) {
pthread_mutex_lock(&ctrMutex);
while (!isNewCommandAvailable()) {
printf("Waiting for ctrCond");
pthread_cond_wait(&ctrCond, &ctrMutex);
printf("Recieved ctrCond: p: %d, S:%d, nr: %d",regForPrimaryPort, regForSecondaryPort, newRequest );
}
if ( !is_supported_qcci() )
{
if (regForPrimaryPort == 1) {
if (qmiPort) {
printf("Rcvd pthread notification for primary QMI port registration");
initAtcopServiceAndRegisterCommands(qmiPort, &userHandle);
} else {
printf("Notification for primary QMI port registration when NOT valid, ignore...");
}
regForPrimaryPort = 0;
}
if (regForSecondaryPort == 1) {
if (secondaryPort) {
printf("Rcvd pthread notification for secondary QMI port registration");
initAtcopServiceAndRegisterCommands(secondaryPort, &userHandleSMD);
} else {
printf("Notification for secondary QMI port registration when NOT valid, ignore...");
}
regForSecondaryPort = 0;
}
if(userHandle < 0 && userHandleSMD < 0)
{
printf("userhandle(s) for both 8k and 9k modems NOT valid -- bail out");
if (qmiHandle >= 0)
{
qmi_release(qmiHandle);
}
stopSelf();
return -1;
}
}
else
{
if ( regForPrimaryPort == 1)
{
printf("Registering for primary port (QCCI).");
connectionResult = 0;
tryInit (INIT_QMI_SRVC, &connectionResult);
printf(" init result: %d\n", connectionResult);
if (connectionResult < 0)
{
printf("Release qmi_client...\n");
qmi_client_release(qmi_at_svc_client);
qmi_at_svc_client = NULL;
stopSelf();
return -1;
}
if (!registerATCommands())
{
printf("Register for primary port (QCCI) failed.");
stopSelf();
return -1;
}
regForPrimaryPort=0;
}
if ( regForServiceUp == 1 )
{
regForServiceUpEvent();
}
}
if (newRequest == 1) {
printf("pthread notified for new request; sending response.");
response = sendit(&fwdcmd);
if (response == NULL) {
printf("Response processing complete、Invalid cmd resp.");
sendInvalidCommandResponse();
printf("Invalid response sending complete.");
} else {
printf("Response processing complete、Sending response.");
sendResponse(response);
printf("Send response complete.");
}
if (fwdcmd.name) free(fwdcmd.name);
if (fwdcmd.tokens) {
for (i = 0; i < fwdcmd.ntokens; i++) {
free(fwdcmd.tokens[i]);
}
free(fwdcmd.tokens);
}
freeAtCmdResponse(response);
newRequest = 0;
printf("New request processing complete.");
}
pthread_mutex_unlock(&ctrMutex);
}
return 0;
}
tryInit
在main中会调用tryInit()
:实现三块的初始化:
- 首先是QMI的初始化;其次是进行QMI连接的初始化:这两块主要是实现能接受到从modem下传上来的At命令。
- 最后是对binder服务的获取初始化:以便能把相应的命令通过binder通讯方式传到相应的地方进行处理。
void tryInit (atfwd_init_type_t type, int *result) {
LOGI("ATFWD :Going to tryInit ATFWD daemon\n");
int retryCnt = 1;
for (; retryCnt <= ATFWD_MAX_RETRY_ATTEMPTS; retryCnt++) {
LOGI("ATFWD :retryCnt <= ATFWD_MAX_RETRY_ATTEMPTS\n");
qmiErrorCode = 0;
switch (type) {
// 初始化QMI
case INIT_QMI:
LOGI("ATFWD :Going to qmi_init(atfwdSysEventHandler)\n");
qmiHandle = qmi_init(atfwdSysEventHandler, NULL);
*result = qmiHandle;
break;
// 连接QMI(实现能接受到从modem下传上来的At命令)
case INIT_QMI_SRVC:
LOGI("ATFWD :Going to qmi_connection_init(qmiPort, &qmiErrorCode)\n");
*result = qmi_connection_init(qmiPort, &qmiErrorCode);
break;
// 获取binder服务,以便能把相应的命令通过binder通讯方式传到相应的地方进行处理
case INIT_ATFWD_SRVC:
LOGI("ATFWD :Going to initializeAtFwdService(case INIT_ATFWD_SRVC)\n");
*result = initializeAtFwdService();
break;
default:
LOGI("Invalid type %d", type);
return;
}
LOGI("ATFWD :result : %d \t ,Init step :%d \t ,qmiErrorCode: %d", *result, type, qmiErrorCode);
if (*result >= 0 && qmiErrorCode == 0) {
break;
}
sleep(retryCnt * ATFWD_RETRY_DELAY);
}
return;
}
initAtcopServiceAndRegisterCommands
初始化并注册所有其添加的At命令;
// 对应的命令
qmi_atcop_at_cmd_fwd_req_type atCmdFwdReqType[] = {
{ //AT command fwd type
1, // Number of commands
{
{ QMI_ATCOP_AT_CMD_NOT_ABORTABLE, "+CKPD"},
}
},
{ //AT command fwd type
1, // Number of commands
{
{ QMI_ATCOP_AT_CMD_NOT_ABORTABLE, "+CTSA"},
}
},
{ //AT command fwd type
1, // Number of commands
{
{ QMI_ATCOP_AT_CMD_NOT_ABORTABLE, "+CFUN"},
}
},
// ...
};
void initAtcopServiceAndRegisterCommands(const char *port, int *handle) {
int i, nErrorCnt, nCommands;
i = nErrorCnt = 0;
initAtCopServiceByPort(port, handle);
if (*handle > 0) {
nCommands = sizeof(atCmdFwdReqType) / sizeof(atCmdFwdReqType[0]);
printf("Trying to register %d commands:\n", nCommands);
for (i = 0; i < nCommands ; i++) {
printf("cmd%d: %s\n", i, atCmdFwdReqType[i].qmi_atcop_at_cmd_fwd_req_type[0].at_cmd_name);
qmiErrorCode = 0;
int registrationStatus = qmi_atcop_reg_at_command_fwd_req(*handle, \
&atCmdFwdReqType[i], &qmiErrorCode);
printf("qmi_atcop_reg_at_command_fwd_req: %d", qmiErrorCode);
if (registrationStatus < 0 || qmiErrorCode != 0) {
printf("Could not register AT command : %s with the QMI Interface - Err code:%d\n",
atCmdFwdReqType[i].qmi_atcop_at_cmd_fwd_req_type[0].at_cmd_name, qmiErrorCode);
nErrorCnt++;
qmiErrorCode = 0;
}
}
if(nErrorCnt == nCommands) {
printf("AT commands registration failure..、Release client handle: %d\n", *handle);
qmi_atcop_srvc_release_client(*handle, &qmiErrorCode);
*handle = -1;
return;
}
} else {
printf("ATcop Service Init failed\n");
return;
}
printf("Registered AT Commands event handler\n");
return;
}
等待新命令
此后,函数会循环在while(1)中,当modem下有传来需要处理的命令的时候,newRequest会置为1,走if(newRequest == 1){……}
if (newRequest == 1) {
LOGI("pthread notified for new request\n");
response = sendit(&fwdcmd);
if (response == NULL) {
sendInvalidCommandResponse();
} else {
sendResponse(response);
}
if (fwdcmd.name) free(fwdcmd.name);
if (fwdcmd.tokens) {
for (i = 0; i < fwdcmd.ntokens; i++) {
free(fwdcmd.tokens[i]);
}
free(fwdcmd.tokens);
}
freeAtCmdResponse(response);
newRequest = 0;
}
main()函数下调用sendit(&fwdcmd)
函数将命令传递出去,并将返回的结果给response数据结构;
Sendit
typedef struct {
int opcode;
char *name; // 指令名称, AT+abc --> abc
int ntokens; // 有多少个参数
char **tokens; // 参数数组
} AtCmd;
typedef struct {
int result;
char *response;
} AtCmdResponse;
extern "C" AtCmdResponse *sendit(const AtCmd *cmd)
{
AtCmdResponse *result = NULL;
result = new AtCmdResponse;
result->response = NULL;
LOGI("sendit");
LOGE("%s:%d peeta", __func__, __LINE__);
if(strcasecmp(cmd->name, "+QFCT")==0){
LOGI("ATFWD AtCmdFwd QFCT");
if(NULL != cmd->tokens) {
LOGI("ATFWD AtCmdFwd Tokens Not NULL ntokens=%d",cmd->ntokens);
if(cmd->ntokens == 0 || cmd->tokens[0] == NULL){
LOGI("ATFWD AtCmdFwd Tokens[0] is NULL");
quec_qfct_handle(result);
}else if(0 == strncmp("wifi-kill",cmd->tokens[0],strlen("wifi-kill"))){
// char *args[5] = { PTT_SOCKET_BIN, "-f", "-d", "-v", NULL };
LOGI("ATFWD AtCmdFwd:%s",cmd->tokens[0]);
property_set("wifi.ptt_socket_app", "false");
property_set("wifi.p_socket_app", "true");
//...
}else{
LOGI("ATFWD AtCmdFwd Tokens is NULL");
quec_qfct_handle(result);
}
}else if(strcasecmp(cmd->name, "+QGMR")==0)
{
quec_qgmr_handle(cmd,result);
}
return result;
}
如果使用到了binder,还可以这样:sendit()函数调用processCommand()函数,processCommand是继承于BpInterface类实现的,我们通过Parcel数据将数据写入data下,然后通过调用唤起RPC进行binder数据通讯:remote()->transact(processAtCmd,data, &reply);
extern "C" AtCmdResponse *sendit(const AtCmd *cmd)
{
AtCmdResponse *result;
if (!cmd) return NULL;
result = gAtCmdFwdService->processCommand(*cmd);
return result;
}
以上主要就是一个ATCommand在ATFWD下的大致流程了。
总结
简单的说,那就是一个进程,进行数据中转的进程:数据从modem下传上来先通过venderril,再从venderril下传到framework(或者别的什么地方)下进行处理,ATFWD便是venderril下的一个中转站。其中与modem的通讯方式采用QMI,与framework采用bingerserver方式通讯。
ATFWD调试技巧
如果在AT指令的实现中遇到了某些问题,可以按照下面的流程进行分析。
0、确保ATFWD进程正常执行,如果没有,则根据log确定 是 中途退出(没走完流程)还是 Android系统的权限问题。
AT Command流程分析之具体实现
主要是介绍作为一个AT Command的开发者,具体如何参与到代码的开发。当然,这里主要是介绍一些基本的开发工作……
想必从前面的学习,你已经了解到AT命令执行的大致流程,基于这个流程,AT Command的功能开发也主要是包括在两个方面:
- BP Side类型的AT命令开发
- AP Side类型的AT命令开发
BP侧
首先是BP Side类型的AT命令开发,或者说如何在ATCoP上去扩展实现实现一个AT命令。
我们知道AT命令分有以下几种类型,在这我们以最常见的扩展AT命令为例,命名:”+CLAY”。
- 基本 AT 命令(basic_table)
- 寄存器 AT 命令(sreg_table)
- 扩展 AT 命令(extended_table)
- 厂商 AT 命令(vendor_table)
定义指针变量
在dsati.h下的dsatetsi_ext_action_index_enum_type枚举数组中添加一个指针变量如下:
DSATETSI_EXT_ACT_CLAY_ETSI_IDX = 14084
建立AT命令和处理函数的映射
在dsatetsictab.c下的dsatetsi_ext_action_table_ex []数据下添加映射:
//...
{DSATETSI_EXT_ACT_CLAY_ETSI_IDX, dsatetsime_exec_clay_cmd }
// ...
定义AT命令
如果想要定义一个at命令,需要首先确定它的命令表项,也就是name、属性、参数情况、处理函数指针等……
下面我们增加的是一个最简单的命令,name是”+CLAY”,属性是无参数。
在dsatetsictab_ex.c下的dsatetsi_ext_action_table []
数组中添加:
{ "+CLAY", READ_ONLY | COMMON_CMD,
SPECIAL_NONE, 0,DSATETSI_EXT_ACT_CLAY_ETSI_IDX
}
具体的含义请参见 AT Command流程分析之AtCop解析模块。
声明处理函数
上面完成以后就能定义其实际的处理函数了,在定义之前,我们先要声明一下,在dsatetsime.h下添加:
dsat_result_enum_type dsatetsime_exec_clay_cmd (
dsat_mode_enum_typemode, /*AT command mode: */
constdsati_cmd_type *parse_table, /*Ptr to cmd in parse table */
consttokens_struct_type *tok_ptr, /*Command tokens from parser */
dsm_item_type*res_buff_ptr /* Place to put response */
);
定义处理函数
dsat_result_enum_type dsatetsime_exec_clay_cmd (
dsat_mode_enum_typemode, /*AT command mode: */
constdsati_cmd_type *parse_table, /*Ptr to cmd in parse table */
consttokens_struct_type *tok_ptr, /*Command tokens from parser */
dsm_item_type*res_buff_ptr /* Place to put response */
){
dsat_result_enum_type result= DSAT_OK;
if(tok_ptr->op == NA){
res_buff_ptr->used =(word) snprintf ((char*)res_buff_ptr->data_ptr,
res_buff_ptr->size,
"%s: %s,%s",
"+CLAY",
"hello",
"world");
}
else if(tok_ptr->op ==(NA|EQ|QU)){} //针对其他的语法格式进行处理
else if(tok_ptr->op ==(NA|QU)){} //针对其他的语法格式进行处理
else if(tok_ptr->op ==(NA|EQ|AR)){} //针对其他的语法格式进行处理
else{ result= DSAT_ERROR;} //针对错误的语法格式进行处理
return result;
}
至此,一个BP Site的自定义AT 命令便开发完成了,这里需要注意的是,这边只是举例实现,而且对AT+CLAY的命令类型作为扩展命令开发的,就所以流程仅供参考……
AP侧
那么,要想扩展添加一个AP Site的AT命令又该如何呢?
首先要确认ATFWD在设备中已添加注册并正常运行(可在system/bin下去查看)
同样的,这里就AT+CLAY举例实现……
1、在Modem侧添加自定义的AT Command的注册。
在*/amss_8909/modem_proc/datamodem/interface/atcop/src/dsatclient_ex.c
下的LOCAL byte allowed_list[][MAX_CMD_SIZE]数组中添加定义:
LOCAL byte allowed_list[][MAX_CMD_SIZE]={……,"+CLAY",""};
2、 AP侧的Vendor下添加AT Command的注册。
在*/vendor/qcom/proprietary/data/ATFWD-daemon/atfwd_daemon.c
下的qmi_atcop_at_cmd_fwd_req_type atCmdFwdReqType[]数组中添加定义:
{ //AT command fwd type
1, // Number of commands
{
{ QMI_ATCOP_AT_CMD_NOT_ABORTABLE, "+CLAY"},
}
},
3、 添加在framework侧的实际处理并返回处理结果。之前我们说过,AP Site的AT命令在串口经过modem通过QMI通讯方式将命令传到Vendor Ril下,但是实际上是需要将该命令传到framework下去处理的,这个时候需要用到binder通讯方式将命令传到framework下去实际处理。
在Vendor下的binder通讯发送在/vendor/qcom/proprietary/data/ATFWD-daemon/IAtCmdFwd.cpp
下去实现的:
virtual AtCmdResponse *processCommand(const AtCmd &cmd)
{
// ...
data.writeInterfaceToken(IAtCmdFwdService::getInterfaceDescriptor());
data.writeInt32(1); //specify there is an input parameter
data.writeInt32(cmd.opcode); //opcode
String16 cmdname(cmd.name);
s16 = strdup8to16(cmd.name, &len);
data.writeString16(s16, len); //command name
free(s16);
data.writeInt32(cmd.ntokens);
for (int i=0; i < cmd.ntokens; i++) {
s16 = strdup8to16(cmd.tokens[i], &len);
data.writeString16(s16,len);
free(s16);
}
status_t status = remote()->transact(processAtCmd, data, &reply);//RPC call
LOGI("Status: %d",status);
if (status != NO_ERROR) {
LOGE("Error in RPC Call to AtCdmFwd Service (Exception occurred?)");
return NULL;
}
// ...
}
实际就是通过
status_t status = remote()->transact(processAtCmd, data, &reply);//RPC call
实现了发送,最后返回的处理结果在status下。
4、在framework侧的处理实现:
status_t BnAtCmdFwdService::onTransact(uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags) {
case processAtCmd: {
// ...…… //接受到binder通讯传来的数据
}
}
最后就是针对接收到的具体的值进行处理。
高通与At指令:ATFWD解析的更多相关文章
- 【转】高通平台android 环境配置编译及开发经验总结
原文网址:http://blog.csdn.net/dongwuming/article/details/12784535 1.高通平台android开发总结 1.1 搭建高通平台环境开发环境 在高通 ...
- 高通 sensor 从native到HAL
app注册传感器监听 Android Sensor Framework 的整体架构如下图所示: 前几篇sensor相关的文章介绍了sensor的hal的知识,以press_sensor实时显示气压坐标 ...
- AngularJS中的指令全面解析(转载)
说到AngularJS,我们首先想到的大概也就是双向数据绑定和指令系统了,这两者也是AngularJS中最为吸引人的地方.双向数据绑定呢,感觉没什么好说的,那么今天我们就来简单的讨论下AngularJ ...
- 高通、猎户机型Android典型bootloader分析
1.bootloader是什么? 简单地说,bootloader 就是在操作系统内核运行之前运行的一段小程序.通过这段小程序,我们可以初始化硬件设备.建立内存空间的映射图,从而将系统的软硬件环境带到一 ...
- 高通平台msm8909 LK 实现LCD 兼容
前段时间小米出现红米note2 换屏门,现在我们公司也要上演了:有两个供应商提供不同IC 的LCD panel. 软件区分的办法是读取LCD IC 的ID 寄存器,下面解析高通平台LK中LCD兼容的过 ...
- Linux加载DTS设备节点的过程(以高通8974平台为例)
DTS是Device Tree Source的缩写,用来描述设备的硬件细节.在过去的ARM Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码, ...
- 高通安卓调试LCD几方面总结
来公司上班现在已经整整一个月了,蔽人不才,能力有限,学习进度缓慢,不过也是有一点点的收获与心得,在这里写出来与大家分享,养成良好的记录习惯也免得后忘记. 不啰嗦了,开入正题.来公司一个月左右的时间,主 ...
- 高通/苹果/联发科:手机CPU那些事
如今人们买手机,都比较关心采用了什么CPU,因为CPU直接决定了这台手机的性能,CPU之于手机就好比人的大脑,它是整台手机的控制中枢系统,也是逻辑部分的控制中心.又相当于车的发动机,发动机越强劲,车子 ...
- Android图形合成和显示系统---基于高通MSM8k MDP4平台
介绍了Android SurfaceFlinger层次以下的图形合成和显示系统,主要基于高通MSM8k MDP4x平台. 做为Android Display专题.SurfaceFlinger的详细介绍 ...
- 高通HAL层之Sensor HAL
高通的HAL层其实分为两种,一种是直接从kernel这边报数据上来的,由sensor HAL层来监听,另一种是走ADSP的模式,HAL层是通过qmi的形式进行监听的: 走ADSP架构的可以看下面的博客 ...
随机推荐
- docker容器资源配额
1.docker 容器控制CPU docker通过cgroup来控制容器使用的资源限制,可以对docker限制的资源包括cpu.内存.磁盘 1.1 指定docker容器可以使用的cpu份额 # 查看配 ...
- vue关于this.$refs.tabs.refreshs()刷新组件,缓存
当更改了用户信息后,需要刷新页面或者组件. 1.当前组件刷新.定义一个请求用户信息的方法,在需要时调用: sessionStorage.setItem('userInfo',JSON.stringif ...
- 几个函数的使用例子:更新VBRK-XBLNR,IB01设备BOM创建,LI11N输入库存盘点
最近用到一些函数,网上的相关资料不多,这里记录一下. 本文链接:https://www.cnblogs.com/hhelibeb/p/17012303.html 1,使用 RV_INVOICE_HEA ...
- Solution Set - NOI真题
NOI2024 RP++! NOI2018 Day1T1 Link&Submission. 考虑一个最高的水位线使所有点通过没有积水的边就可以连通,也就是求出了一棵海拔的最大生成树.会发现只有 ...
- CF-943(已更B-E)
CF- 943(已更 B-E) D赛时没调出来(╬▔皿▔)╯,还有几分钟的时候反而把E过了,本来应该是上大分一场(⊙﹏⊙),等会会补G1 这假期要刷题,还要补文化课--后面有空的话更一下之前打的线下赛 ...
- C#使用MX Component实现三菱PLC软元件数据采集的完整步骤(仿真)
前言 本文介绍了如何使用三菱提供的MX Component插件实现对三菱PLC软元件数据的读写,记录了使用计算机仿真,模拟PLC,直至完成测试的详细流程,并重点介绍了在这个过程中的易错点,供参考. 用 ...
- Python第三方库的安装和导入
目录 一.Python第三方库的安装 1. 使用pip命令行安装 2. 使用PyCharm进行安装 3. 下载第三方库文件到本地进行安装 4. 通过国内源进行安装 二.Python第三方库的导入 1. ...
- 【工程实践】go语言实现MerkleTree
简介 默克尔树(MerkleTree)是一种典型的二叉树结构,其主要特点为: 最下面的叶节点包含存储数据或其哈希值: 非叶子节点(包括中间节点和根节点)的内容为它的两个孩子节点内容的哈希值. 所以底层 ...
- JDK源码阅读-------自学笔记(十六)(java.util.Random随机数类)
Random类简介 如果使用Math.random()计算过于复杂的话,我们可以使用例外一种方式得到随机数,即Random类,这个类是专门用来生成随机数的,并且Math.random()底层调用的就是 ...
- .net core 微信支付-微信小程序支付(服务端C#代码)
前言 前段时间研究了下微信支付-小程序支付的功能.但微信支付文档中关于.net C#的语言的sdk没有,只有java go 和php版本的,当然社区也有很多已经集成好的微信支付.net core sd ...