Android9.x MTK NVRAM的读写---上层实现SN和MAC读写

前言

MTK平台有自己的序列号和MAC地址烧录工具,但只能用于个人的调试,如果产品要量产,还使用MTK平台自己的烧录工具,则就不合适了,因为满足不了工厂生产的一些客制化功能,比如SN号和MAC地址拼凑在一起同时烧录,MTK烧录工具就做不到。那么就需要设备厂商自己开发烧录工具了。本文就是解决MTK 平台Android9.0上如何通过应用层来进行读写SN号和MAC地址。

概述

在MTK平台中,SN号和MAC地址都是保存在NVRAM中的,所以读写SN号和MAC地址,实际上就是对NVRAM进行读写操作。而在Android9.0系统中,NVRAM的读写方式已经发生改变,跟Android8.0以前的读写方式不同。
Android8.0以前NVRAM的读写方式

Android8.0以前,是通过AIDL的方式去获取一个名为【NvRAMAgent】的服务,然后通过该服务调用相关接口进行读写,获取NVRAM服务的方式如下:

IBinder binder = ServiceManager.getService("NvRAMAgent");
NvRAMAgent agent = NvRAMAgent.Stub.asInterface(binder);

读写方式如下:

fid = getProductInfoLid(); //这里获取SN的lid
byte[] ret = agent.readFile(fid); //通过lid去读取产品SN
agent.writeFile(fid, ret); //向NVRAM中写入SN

Android8.0以前,向NVRAM中读写SN或者MAC地址等信息时,一般是通过LID的方式,LID的值是在native中定义的。可以通过一个native函数获取到SN或者MAC地址对应的LID号,然后再进行读写。
LID号可以简单理解为SN或者MAC地址在NVRAM中的地址。

Android9.0 NVRAM的读写方式

到了Android8.0之后(这里以Android9.0系统为例),NVRAM的读写方式已经变更,不再通过AIDL去获取NVRAM服务,然后进行读写。而是通过HIDL的方式去获取服务来进行读写,方法如下:

import vendor.mediatek.harware.nvram.V1_0.INvram;

INvram agent = INvram.getService();

读写方法:

import com.android.internal.util.HexDump;
//读取:
String buffer = agent.readFileByName(path,size);
//size读取多长的数据,处理数据要注意直接通过字符串getByte获取的长度
//会是size的两倍+1(size是有效大小,根据Nvram存储的数据结构决定)
//+1是因为字符串带有结束符

//写入:
byte[] buff = HexDump.hexStringToByteArray(buffer.subString(0,buffer.length() - 1);
ArrayList<Byte> dataArray = new ArrayList<Byte>(size);

//修改buff数组中某一个字节的数据,然后重新写入到Nvram:
buff[index] = 0xff;
//將修改后的字节数组存到Array中
for(int index = 0; index < size; index ++) {
    dataArray.add(index,new Byte(buff[index]))
}
//最后需要:
agent.writeFileByNamevec(path,size,dataArray);

可以看到,Android8.0前后,NVRAM读写的不同点如下:
1.获取NVRAM服务的方式不一样,一个是AIDL,一个是HIDL;
2.NVRAM服务的接口不一致,一个是通过LID号的方式来进行读写,一个是通过节点的方式来进行读写;

这里需要注意的是,调用NvRam的新读写方法,这里要在Android.mk中新增依赖库,否则会编译不通过:

LOCAL_STATIC_JAVA_LIBRARIES += \
vendor.mediatek.hardware.nvram-V1.0-java-static

上层如何使用NVRAM接口

Android9.0 NVRAM的接口在上一节已经大概介绍,细心的朋友已经注意到,新的NVRAM接口是通过节点方式来读写的,那么这个节点在哪里呢?节点路径如下:

/vendor/nvdata/APCFG/APRDEB/WIFI  //wifi mac 地址
/VENDOR/nvdata/APCFG/APRDEB/BT_Addr //蓝牙 mac地址
/VENDOR/nvdata/APCFG/APRDEB/PRODUCT_INFO //SN号

调用如上接口,传入以上路径,就可以读写SN号和MAC地址。

但是,这里要说一个比较坑的事。那就是,如果通过这种方法去读写SN号,那么系统重启之后,SN号并没有更新成我们所烧录的那个SN号。
这是因为在MTK Android9.0中,有两种方式读写SN号:客户应用自己写或者通过MTK烧录工具烧写。专门提供给应用层读写的,路径就是如上提供的,但这个SN号读写之后并不会被系统所识别,也就是说不会被写入到序列号相关的属性中,如ro.serialno。另外一种通过烧录工具写入的,写入的地方为/dev/pro_info,重启后,这个可以被系统识别。

那如果即想要实现上层读写SN号,又能够被系统识别,那能否做到呢?答案当然是YES。
实现上层读写SN,系统也能识别,要想系统能够识别,则PRODUCT_INFO就不能出现,可以通过如下的宏来进行配置:

MTK_PRODUCT_INFO_SUPPORT = yes
则表示支持MTK的sn号烧录工具进行读写,然后该SN号可以在LK的时候被读出来,最后在init.cpp中赋值给ro.serial属性;
但这种配置无法支持应用对PRODUCT_INFO进行读写。
MTK_PRODUCT_INFO_SUPPORT = no
这种则表示支持应用对PRODUCT_INFO进行读写,但不支持MTK的sn号烧录工具读写。

那如果没有PRODUCT_INFO这个节点,上层该如何读写呢?
Android8.0以前的系统也不是通过节点方式读写SN号的,而是通过LID号的方式,那Android9.0也可以同样通过这个LID号来实现,只是系统没有提供接口给上层调用而已。那我们就增加一个接口,通过LID号的方式来读写SN号。

前面已经说了,Android9.0 的NVRAM服务是通过HIDL方式获取,所以我们需要在HIDL中增加一个接口。

修改HAL文件对外的接口说明
是在hal文件中,该文件的路径一般如下:

vendor/mediatek/proprietary/hardware/interfaces/nvram/1.0/INvram.hal

增加如下接口:

interface INvram {
    readFileByName(string filename, uint32_t size)
            generates (string data);
    writeFileByNamevec(string filename, uint32_t size, vec<uint8_t> data)
            generates (int8_t retval);
    //20200222, Add nvram interface@{
    mnqReadFileByLid(int32_t lid, uint32_t size)
            generates (string data);
    mnqWriteFileByLidvec(int32_t lid, uint32_t size, vec<uint8_t> data)
            generates (int8_t retval);
    //20200222, Add nvram interface@}
};

重新生成哈希值
这里注意,更改了这个文件后,需要更新如下文件:

vendor/mediatek/proprietary/hardware/interfaces/current.txt

Android采用哈希值来管理所有的HIDL hal接口文件,一旦该文件被修改,那么哈希值就会跟current.txt文件中的哈希值对不上,编译就会报错。这时可以通过如下工具来生成新的哈希值:

hidl-gen -L hash -r vendor.mediatek.hardware:vendor/mediatek/proprietary/hardware/interfaces -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport vendor.mediatek.hardware.nvram@1.0

如上指令就是针对vendor.mediatek.hardware.nvram@1.0中hal文件的修改,生成新的哈希值,将该哈希值更新到current.txt文件中,编译就能通过。

实现新增接口
在hal文件中新增了一个接口后,需要有相应的接口实现,需要在头文件中新增接口:
vendor/mediatek/proprietary/external/libnvram/nvram_hidl/1.0/NvRam.h

//20200222, Add nvram interface @{
Return<void> mnqReadFileByLid(int32_t lid, uint32_t size, readFileByName_cb _hidl_cb) override;
Return<int8_t> mnqWriteFileByLidvec(int32_t lid, uint32_t size, const hidl_vec<uint8_t>& data) override;
//20200222, Add nvram interface @}

然后在对应的源文件中具体实现该接口:

vendor/mediatek/proprietary/external/libnvram/nvram_hidl/1.0/NvRam.cpp
//20200222, Add nvram interface @{
Return<void> Nvram::mnqReadFileByLid(int32_t lid, uint32_t size, readFileByName_cb _hidl_cb) {
    // TODO implement
    int pRecSize=0,pRecNum=0;
    bool IsRead=1;
    char *buff=NULL;
    int file_lid = -1;
    string result;
    int i = 0;
    char *nvramstr = (char*)malloc(2*size+1);
    char *nvramptr = nvramstr;

    file_lid = lid;
    F_ID fd=NVM_GetFileDesc(file_lid,&pRecSize,&pRecNum,IsRead);
    if (fd.iFileDesc==-1)
    {
        LOG(ERROR) << "open file Error!";
        free(nvramstr);
        return Void();
    }
    LOG(ERROR) << "RecNum is "<<pRecNum;
    //size=pRecSize*pRecNum;
    buff=(char *)malloc(size);
    if(buff == NULL)
    {
        NVRAM_LOG("Malloc Error!\n");
        if(!NVM_CloseFileDesc(fd))
            NVRAM_LOG("close File error!\n");
        free(nvramstr);
        return Void();
    }
    if((ssize_t)size == read(fd.iFileDesc,buff,(ssize_t)size))
    {
        if(NVM_CloseFileDesc(fd))
        {
            NVRAM_LOG("Read Done!Size is %d\n",size);
            //return buff;
        }
        else
        {
            NVRAM_LOG("Close file error!\n");
            free(buff);
            free(nvramstr);
            return Void();
        }
    }
    else
    {
        NVRAM_LOG("read File error!\n");
        if(!NVM_CloseFileDesc(fd))
            NVRAM_LOG("close File error!\n");
        free(buff);
        free(nvramstr);
        return Void();
    }

    NVRAM_LOG("nvramstr buff[0]%x, buff[1]%x, buff[2]%x, buff[3]%x, buff[4]%x, buff[5]%x, buff[6]%x, buff[7]%x, buff[8]%x \n",
        buff[0],buff[1],buff[2],buff[3],buff[4],buff[5],buff[6],buff[7],buff[8]);


    for(i=0; i<(int)size; i++)
        {
        nvramptr += sprintf(nvramptr, "%02X",buff[i]);
        }
    sprintf(nvramptr,"\n");
    *(nvramptr+1)='\0';


    NVRAM_LOG("nvramstr %s\n",nvramstr);
    _hidl_cb(nvramstr);
    free(buff);
    return Void();
}

Return<int8_t> Nvram::mnqWriteFileByLidvec(int32_t lid, uint32_t size, const hidl_vec<uint8_t>& data) {
    // TODO implement
    char *cstr_data=new char[data.size()+1];
    if (cstr_data==NULL || size==0) {
    NVRAM_LOG("cstr_data==NULL\n");
        if(cstr_data!=NULL)
            delete[] cstr_data;
    return int8_t {};
    }

    covertVector2Array(data, cstr_data);

    int pRecSize=0,pRecNum=0,looptimes=0;
    bool IsRead=0;
    int file_lid = -1;

    file_lid = lid;

    F_ID fd=NVM_GetFileDesc(file_lid,&pRecSize,&pRecNum,IsRead);
    if (fd.iFileDesc==-1)
    {
        NVRAM_LOG("open file Error!\n");
        delete[] cstr_data;
        return int8_t {};
    }
    #if 0
    if(size != pRecSize)
    {
        NVRAM_LOG("Input size (%d) and RecSize (%d) not match!\n",size,pRecSize);
        if(!NVM_CloseFileDesc(fd))
            NVRAM_LOG("close File error!\n");
        //return 0;
        return int8_t {};
    }
    #endif

    // GetFileDesc should return right pos and this would cause pro_info multi lids issue.
    #if 0
    if(0 != lseek(fd.iFileDesc,0,SEEK_SET)){
        NVRAM_LOG("lseek error!\n");
        if(!NVM_CloseFileDesc(fd))
            NVRAM_LOG("close File error!\n");
        return 0;
        }
    #endif
    looptimes = pRecNum;
    NVRAM_LOG("RecNum is :%d\n",pRecNum);
    while(looptimes--)
    {
        if((ssize_t)size != write(fd.iFileDesc,cstr_data,(ssize_t)size))
        {
            NVRAM_LOG("write file error!\n");
            if(!NVM_CloseFileDesc(fd))
                NVRAM_LOG("close File error!\n");
            delete[] cstr_data;
            return int8_t {};
        }
    }
    if(NVM_CloseFileDesc(fd))
    {
        NVRAM_LOG("Write file Done!\n");
        delete[] cstr_data;
        return int8_t {};
    }
    else
    {
        NVRAM_LOG("close File error!\n");
        delete[] cstr_data;
        return int8_t {};
    }
    //return int8_t {};
}
//20200222, Add nvram interface @}

重新编译后,上层就可以通过如下两个接口来进行读写SN号和mac:

Return<void> mnqReadFileByLid(int32_t lid, uint32_t size, readFileByName_cb _hidl_cb) override;
Return<int8_t> mnqWriteFileByLidvec(int32_t lid, uint32_t size, const hidl_vec<uint8_t>& data) override;

这两个接口跟Android8.0以前的系统就很像了,也是通过LID号的方式来进行读写,并且读写后,可以通过系统来进行识别。
应用层调用新增接口读取的demo

这里简单写一个demo,供大家参考:

....
import vendor.mediatek.hardware.nvram.V1_1.INvram;
import com.android.internal.util.HexDump;
....

public class NvramInterface {

    static private INvram mINvRAMAgent = null;
    //如下这个是native方法,需要JNI层中实现
    static private native int getProductInfoLid();
    static private native int getBtInfoLid();
    static private native int getWifiInfoLid();
    
    static public INvram getINvAgent() {
        if (mINvRAMAgent != null) {
            return mINvRAMAgent;
        }
        INvram agent = null;
        try {
             agent = INvram.getService();
             if (agent == null) {
                Log.d(TAG, "agent111 is null");
                return null;
             }
        } catch (Exception e) {
            Log.e(TAG, "e=" + e);
            return null;
        }
        
        mINvRAMAgent = agent;
        return agent;
    }

    static public boolean saveWifiMac(String mac) {
        byte[] macBytes = toBytes(mac);
        //Log.d(TAG, "saveWifiMac---->" + mac);
        if (macBytes == null) {
            return false;
        }
        INvram agent = getINvAgent();
        if (agent == null) {
            return false;
        }
        try {
            int lid = getWifiInfoLid();
            //Log.d(TAG, "wifi lid is " + lid);
            String buff = agent.mnqReadFileByLid(lid, 10);
            //Log.d(TAG, "buff " + buff + " length: " + buff.length());

            if(buff != null) {
                byte[] buffArr = HexDump.hexStringToByteArray(
                                    buff.substring

(0, buff.length() - 1));
                int i = 0;
                for (i = 0; i < 6; i ++) {
                    buffArr[i+4] = macBytes[i];
                }
                ArrayList<Byte> dataArray = new ArrayList<Byte>(10);

                for (i = 0; i < 10; i++) {
                    dataArray.add(i, new Byte(buffArr[i]));
                }
                int result = agent.mnqWriteFileByLidvec(lid, 10, dataArray);
                //Log.d(TAG, "write result is " +result);
                if (result == 0) {
                    return true;
                } else {
                    return false;
                }
            }
        } catch(Exception e) {
                Log.d(TAG, "logstart:\n" + e.getMessage());
        }
        
        return false;
    }

    static public boolean saveSn(String sn) {
        INvram agent = getINvAgent();
        if (agent == null) {
            return false;
        }
        if (sn.length() > 64) {
            return false;
        }
        
       try {
            int lid = getProductInfoLid();
            //Log.d(TAG, "product info id is " + lid);
            String buff = agent.mnqReadFileByLid(lid, 64);

            //Log.d(TAG, "buff " + buff + " length: " + buff.length());

            if(buff != null) {
                byte[] buffArr = HexDump.hexStringToByteArray(
                                    buff.substring(0, buff.length() - 1));

                 byte[] tempBuf = sn.getBytes("UTF-8");
                 //Log.d(TAG, "macBuf length is " +tempBuf.length);
                 for (int i = 0; i < 64; i++) {
                     if (i < tempBuf.length) {
                         buffArr[i] = tempBuf[i];
                     } else {
                         buffArr[i] = 0x00;
                     }
                 }
                
                ArrayList<Byte> dataArray = new ArrayList<Byte>(64);

                for (int i = 0; i < 64; i++) {
                    dataArray.add(i, new Byte(buffArr[i]));
                }
                int result = agent.mnqWriteFileByLidvec(lid, 64, dataArray);
                
                //Log.d(TAG, "write result is " +result);
                if (result == 0) {
                    return true;
                } else {
                    return false;
                }
            }
        } catch(Exception e) {
                Log.d(TAG, "logstart:\n" + e.getMessage());
        }
        
        return false;
    }

    static public String readWifiMac() {
        INvram agent = getINvAgent();
        if (agent == null) {
            return null;
        }
        try {
            int lid = getWifiInfoLid();
            //Log.d(TAG, "wifi lid is " + lid);
            String buff = agent.mnqReadFileByLid(lid, 10);
            //Log.d(TAG, "wifi buff " + buff + " length: " + buff.length());
            if (buff != null) {
                return buff.substring(8);
            }
        } catch(Exception e) {
                Log.d(TAG, "logstart:\n" + e.getMessage());
        }
        return null;
    }
    
    static public String readSn() {
        INvram agent = getINvAgent();
        if (agent == null) {
            return null;
        }

        try {
            int lid = getProductInfoLid();
            //Log.d(TAG, "product info id is " + lid);
            String buff = agent.mnqReadFileByLid(lid, 64);
            //Log.d(TAG, "product info is " + buff);
            String buffTmp = buff.replaceAll("0+$", "");
            if(buffTmp != null) {
                byte[] buffArr = HexDump.hexStringToByteArray(
                                    buffTmp.substring(0, buffTmp.length() - 1));

                String sn = new String(buffArr);
                Log.d(TAG, "sn is " + sn);
                return sn;
            }
        } catch(Exception e) {
                Log.d(TAG, "logstart:\n" + e.getMessage());
        }
        return null;
    }
}

结语

本文大概列出了Android8.0前后对NVRAM的读写的不同之处,接着解决SN号在上层写入之后不能被系统识别的问题,最后提供了一个上层读写NVRAM的例子。

在Android9.0上实现SN号和MAC的烧录还是费了不少周章,也踩了不少坑。在这里做一个记录,也希望其他想要实现该功能的同学少走一些弯路,少踩坑。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Android框架中,NVRAM(非易失性随机存储器)被用于存储系统相关的配置和参数,如无线通信模块的MAC地址、IMEI号码等。NVRAM的读写Android设备的系统级别中发挥重要作用。 首先,NVRAM读取功能在Android框架中由系统服务提供,通过封装底层的硬件接口和驱动程序来实现Android的系统服务负责与硬件之间的通信,从NVRAM中读取所需的参数。在读取过程中,通过定义和使用适当的API,应用程序或系统组件可以请求读取特定的NVRAM值,以进行相关的操作和功能。 其次,NVRAM的写入功能也是由系统服务提供的。当应用程序或系统组件需要修改某个NVRAM参数时,可以通过调用相应的API来实现。系统服务将接收到的修改请求转发给硬件接口和驱动程序,然后将新值写入NVRAM中。这样,NVRAM中对应的参数将被更新,以满足新的需求。 在Android框架中,NVRAM读写功能的正确实现对于系统的正常运行非常重要。它确保各种系统组件可以正确访问配置和参数信息,并确保设备在启动时能够正确初始化。同时,NVRAM的读写操作需要受到适当的权限控制,只有具备足够权限的应用程序或系统组件才能进行相关的操作。 综上所述,Android框架中的NVRAM读写功能通过系统服务提供,并且在系统级别中发挥重要作用。这种功能确保了系统配置和参数的正确访问和修改,从而保障设备的正常运行。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值