目次: Linux
Raspberry Pi 3を持っているのですが、あまり速くない(当たり前ですけど)こともあり、ほとんどコンパイルには使っていませんでした。今日、久しぶりにコードのビルドに使ってみたら、変な症状にハマりました。
$ autoconf --version autoconf (GNU Autoconf) 2.69 ... $ automake --version automake (GNU automake) 1.15 ... $ autoreconf -fi aclocal: warning: couldn't open directory 'm4': No such file or directory configure.ac:23: installing 'conf/compile' configure.ac:16: installing 'conf/install-sh' configure.ac:16: installing 'conf/missing' src/Makefile.am: installing 'conf/depcomp'
更地からのビルドなのでautoreconf -fiを実行しています。この時点では特にエラーも出ずに終わったように見えます。
$ ./configure checking for a BSD-compatible install... /usr/bin/install -c checking whether build environment is sane... yes checking for a thread-safe mkdir -p... /bin/mkdir -p checking for gawk... gawk checking whether make sets $(MAKE)... yes checking whether make supports nested variables... yes checking whether make supports nested variables... (cached) yes ./configure: line 2505: syntax error near unexpected token `ac_ext=c' ./configure: line 2505: `ac_ext=c'
しかしconfigureが謎のエラーで終了してしまいます。しかもconfig.logにエラーの内容が記録されておらず、怪しいです。
しばしconfigure.acをいじってみてわかったことは、以下の条件を満たしていると、このエラーが発生するようです。
解決策はlibtoolをインストールするか、libtoolを使っていないならconfigure.acからLT_INIT() を削除しても良いです。
この辺の仕組みは詳しくありませんが、libtoolが無いなら無いと言ってくれれば、もう少しわかりやすいのにな…と思います。
 この記事にコメントする
 この記事にコメントする
先日、作った(2018年6月1日の日記参照)ISDBというかARIBのデスクランブラの続きです。
DVB APIで制御可能なチューナー(私はPT2で確認しています)を使っている方であれば、下記のようにチューニング(コードは GitHubにあります)できます。チューニングに成功して放送が受信されると、/dev/dvb/adapter0/dvr0からスクランブルの掛かったMPEG2-TSが出力されます。
例: BSプレミアム(衛星はアダプタ0か2、地デジはアダプタ1か3を使います) $ ./sample_dvb 0 S BS 3 0x4031 ... ごちゃごちゃ出るのが邪魔くさければ、 $ ./sample_dvb 0 S BS 3 0x4031 > /dev/null
スマートカードリーダーをPCに接続し、B-CASカードをリーダーに挿入した上で、下記のようにデスクランブル(コードは GitHubにあります)できます。デスクランブルしたMPEG2-TSはUDPで送るか、ファイルに保存できます。
例: 自分自身にUDPで送る $ ./arib_descramble /dev/dvb/adapter0/dvr0 localhost 1234 ...
VLCを起動し、udp://@:1234を再生すると、受信中の放送が映るはずです。
自身の規格理解のためもあって、かなり手抜き実装していて、異常に重いため、いくつか改良してみました。まずプロファイラで見てみると、MULTI2復号と、どこかにある無駄なコピーに、時間がかかっているようです。
MULTI2復号の高速化にはSSE2を使ってみました。MULTI2の復号は8バイトずつですが、SSE2を活用するには32バイトの方が都合が良いです。ですので4単位まとめて(4 x 8バイト = 32バイト = 128bit = SSE2のレジスタ幅)処理して、残った32バイトに満たないデータは従来どおり8バイトずつ処理します。
残念ながら、結果から言うとあまり最適化が効きませんでした。SIMDで高速化できないロード/ストアの割合が高いのか、計算が占める割合が低いのか、いまいちわからなかったのですが、あまり高速化できませんでした。CPU利用率でいうと12% が11% になるか、ならないか…程度です。
無駄なコピーは2箇所見つけたのでガッツリ消しました。これは効果があったようで、CPU利用率でいうと11% が10% くらいまで削減できました。
無駄なコピーはもう1つありましたが、単純に消すわけにいかなくてやや難しそうだったので、また今度にします。
PCだと、CPU利用率10% 程度だったので、最近のマルチコアCPUなら割と余裕の負荷です。ではショボいCPUで実行するとどうなるか、試してみました。
手持ちのRaspberry Pi 3(ARM Cortex A53 x 4/1.4GHz)で実行してみたところ、CPU利用率25〜27% 程度でした。動かないかもしれないと思っていたので、正直意外でした。かなり健闘していると思います。
ARMにはNEONというSIMD命令がありますが、NEONを使った復号の高速化にはまだ手を出していません。今度やってみますが、SSE2の結果を見た限りでは、絶大な効果は見込めないでしょう。きっと。
 この記事にコメントする
 この記事にコメントする
Android 8がMPEG2-TSのPSI(Program Specific Information)をどのように処理しているのか、気になったので調べてみました。調査に使ったコードはAOSPのタグandroid-8.1.0_r33です。
PSIのことをセクションと呼ぶ人もいますね。MPEG2 Systemの規格書ISO13818-1/ITU-T H.222.0によれば、PSIはxxx Tableという名前(PATならProgram Association Table)で、テーブルは1つないし、複数のセクション(xxx_sectionという名前で定義されている、PATならprogram_association_section)から構成されるからだと思います。
さておきTSを処理しているところは、下記のようになっています。
//frameworks/av/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
status_t MPEG2TSExtractor::feedMore(bool isInit) {
    Mutex::Autolock autoLock(mLock);
    uint8_t packet[kTSPacketSize];
    ssize_t n = mDataSource->readAt(mOffset, packet, kTSPacketSize);
    if (n < (ssize_t)kTSPacketSize) {
        if (n >= 0) {
            mParser->signalEOS(ERROR_END_OF_STREAM);
        }
        return (n < 0) ? (status_t)n : ERROR_END_OF_STREAM;
    }
    ATSParser::SyncEvent event(mOffset);
    mOffset += n;
    status_t err = mParser->feedTSPacket(packet, kTSPacketSize, &event); //★★★★
    if (event.hasReturnedData()) {
        if (isInit) {
            mLastSyncEvent = event;
        } else {
            addSyncPoint_l(event);
        }
    }
    return err;
}
ここで出てくるmParserはATSParserのポインタなので、ATSParserの実装を見てみます。
//frameworks/av/media/libstagefright/mpeg2ts/ATSParser.cpp
status_t ATSParser::feedTSPacket(const void *data, size_t size,
        SyncEvent *event) {
    if (size != kTSPacketSize) {
        ALOGE("Wrong TS packet size");
        return BAD_VALUE;
    }
    ABitReader br((const uint8_t *)data, kTSPacketSize);
    return parseTS(&br, event); //★★★★
}
status_t ATSParser::parseTS(ABitReader *br, SyncEvent *event) {
    ALOGV("---");
//...
    status_t err = OK;
    unsigned random_access_indicator = 0;
    if (adaptation_field_control == 2 || adaptation_field_control == 3) {
        err = parseAdaptationField(br, PID, &random_access_indicator);
    }
    if (err == OK) {
        if (adaptation_field_control == 1 || adaptation_field_control == 3) {
            err = parsePID(br, PID, continuity_counter,
                    payload_unit_start_indicator,
                    transport_scrambling_control,
                    random_access_indicator,
                    event); //★★★★
        }
    }
//...
status_t ATSParser::parsePID(
        ABitReader *br, unsigned PID,
        unsigned continuity_counter,
        unsigned payload_unit_start_indicator,
        unsigned transport_scrambling_control,
        unsigned random_access_indicator,
        SyncEvent *event) {
    ssize_t sectionIndex = mPSISections.indexOfKey(PID);
//...
    if (sectionIndex >= 0) { //★★★★PATかPMTのPIDならこの条件が成り立つ
        sp<PSISection> section = mPSISections.valueAt(sectionIndex);
ここで出てくるmPSISectionはunsignedをキー、sp<PSISection> を値とするKeyedVectorです。キー0にPATを持っていて、それ以外のキーはPMTのPID(PATが一覧を持っている)です。PMTのPIDはPATを受信したときにATSParser::parseProgramAssociationTable() が追加するようです。
//frameworks/av/media/libstagefright/mpeg2ts/ATSParser.cpp
status_t ATSParser::parsePID(
        ABitReader *br, unsigned PID,
        unsigned continuity_counter,
        unsigned payload_unit_start_indicator,
        unsigned transport_scrambling_control,
        unsigned random_access_indicator,
        SyncEvent *event) {
    ssize_t sectionIndex = mPSISections.indexOfKey(PID);
//...
    if (sectionIndex >= 0) { //★★★★PATかPMTのPIDならこの条件が成り立つ
        sp<PSISection> section = mPSISections.valueAt(sectionIndex);
//...
        if (PID == 0) {
            parseProgramAssociationTable(§ionBits); //★★★★PID 0ならPATの解析
        } else {
            bool handled = false;
            for (size_t i = 0; i < mPrograms.size(); ++i) { //★★★★ それ以外はPMTかどうか見る
                status_t err;
                if (!mPrograms.editItemAt(i)->parsePSISection( //★★★★PMTか?
                            PID, §ionBits, &err)) {
                    continue;
                }
//...
bool ATSParser::Program::parsePSISection(
        unsigned pid, ABitReader *br, status_t *err) {
    *err = OK;
    if (pid != mProgramMapPID) {
        return false;
    }
    *err = parseProgramMap(br); //★★★★PMTだったのでPMTの解析
    return true;
}
status_t ATSParser::Program::parseProgramMap(ABitReader *br) {
    unsigned table_id = br->getBits(8);
    ALOGV("  table_id = %u", table_id);
//...
    // descriptors
    CADescriptor programCA;
    bool hasProgramCA = findCADescriptor(br, program_info_length, &programCA); //★★★★PMTの持っているdescriptorを見ている
    if (hasProgramCA && !mParser->mCasManager->addProgram(
            mProgramNumber, programCA)) { //★★★★CA descriptorの指すPIDつまりECMのPIDを追加
        return ERROR_MALFORMED;
    }
//...
    size_t infoBytesRemaining = section_length - 9 - program_info_length - 4;
    while (infoBytesRemaining >= 5) { //★★★★ エレメンタリストリームのPIDと一緒に付いているdescriptorを見ている
//...
        CADescriptor streamCA;
        bool hasStreamCA = findCADescriptor(br, ES_info_length, &streamCA);
        if (hasStreamCA && !mParser->mCasManager->addStream(
                mProgramNumber, elementaryPID, streamCA)) { //★★★★CA descriptorの指すPIDつまりECMのPIDを追加
            return ERROR_MALFORMED;
        }
//...
    }
//...
    for (size_t i = 0; i < infos.size(); ++i) {
        StreamInfo &info = infos.editItemAt(i);
        if (mParser->mCasManager->isCAPid(info.mPID)) { //★★★★CA descriptorに記載のあったストリーム
            // skip CA streams (EMM/ECM)
            continue;
        }
        ssize_t index = mStreams.indexOfKey(info.mPID);
        if (index < 0) {
            sp<Stream> stream = new Stream(
                    this, info.mPID, info.mType, PCR_PID, info.mCASystemId);
            if (mSampleAesKeyItem != NULL) {
                stream->signalNewSampleAesKey(mSampleAesKeyItem);
            }
            isAddingScrambledStream |= info.mCASystemId >= 0; //★★★★CA descriptorに記載が無いのにスクランブルされている??
            mStreams.add(info.mPID, stream);
        }
    }
ざっくり言うと、スクランブルの掛かったストリームはmParser->mCasManagerに任せ、スクランブルの掛かっていないストリームはmStreamsに任せるようです。
CA descriptorに載っていないのにスクランブルの掛かった変なストリームがあると警告が出るようになっています。
 この記事にコメントする
 この記事にコメントする
昨日の続き。スクランブルの掛かったストリームはmParser->mCasManagerに任せていました。mCasManagerはATSParser::CasManagerでしたので、実装を見てみます。
//frameworks/av/media/libstagefright/mpeg2ts/CasManager.cpp
bool ATSParser::CasManager::parsePID(ABitReader *br, unsigned pid) {
    ssize_t index = mCAPidToSessionIdMap.indexOfKey(pid);
    if (index < 0) {
        return false;
    }
    hidl_vec<uint8_t> ecm;
    ecm.setToExternal((uint8_t*)br->data(), br->numBitsLeft() / 8);
    auto returnStatus = mICas->processEcm(mCAPidToSessionIdMap[index], ecm); //★★★★processEcm()
    if (!returnStatus.isOk() || (Status) returnStatus != Status::OK) {
        ALOGE("Failed to process ECM: trans=%s, status=%d",
                returnStatus.description().c_str(), (Status) returnStatus);
    }
    return true; // handled
}
謎のmICasがどこから来るかは、後で調べるとして、関数名processEcm() で探してみると、HALの方にコードがあります。
//hardware/interfaces/cas/1.0/default/CasImpl.cpp
Return<Status> CasImpl::processEcm(
        const HidlCasSessionId &sessionId, const HidlCasData& ecm) {
    ALOGV("%s: sessionId=%s", __FUNCTION__,
            sessionIdToString(sessionId).string());
    std::shared_ptr<CasPlugin> holder = std::atomic_load(&mPluginHolder); //★★★★CasPlugin
    if (holder.get() == nullptr) {
        return toStatus(INVALID_OPERATION);
    }
    return toStatus(holder->processEcm(sessionId, ecm));
}
想像するにCasPluginというクラスを派生させて処理を実装するのでしょう。探してみるとframeworks/av/drm/mediacas/plugins以下にclearkeyとmockという実装があります。
//frameworks/av/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.h
class ClearKeyCasPlugin : public CasPlugin {
...
//frameworks/av/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp
status_t ClearKeyCasPlugin::processEcm(
        const CasSessionId &sessionId, const CasEcm& ecm) {
    ALOGV("processEcm: sessionId=%s", sessionIdToString(sessionId).string());
    sp<ClearKeyCasSession> session =
            ClearKeySessionLibrary::get()->findSession(sessionId);
    if (session == NULL) {
        return ERROR_CAS_SESSION_NOT_OPENED;
    }
    Mutex::Autolock lock(mKeyFetcherLock);
    return session->updateECM(mKeyFetcher.get(), (void*)ecm.data(), ecm.size()); //★★★★mKeyFetcher
}
status_t ClearKeyCasSession::updateECM(
        KeyFetcher *keyFetcher, void *ecm, size_t size) {
//...
    uint64_t asset_id;
    std::vector<KeyFetcher::KeyInfo> keys;
    status_t err = keyFetcher->ObtainKey(mEcmBuffer, &asset_id, &keys); //★★★★keyFetcher
    if (err != OK) {
        ALOGE("updateECM: failed to obtain key (err=%d)", err);
        return err;
    }
    ALOGV("updateECM: %zu key(s) found", keys.size());
    for (size_t keyIndex = 0; keyIndex < keys.size(); keyIndex++) {
        String8 str;
        const sp<ABuffer>& keyBytes = keys[keyIndex].key_bytes;
        CHECK(keyBytes->size() == kUserKeyLength);
        int result = AES_set_decrypt_key(
                reinterpret_cast<const uint8_t*>(keyBytes->data()),
                AES_BLOCK_SIZE * 8, &mKeyInfo[keyIndex].contentKey); //★★★★libsslの関数に渡して鍵を生成している?ようだ
//...
//frameworks/av/drm/mediacas/plugins/clearkey/ClearKeyFetcher.cpp
status_t ClearKeyFetcher::ObtainKey(const sp<ABuffer>& buffer,
        uint64_t* asset_id, std::vector<KeyInfo>* keys) {
//...
引数に渡しているmKeyFetcher(とget() が返すkeyFetcherも同様に)はKeyFetcher型のポインタでした。KeyFetcherを継承したClearKeyFetcher型のオブジェクトが格納されていました。
ClearKeyの仕組みは詳しく知りませんが、ClearKeyCasSession::updateECM() でAESの復号などをしていることと、AESの復号鍵はClearKeyFetcher::ObtainKey() がECMを読んで復号鍵を取得してくれるように見えました。
AndroidでECMの解読を行っている箇所が見つけられました。エレメンタリストリームのデスクランブルはどこで行っているのでしょうね…??
 この記事にコメントする
 この記事にコメントする
デスクランブル処理はどこにあるんでしょう??それらしい処理を辿ってみましたが、本当にこの処理が動くのか、何を切っ掛けにデスクランブル処理が働き始めるのか、動かしてみないとわからなさそうです。うーん……。
//frameworks/base/media/java/android/media/MediaDescrambler.java
    public final int descramble(
            @NonNull ByteBuffer srcBuf, @NonNull ByteBuffer dstBuf,
            @NonNull MediaCodec.CryptoInfo cryptoInfo) {
//...
        try {
            return native_descramble(
                    cryptoInfo.key[0],
                    cryptoInfo.numSubSamples,
                    cryptoInfo.numBytesOfClearData,
                    cryptoInfo.numBytesOfEncryptedData,
                    srcBuf, srcBuf.position(), srcBuf.limit(),
                    dstBuf, dstBuf.position(), dstBuf.limit());
        } catch (ServiceSpecificException e) {
            MediaCasStateException.throwExceptionIfNeeded(e.errorCode, e.getMessage());
        } catch (RemoteException e) {
            cleanupAndRethrowIllegalState();
        }
        return -1;
    }
ここで呼び出しているnative_descramble() はnativeであると宣言されています。つまりJNI経由で呼び出します。メディアフレームワーク(android.media)のJNI実装はframeworks/base/media/jniに配置されているようです。
//frameworks/base/media/jni/android_media_MediaDescrambler.cpp
static jint android_media_MediaDescrambler_native_descramble(
        JNIEnv *env, jobject thiz, jbyte key, jint numSubSamples,
        jintArray numBytesOfClearDataObj, jintArray numBytesOfEncryptedDataObj,
        jobject srcBuf, jint srcOffset, jint srcLimit,
        jobject dstBuf, jint dstOffset, jint dstLimit) {
    sp<JDescrambler> descrambler = getDescrambler(env, thiz); //★★★★descramblerはJDescrambler型
    if (descrambler == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
                "Invalid descrambler object!");
        return -1;
    }
    hidl_vec<SubSample> subSamples;
    ssize_t totalLength = getSubSampleInfo(
            env, numSubSamples, numBytesOfClearDataObj,
            numBytesOfEncryptedDataObj, &subSamples);
    if (totalLength < 0) {
        jniThrowException(env, "java/lang/IllegalArgumentException",
                "Invalid subsample info!");
        return -1;
    }
//...
    err = descrambler->descramble(
            key, totalLength, subSamples,
            srcPtr, srcOffset, dstPtr, dstOffset,
            &status, &bytesWritten, &detailedError); //★★★★
//...
status_t JDescrambler::descramble(
        jbyte key,
        ssize_t totalLength,
        const hidl_vec<SubSample>& subSamples,
        const void *srcPtr,
        jint srcOffset,
        void *dstPtr,
        jint dstOffset,
        Status *status,
        uint32_t *bytesWritten,
        hidl_string *detailedError) {
//...
    auto err = mDescrambler->descramble(
            (ScramblingControl) key,
            subSamples,
            mDescramblerSrcBuffer,
            0,
            dstBuffer,
            0,
            [&status, &bytesWritten, &detailedError] (
                    Status _status, uint32_t _bytesWritten,
                    const hidl_string& _detailedError) {
                *status = _status;
                *bytesWritten = _bytesWritten;
                *detailedError = _detailedError;
            }); //★★★★
//...
ここで出てくるmDescramblerはIDescrambler型のポインタです。パッと見では、何がセットされているのか良くわかりませんが、このインタフェースを実装しているのは下記しかなさそうです。
//hardware/interfaces/cas/1.0/default/DescramblerImpl.h
class DescramblerImpl : public IDescrambler {
//...
//hardware/interfaces/cas/1.0/default/DescramblerImpl.cpp
Return<void> DescramblerImpl::descramble(
        ScramblingControl scramblingControl,
        const hidl_vec<SubSample>& subSamples,
        const SharedBuffer& srcBuffer,
        uint64_t srcOffset,
        const DestinationBuffer& dstBuffer,
        uint64_t dstOffset,
        descramble_cb _hidl_cb) {
    ALOGV("%s", __FUNCTION__);
    // Get a local copy of the shared_ptr for the plugin. Note that before
    // calling the HIDL callback, this shared_ptr must be manually reset,
    // since the client side could proceed as soon as the callback is called
    // without waiting for this method to go out of scope.
    std::shared_ptr<DescramblerPlugin> holder = std::atomic_load(&mPluginHolder); //★★★★holder = mPluginHolder
    if (holder.get() == nullptr) {
        _hidl_cb(toStatus(INVALID_OPERATION), 0, NULL);
        return Void();
    }
//...
    // Casting hidl SubSample to DescramblerPlugin::SubSample, but need
    // to ensure structs are actually idential
    int32_t result = holder->descramble(
            dstBuffer.type != BufferType::SHARED_MEMORY,
            (DescramblerPlugin::ScramblingControl)scramblingControl,
            subSamples.size(),
            (DescramblerPlugin::SubSample*)subSamples.data(),
            srcPtr,
            srcOffset,
            dstPtr,
            dstOffset,
            NULL);
//...
このmPluginHolderはDescramblerPlugin型です。DescramblerImplが生成される時に設定されます。DescramblerImplを生成するのはMediaCasService::createDescrambler() のみ?のように見えます。
次に出てくるholderにはDescramblerPluginのポインタが入ります。Pluginの実装を探してみるとClearKeyにそれらしき処理があります。
//frameworks/av/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp
ssize_t ClearKeyDescramblerPlugin::descramble(
        bool secure,
        ScramblingControl scramblingControl,
        size_t numSubSamples,
        const SubSample *subSamples,
        const void *srcPtr,
        int32_t srcOffset,
        void *dstPtr,
        int32_t dstOffset,
        AString *errorDetailMsg) {
    ALOGV("descramble: secure=%d, sctrl=%d, subSamples=%s, "
            "srcPtr=%p, dstPtr=%p, srcOffset=%d, dstOffset=%d",
          (int)secure, (int)scramblingControl,
          subSamplesToString(subSamples, numSubSamples).string(),
          srcPtr, dstPtr, srcOffset, dstOffset);
    if (mCASSession == NULL) {
        ALOGE("Uninitialized CAS session!");
        return ERROR_CAS_DECRYPT_UNIT_NOT_INITIALIZED;
    }
    return mCASSession->decrypt(
            secure, scramblingControl,
            numSubSamples, subSamples,
            (uint8_t*)srcPtr + srcOffset,
            dstPtr == NULL ? NULL : ((uint8_t*)dstPtr + dstOffset),
            errorDetailMsg);
}
ここで出てくるmCASSessionはClearKeyCasSessionを指すようです。setMediaCasSession() にて設定されています。これは後で追ってみます。
//av/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp
// Decryption of a set of sub-samples
ssize_t ClearKeyCasSession::decrypt(
        bool secure, DescramblerPlugin::ScramblingControl scramblingControl,
        size_t numSubSamples, const DescramblerPlugin::SubSample *subSamples,
        const void *srcPtr, void *dstPtr, AString * /* errorDetailMsg */) {
        return ERROR_CAS_CANNOT_HANDLE;
    
    AES_KEY contentKey;
        // Hold lock to get the key only to avoid contention for decryption
        Mutex::Autolock _lock(mKeyLock);
        int32_t keyIndex = (scramblingControl & 1);
            ALOGE("decrypt: key %d is invalid", keyIndex);
            return ERROR_CAS_DECRYPT;
        
        contentKey = mKeyInfo[keyIndex].contentKey; //★★★★ClearKeyCasSession::updateECM() で設定する鍵だと思う
    
//...
        // Don't decrypt if len < AES_BLOCK_SIZE.
        // The last chunk shorter than AES_BLOCK_SIZE is not encrypted.
        if (scramblingControl != DescramblerPlugin::kScrambling_Unscrambled
                && subSamples[i].mNumBytesOfEncryptedData >= AES_BLOCK_SIZE) {
            err = decryptPayload(
                    contentKey,
                    numBytesinSubSample,
                    subSamples[i].mNumBytesOfClearData,
                    (char *)dst);
        }
//...
// Decryption of a TS payload
status_t ClearKeyCasSession::decryptPayload(
        const AES_KEY& key, size_t length, size_t offset, char* buffer) const {
    CHECK(buffer);
    // Invariant: only call decryptPayload with TS packets with at least 16
    // bytes of payload (AES_BLOCK_SIZE).
    CHECK(length >= offset + AES_BLOCK_SIZE);
    return TpBlockCtsDecrypt(key, length - offset, buffer + offset);
}
// AES-128 CBC-CTS decrypt optimized for Transport Packets. |key| is the AES
// key (odd key or even key), |length| is the data size, and |buffer| is the
// ciphertext to be decrypted in place.
status_t TpBlockCtsDecrypt(const AES_KEY& key, size_t length, char* buffer) {
    CHECK(buffer);
//...
CBCモードでAES暗号を復号しているようです。
まとめると、MediaDescrambler(ドキュメントはここ)のdescrambler() によって、スクランブルされたデータのデスクランブルができそう、ということがわかりました。
ドキュメントにそう書いてあるし、当たり前なんですけどね。新たにデスクランブラを足そうと思う人は、中身も知らないと足せないです。
 この記事にコメントする
 この記事にコメントする
もはや自分以外の誰得の内容なのか、わかりませんが、気にせず書きます。
その3にて「DescramblerImplを生成するのはMediaCasService::createDescrambler() のみ?のように見えます。」と書きました。この関数に至る経路がわかると、デスクランブラがどの暗号系を選択するのか?いつ選択するのか?などがわかるようになります。
で、今回はcreateDescrambler() が呼ばれる経路を見つけたのでメモします。しかしながらExtractorのsetMediaCas() を呼び出すのは誰なのか?まだ謎のままなので、いまいちスッキリしませんけども。
//frameworks/av/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
status_t MPEG2TSExtractor::setMediaCas(const HInterfaceToken &casToken) {
    HalToken halToken;
    halToken.setToExternal((uint8_t*)casToken.data(), casToken.size());
    sp<ICas> cas = ICas::castFrom(retrieveHalInterface(halToken));
    ALOGD("setMediaCas: %p", cas.get());
    status_t err = mParser->setMediaCas(cas); //★★★★mParser
    if (err == OK) {
        ALOGI("All tracks now have descramblers");
        init();
    }
    return err;
}
ここで出てくるmParserの型はATSParserでしたので、ATSParserの実装を見てみます。
//frameworks/av/media/libstagefright/mpeg2ts/ATSParser.cpp
status_t ATSParser::setMediaCas(const sp<ICas> &cas) {
    status_t err = mCasManager->setMediaCas(cas); //★★★★mCasManager
    if (err != OK) {
        return err;
    }
    for (size_t i = 0; i < mPrograms.size(); ++i) {
        mPrograms.editItemAt(i)->updateCasSessions();
    }
    return OK;
}
ここで出てくるmCasManagerの型はATSParser::CasManagerでした。CasManagerを見てみましょう。
//frameworks/av/media/libstagefright/mpeg2ts/CasManager.cpp
status_t ATSParser::CasManager::setMediaCas(const sp<ICas> &cas) {
    if (cas == NULL) {
        ALOGE("setMediaCas: received NULL object");
        return BAD_VALUE;
    }
    if (mICas != NULL) {
        ALOGW("setMediaCas: already set");
        return ALREADY_EXISTS;
    }
    for (size_t index = 0; index < mProgramCasMap.size(); index++) {
        status_t err;
        if ((err = mProgramCasMap.editValueAt(
                index)->setMediaCas(cas, mCAPidToSessionIdMap)) != OK) { //★★★★mProgramCasMap
            return err;
        }
    }
    mICas = cas; //★★★★mICas
    return OK;
}
Extractorから渡されてきたcasは、CasManagerのメンバ変数mICasに保存されるようです。ちなみにこのmICasはその2のCasManager::parsePID() にて、mICas->processEcm(mCAPidToSessionIdMap[index], ecm); の呼び出し時に出てきました。ここで保存されていたんですね。
話を戻してmProgramCasMapはunsignedがキー、ProgramCasManagerのポインタが値のKeyedVectorです。
status_t ATSParser::CasManager::ProgramCasManager::setMediaCas(
        const sp<ICas> &cas, PidToSessionMap &sessionMap) {
    if (mHasProgramCas) {
        return initSession(cas, sessionMap, &mProgramCas); //★★★★
    }
    // TODO: share session among streams that has identical CA_descriptors.
    // For now, we open one session for each stream that has CA_descriptor.
    for (size_t index = 0; index < mStreamPidToCasMap.size(); index++) {
        status_t err = initSession(
                cas, sessionMap, &mStreamPidToCasMap.editValueAt(index)); //★★★★
        if (err != OK) {
            return err;
        }
    }
    return OK;
}
status_t ATSParser::CasManager::ProgramCasManager::initSession(
         const sp<ICas>& cas,
         PidToSessionMap &sessionMap,
         CasSession *session) {
    sp<IMediaCasService> casService = IMediaCasService::getService("default"); //★★★★IMediaCasService型
    if (casService == NULL) {
        ALOGE("Cannot obtain IMediaCasService");
        return NO_INIT;
    }
//...
    returnDescrambler = casService->createDescrambler(descriptor.mSystemID); //★★★★createDescrambler()
    if (!returnDescrambler.isOk()) {
        ALOGE("Failed to create descrambler: trans=%s",
                returnDescrambler.description().c_str());
        goto l_fail;
    }
    descramblerBase = (sp<IDescramblerBase>) returnDescrambler;
    if (descramblerBase == NULL) {
        ALOGE("Failed to create descrambler: null ptr");
        goto l_fail;
    }
やっとcreateDescrambler() が出てきました。casServiceはIMediaCasService型ですが、このインタフェースを実装しているクラスは1つしかなさそうです。
//hardware/interfaces/cas/1.0/default/MediaCasService.h
class MediaCasService : public IMediaCasService {
//...
//hardware/interfaces/cas/1.0/default/MediaCasService.cpp
Return<sp<IDescramblerBase>> MediaCasService::createDescrambler(int32_t CA_system_id) {
    ALOGV("%s: CA_system_id=%d", __FUNCTION__, CA_system_id);
    sp<IDescrambler> result;
    DescramblerFactory *factory;
    sp<SharedLibrary> library;
    if (mDescramblerLoader.findFactoryForScheme(
            CA_system_id, &library, &factory)) { //★★★★DescramblerPluginを探す処理はこの辺にありそう
        DescramblerPlugin *plugin = NULL;
        if (factory->createPlugin(CA_system_id, &plugin) == OK
                && plugin != NULL) {
            result = new DescramblerImpl(library, plugin); //★★★★DescramblerImplを生成している箇所があった
        }
    }
    return result;
}
ここでゴールのようです。まとめるとExtractorのsetMediaCas() を呼ぶと、
 この記事にコメントする
 この記事にコメントする
目次: GCC
目次: Linux
AArch64(64ビットARM)向けのLinux開発環境を構築しました。超有名ツールの組み合わせだし、簡単だろうと思っていたのですが、意外とハマって1日掛かってしまったのでメモしておきます。使用するツールは下記の通りです。
クロスコンパイラを生成するためcrosstool-NG(GitHubへのリンク)を使います。crosstool-NGはARM向け以外にもクロスコンパイラやツールチェインの構築が簡単にできて便利です。
私の環境(Debian Testing)だとlibtool-binパッケージがインストールされていなくてハマったので、インストールしておくと良いかもしれません。
$ git clone https://github.com/crosstool-ng/crosstool-ng $ cd crosstool-ng $ ./bootstrap INFO :: *** Generating package version descriptions INFO :: Master packages: android-ndk autoconf automake avr-libc binutils cloog duma elf2flt expat gcc gdb gettext glibc-ports glibc gmp isl libelf libiconv libtool linux ltrace m4 make mingw-w64 mpc mpfr musl ncurses newlib strace uClibc zlib INFO :: Generating 'config/versions/android-ndk.in' INFO :: Generating 'config/versions/autoconf.in' ...(snip)... INFO :: Generating comp_libs.in (menu) INFO :: *** Gathering the list of data files to install INFO :: *** Running autoreconf INFO :: *** Done! $ ./configure --enable-local checking for a BSD-compatible install... /usr/bin/install -c checking whether build environment is sane... yes checking for a thread-safe mkdir -p... /bin/mkdir -p ...(snip)... config.status: creating config.h config.status: config.h is unchanged config.status: executing depfiles commands $ make /usr/bin/make all-recursive make[1]: Entering directory '/home/katsuhiro/share/projects/oss/crosstool-ng' Making all in kconfig make[2]: Entering directory '/home/katsuhiro/share/projects/oss/crosstool-ng/kconfig' bison -y -l -b zconf -p zconf -ozconf.c zconf.y ...(snip)... /bin/mkdir -p docs && ( /bin/sed -e 's,[@]docdir[@],/usr/local/share/doc/crosstool-ng,g' -e 's,[@]pkgdatadir[@],/usr/local/share/crosstool-ng,g' -e 's,[@]pkglibexecdir[@],/usr/local/libexec/crosstool-ng,g' -e 's,[@]progname[@],'`echo ct-ng | sed 's,x,x,'`',g' | /bin/bash config.status --file=- ) < docs/ct-ng.1.in >docs/ct-ng.1-t && mv -f docs/ct-ng.1-t docs/ct-ng.1 make[2]: Leaving directory '/home/katsuhiro/share/projects/oss/crosstool-ng' make[1]: Leaving directory '/home/katsuhiro/share/projects/oss/crosstool-ng'
カレントディレクトリにct-ngという名前のファイルが生成されます。通常はクロスコンパイラをインストールして使いますが、私はインストールしないで欲しい(適宜入れ替えたいから)ので、--enable-localオプションを付けています。
$ ./ct-ng menuconfig
- Target options  --->
    Target Architecture (alpha)  --->
      armに変更する
    Bitness: (32-bit)  --->
      64-bitに変更する
- Operating System  --->
    Target OS (bare-metal)  --->
      linuxに変更する
- C compiler  --->
    C++ (NEW) を選択する
$ ./ct-ng build
[00:34] /
ビルド中は多少メッセージも出ますが、基本的に経過時間と棒がくるくる回るだけです。ログが見たい方は、ct-ngと同じディレクトリにあるbuild.logをtail -fなどで表示すると良いでしょう。
マシン性能によりますが、./ct-ng buildによるクロスコンパイラのビルドは1時間くらい掛かると思います。ビルドが終わると、ホームディレクトリにx-toolsというディレクトリが作られていると思います。AArch64用のクロスコンパイラ(gcc 8)をビルドした場合、x-tools以下は下記のようになっているはずです。
$ cd ~/x-tools $ ls aarch64-unknown-linux-gnu $ ls aarch64-unknown-linux-gnu aarch64-unknown-linux-gnu bin build.log.bz2 include lib libexec share $ ls aarch64-unknown-linux-gnu/bin aarch64-unknown-linux-gnu-addr2line aarch64-unknown-linux-gnu-gcov-dump aarch64-unknown-linux-gnu-ar aarch64-unknown-linux-gnu-gcov-tool aarch64-unknown-linux-gnu-as aarch64-unknown-linux-gnu-gfortran aarch64-unknown-linux-gnu-c++ aarch64-unknown-linux-gnu-gprof aarch64-unknown-linux-gnu-c++filt aarch64-unknown-linux-gnu-ld aarch64-unknown-linux-gnu-cc aarch64-unknown-linux-gnu-ld.bfd aarch64-unknown-linux-gnu-cpp aarch64-unknown-linux-gnu-ld.gold aarch64-unknown-linux-gnu-ct-ng.config aarch64-unknown-linux-gnu-ldd aarch64-unknown-linux-gnu-dwp aarch64-unknown-linux-gnu-nm aarch64-unknown-linux-gnu-elfedit aarch64-unknown-linux-gnu-objcopy aarch64-unknown-linux-gnu-g++ aarch64-unknown-linux-gnu-objdump aarch64-unknown-linux-gnu-gcc aarch64-unknown-linux-gnu-populate aarch64-unknown-linux-gnu-gcc-7.3.0 aarch64-unknown-linux-gnu-ranlib aarch64-unknown-linux-gnu-gcc-ar aarch64-unknown-linux-gnu-readelf aarch64-unknown-linux-gnu-gcc-nm aarch64-unknown-linux-gnu-size aarch64-unknown-linux-gnu-gcc-ranlib aarch64-unknown-linux-gnu-strings aarch64-unknown-linux-gnu-gcov aarch64-unknown-linux-gnu-strip
クロスコンパイラは~/x-tools/aarch64-unknown-linux-gnu/bin以下にあります。今後、このクロスコンパイラを使います。
カーネルはLinuxの開発版linux-nextを使います(Gitリポジトリへのリンク)。StableカーネルやLTSカーネルも同じ手順で良いと思いますが、古いカーネルをビルドするときは、crosstool-NGでgcc 7かgcc 6を選択したほうが良いかもしれません(ビルド時に警告が出てくると邪魔なので…)。
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git $ cd linux-next $ export ARCH=arm64 $ export CROSS_COMPILE=~/x-tools/aarch64-unknown-linux-gnu/bin/aarch64-unknown-linux-gnu- $ make defconfig $ make all scripts/kconfig/conf --syncconfig Kconfig WRAP arch/arm64/include/generated/uapi/asm/ioctl.h WRAP arch/arm64/include/generated/uapi/asm/errno.h WRAP arch/arm64/include/generated/uapi/asm/ioctls.h ...(snip)... LD [M] sound/soc/generic/snd-soc-simple-card-utils.ko LD [M] sound/soc/generic/snd-soc-simple-card.ko LD [M] sound/soc/sh/rcar/snd-soc-rcar.ko
マシン性能によりますが、カーネルのビルドは数十分くらい掛かると思います。AArch64用のカーネルをビルドした場合、arch/arm64/boot以下は下記のようになっているはずです。
$ cd arch/arm64/boot $ ls Image Image.gz Makefile dts install.sh
ビルドが終わると、arch/arm64/boot以下にImageという名前のファイルが作られていると思います。このイメージファイルを後で使います。
 この記事にコメントする
 この記事にコメントする
目次: GCC
目次: Linux
昨日の続きです。AArch64(64ビットARM)向けのLinux開発環境を構築しました。使用するツールは下記の通りです。
カーネルのビルドまで終わりましたので、次はルートファイルシステムです。
ルートファイルシステムの構築にはbuildrootを使います(Gitリポジトリへのリンク)。
$ git clone https://git.buildroot.net/buildroot
$ cd buildroot
$ make menuconfig
- Target options  --->
    Target Architecture (i386)  --->
      AArch64 (little endian) に変更する
- Toolchain  --->
    Toolchain type (Buildroot toolchain)  --->
      External toolchainに変更する
    Toolchain (Linaro AArch64 2018.05)  --->
      Custom toolchainに変更する
    () Toolchain path (NEW)
      /home/username/x-tools/aarch64-unknown-linux-gnuに変更する(チルダ ~ は使えないので、注意)
    ($(ARCH)-linux) Toolchain prefix
      aarch64-unknown-linux-gnuに変更する
    External toolchain gcc version (4.3.x)  --->
      8.xに変更する(crosstool-NGの設定と合わせる)
    External toolchain kernel headers series (2.6.x)  --->
      4.16.xに変更する(crosstool-NGの設定と合わせる)
    External toolchain C library (uClibc/uClibc-ng)  --->
      glibc/eglibcに変更する(crosstool-NGの設定と合わせる)
    Toolchain has C++ support? (NEW)
      設定する
- Filesystem images  --->
    cpio the root filesystem (for use as an initial RAM filesystem)
      設定する
$ make
/usr/bin/make -j1 O=/home/katsuhiro/share/projects/oss/buildroot/output HOSTCC="/usr/lib/ccache/gcc" HOSTCXX="/usr/lib/ccache/g++" silentoldconfig
...(snip)...
マシン性能によりますが、ほぼbusyboxしかビルドしませんので、カーネルよりは短い時間で終わるはずです。生成されたファイルはoutputディレクトリに集められています。output以下は下記のようになっているはずです。
$ ls output build host images staging target $ ls output/images/ rootfs.cpio rootfs.tar
ディレクトリには2つファイルがありますが、cpioフォーマットの方(rootfs.cpio)を使います。
エミュレータはqemu(Gitリポジトリへのリンク)を使います。qemuのビルドは後回しにして、とりあえず今までビルドしてきたカーネル+ルートファイルシステムを実行してみます。
Debianの場合はapt-get install qemu-systemでインストール可能です。
$ qemu-system-aarch64 -machine virt -cpu cortex-a57 -kernel linux-next/arch/arm64/boot/Image -initrd buildroot/output/images/rootfs.cpio -serial stdio [ 0.000000] Booting Linux on physical CPU 0x0000000000 [0x411fd070] [ 0.000000] Linux version 4.18.0-rc4-next-20180713 (katsuhiro@blackbird) (gcc version 8.1.0 (crosstool-NG 1.23.0.418-d590)) #1 SMP PREEMPT Mon Jul 16 14:38:51 JST 2018 [ 0.000000] Machine model: linux,dummy-virt [ 0.000000] efi: Getting EFI parameters from FDT: [ 0.000000] efi: UEFI not found. ...(snip)... [ 3.409356] ALSA device list: [ 3.410631] No soundcards found. [ 3.419281] uart-pl011 9000000.pl011: no DMA platform data [ 3.534574] Freeing unused kernel memory: 1344K Starting logging: OK Initializing random number generator... [ 5.446846] random: dd: uninitialized urandom read (512 bytes read) done. Starting network: OK Welcome to Buildroot buildroot login: root # uname -a Linux buildroot 4.18.0-rc4-next-20180713 #1 SMP PREEMPT Mon Jul 16 14:38:51 JST 2018 aarch64 GNU/Linux
オプションでハマったのは -cpu cortex-a57です。省略するとAArch64に対応していないCPUがデフォルトで選ばれるようで、全く動かないです。明示的にARMv8のCPUを指定してください。また -serial stdioを付けないと、シリアルが出力されません。
このときinitramfsを指定し忘れると、起動はしますが下記のようなメッセージを表示して停止します。
$ qemu-system-aarch64 -machine virt -cpu cortex-a57 -kernel linux-next/arch/arm64/boot/Image -serial stdio [ 0.000000] Booting Linux on physical CPU 0x0000000000 [0x411fd070] [ 0.000000] Linux version 4.18.0-rc4-next-20180713 (katsuhiro@blackbird) (gcc version 8.1.0 (crosstool-NG 1.23.0.418-d590)) #1 SMP PREEMPT Mon Jul 16 14:38:51 JST 2018 [ 0.000000] Machine model: linux,dummy-virt ...(snip)... [ 2.777433] ALSA device list: [ 2.777760] No soundcards found. [ 2.785662] uart-pl011 9000000.pl011: no DMA platform data [ 2.793530] VFS: Cannot open root device "(null)" or unknown-block(0,0): error -6 [ 2.794033] Please append a correct "root=" boot option; here are the available partitions: [ 2.794757] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) [ 2.796280] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 4.18.0-rc4-next-20180713 #1 [ 2.796760] Hardware name: linux,dummy-virt (DT) [ 2.797221] Call trace: [ 2.797472] dump_backtrace+0x0/0x148 [ 2.797837] show_stack+0x14/0x20 [ 2.798087] dump_stack+0x90/0xb4 [ 2.798352] panic+0x120/0x27c [ 2.798577] mount_block_root+0x1a0/0x250 [ 2.798875] mount_root+0x11c/0x148 [ 2.799126] prepare_namespace+0x128/0x16c [ 2.799423] kernel_init_freeable+0x208/0x228 [ 2.799732] kernel_init+0x10/0x100 [ 2.800005] ret_from_fork+0x10/0x18 [ 2.800694] Kernel Offset: disabled [ 2.801107] CPU features: 0x21806082 [ 2.801400] Memory Limit: none [ 2.802136] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]---
このようにmount_rootでpanicする場合は、qemuの -initrdオプションか、指定しているファイルをご確認ください。
 この記事にコメントする
 この記事にコメントする
「エアコンの嫌なニオイが完全に消えた」 "窓全開、16度で1時間つけっぱなし" で本当にニオイが取れる理由 - ねとらぼ を読んで。
こんなの嘘だろ…、と思ってうちの8年モノ(2010年製)のエアコンにやってみたら、素晴らしい効き目でした。運転開始から冷風がでるまでの、放置した雑巾のような臭いが、完全に消えました。
この効果はいつまで持つのか気になるので、また臭くなった時に備えて、いつ浄化したか思い出せるようにしておきます。
ちなみにエアコンを16度で全開運転すると、壊れるんじゃないかと思うくらい、室外機が唸り始めて、非常にうるさいです。
暑いけれど、昼にやった方が良いと思います。夜やると近所迷惑です。
メモ: 技術系の話はFacebookから転記しておくことにした。
 この記事にコメントする
 この記事にコメントする
UARTをBluetoothに変換してくれるHC-06(モジュールの販売サイト、仕様書も置いてある)モジュール搭載ボードを買いました。技適マークが無いように見えます、これ日本で使ったらダメな奴かな…?
電源は5Vで、UARTは3.3Vです。ArduinoとかRaspberry Pi用のUSB - UART変換ケーブルを使えば、電源からUARTまで全ての線が揃っているはずです。Raspberry Piに繋いでも動きます。
私が購入したボード(HiLetgo製ZS-040)の場合、電源を入れるとLEDが点滅し始めます。この状態でBluetoothデバイスの検索を行うとHC-06という名前(変更可)のデバイスが見つかりますので、ペアリングします。PINは1234です(変更可)。
ペアリングするとCOMが2つ増えると思います。なぜ2つなのか良くわかりません。片方はうんともすんとも言いませんので、使いません。もうひとつのやや接続に時間が掛かる方を使います。
2つのCOMのどちらを使うべきかは、LEDで見分けることができます。LEDが点滅→点灯に変わる方が本物(?)です。まあ、両方繋いでも問題は起きないようなので、面倒なら両方繋いでしまえば良いと思います。
ペアリングを解除した状態(LEDが点滅)にすると、HC-06の設定を変更することができます。シリアルの設定は、ボーレート9600、8ビット、パリティなしにします。
このデバイスは送った文字をエコーバックしないので、ローカルエコーを強制的にONにしてください。またキーボードで入力すると、ATコマンド先頭のAT二文字を打った段階で「OK」が返ってきてしまい、コマンド発行ができません。コマンド全体をメモ帳などに打ち、コピペで貼り付けるとうまくいきます。
メモ帳からコピペはイマイチなので、ライン編集モードをONにすれば良いだろと思ったら、行の最後にCRが送られてしまうためか、AT+PINコマンドなどがうまくいかなくなります。
デバイス名、つまりBluetoothデバイスの検索のときに出てくる名前です。デバイス名を変更するにはAT+NAMEの後に名前を打ちます。例えば "HC-06-1" に変えたければAT+NAMEHC-06-1を送ります。大文字と小文字は区別されます。返事にOKsetnameと返ってくれば成功です。
またPINを変える際はAT+PINを送ります。例えば8888に変えたければAT+PIN8888です。返事にOKsetPINと返ってくれば成功です。
シリアルの設定を変更するコマンドもあります。興味があれば仕様書を見てくださいまし。
 この記事にコメントする
 この記事にコメントする
目次: ARM
Rockchip RK3328搭載のシングルボードコンピュータ、ROCK64 4GB版を買いました。秋月電子で6,500円くらいでした。メモリの少ない1GB版にすると、さらにお安いです。
ROCK64はSDカードから起動できます。OSイメージは開発元が用意しているEtcherを使うと(GitHubからダウンロードできます)ダウンロード+SDカードへの書き込みが簡単にできます。便利です。
しかし手持ちのSDカードが4GBだったため、Etcherに8GBのカードを入れろと言われてしまい、OSイメージが書き込めませんでした。
最近は暑すぎて、電器屋に行くのさえ辛いですが、こればかりは行くしかないですね。昼の外出は危険だと思い、夕方にしましたが、それでも暑いです。
汗だくになりつつ16GBのSDカードを買ってきてDebianを書き込み、起動しましたがシリアルが出ません。何でだ。
どうしてROCK64のUARTが表示されないのかなと思ってググってみたら、ボーレートがまさかの1.5Mbpsでした(サポートフォーラムへのリンク)。UARTで1.5Mbpsなんてボーレート初めて使いました。
偶然、手持ちのFT232R(USB-UART変換チップ)が1.5Mbpsに対応していてラッキーでした。一方のHC06(Bluetooth-UART変換チップ)は1.5Mbpsに対応しておらず、UARTを無線化しようと思ってせっかく買いましたが撃沈です…。
シリアルを見ていると、独自ローダー(?)、ATF(ARM Trusted Firmware)、U-Bootの順に動いており、U-Bootはdistro bootを使ってLinux 4.4.77をロードしているようです。
DDR version 1.06 20170424 In SRX LPDDR3 786MHz Bus Width=32 Col=11 Bank=8 Row=15/15 CS=2 Die Bus-Width=32 Size=4096MB ddrconfig:7 OUT Boot1 Release Time: 2017-05-18, version: 2.43 ChipType = 0x11, 187 emmc reinit emmc reinit SdmmcInit=2 20 SdmmcInit=0 0 BootCapSize=0 UserCapSize=14832MB FwPartOffset=2000 , 0 StorageInit ok = 48105 Raw SecureMode = 0 SecureInit read PBA: 0x4 SecureInit read PBA: 0x404 SecureInit read PBA: 0x804 SecureInit read PBA: 0xc04 SecureInit read PBA: 0x1004 SecureInit ret = 0, SecureMode = 0 LoadTrustBL No find bl30.bin No find bl32.bin Load uboot, ReadLba = 2000 Load OK, addr=0x200000, size=0x92d74 RunBL31 0x10000 NOTICE: BL31: v1.3(debug):f947c7e NOTICE: BL31: Built : 09:28:45, May 31 2017 NOTICE: BL31:Rockchip release version: v1.3 INFO: ARM GICv2 driver initialized INFO: Using opteed sec cpu_context! INFO: boot cpu mask: 1 INFO: plat_rockchip_pmu_init: pd status 0xe INFO: BL31: Initializing runtime services WARNING: No OPTEE provided by BL2 boot loader, Booting device without OPTEE initialization. SMC`s destined for OPTEE will return SMC_UNK ERROR: Error initializing runtime service opteed_fast INFO: BL31: Preparing for EL3 exit to normal world INFO: Entry point address = 0x200000 INFO: SPSR = 0x3c9
U-Boot 2017.09-g5aef9f7 (Oct 12 2017 - 09:11:39 +0000), Build: jenkins-linux-build-rock-64-136 Model: Pine64 Rock64 DRAM: 4 GiB MMC: rksdmmc@ff500000: 1, rksdmmc@ff520000: 0 *** Warning - bad CRC, using default environment In: serial@ff130000 Out: serial@ff130000 Err: serial@ff130000 Model: Pine64 Rock64 Net: eth0: ethernet@ff540000 Hit any key to stop autoboot: 0
U-Bootの日付は2017年で、まだ新し目ですが、Linuxはかなり古いですね。後でUpstreamカーネルに入れ替えてみましょうか。
 この記事にコメントする
 この記事にコメントする
目次: ARM
昨日(2018年7月22日の日記参照)に引き続き、ROCK64をいじっています。
私も先日知ったばかりで、さほど詳しくありませんが、distro bootはかなり便利です。ROCK64のSDカードはmmc 1です(※)ので、SDカードにFATかext2のパーティション(例えば6番目だとする)を切ってmkfsしておいて、
sysboot mmc 1:6 fat 0x500000 /extlinux/extlinux.conf
このようなコマンドを実行すると、extlinux.confに書いてある設定に従ってカーネルをロードしてくれます。Debianなどもこの設定ファイルを使えば起動できて便利です。
アプリの開発者ならばdistro bootの方が速いし楽でしょう。カーネルの開発者ならばTFTPブートの方が嬉しいかもしれません。SDカードを抜き差しせずに済みます。
一応ROCK64でカーネルをセルフコンパイルすれば、PC要らずで自己完結できますし、SDカードの抜き差しも要りません。しかしROCK64は、さすがにコンパイルに使うには遅いので、クロスコンパイル+TFTPブートの方が効率は良さそうです……。
(※)ROCK64のU-Bootから見ると、SDカードはmmc 1です。オプションで売っているeMMCボードがmmc 0らしいです。私はeMMCボード持ってないので、詳細はわかりません。
U-Bootのdistro bootのお陰で、カーネルの入れ替えはとても楽です。ROCK64のカーネルを4.4からupstreamのlinux-nextに入れ替えてみたところ、本当に入れ替えたのか不安になるくらい、何も問題なく起動しました。Rockchipやるなあ…。
RockchipはOpenSourceプロジェクトWiki(Wikiへのリンク)があり、ブートローダーから全てOSSで動かせるようです。ブートローダーは隠されていて、ソースコードを見るなんて有り得なかった時代に比べると、隔世の感です。
Rockchip以外にもAllWinnerもLinuxへのupstream活動は盛んなようで、upstreamカーネルが動く可能性が高そうです。AllWinnerのボードは何が良いんでしょうね?お手頃感ではNanoPi NEO 2でしょうか?
RockchipもAllWinnerもシングルボードコンピュータに多く採用されており、性能もなかなかですし、とても安く手に入ります。決して速くないマイコンボードが1枚何万円もしていた時代に比べると、ありがたい時代になったと思います。
ブートローダーも最新版に入れ替えようと思い、Rockchip Opensource Wikiに乗っていた手順を試しましたが、ちょっと内容が古いのかU-Bootのビルドは通りますが、ATFのビルドはコケてしまいました。
Wiki曰くSDカードの0x40セクタに2ndブートローダーを置けば良いらしいですが、U-Boot SPLは廃止されてしまいましたし、ATFはビルドが通らなかったため、肝心のブートローダー(U-Boot SPLもしくはATF BL31)が作れません。困った。
現状、ブートローダーを変更したい積極的な理由も特にないので、ブートローダーのビルドはまた今度ですね。
 この記事にコメントする
 この記事にコメントする
 wiki
 wiki Linux JM
 Linux JM Java API
 Java API 2002年
 2002年 2003年
 2003年 2004年
 2004年 2005年
 2005年 2006年
 2006年 2007年
 2007年 2008年
 2008年 2009年
 2009年 2010年
 2010年 2011年
 2011年 2012年
 2012年 2013年
 2013年 2014年
 2014年 2015年
 2015年 2016年
 2016年 2017年
 2017年 2018年
 2018年 2019年
 2019年 2020年
 2020年 2021年
 2021年 2022年
 2022年 2023年
 2023年 2024年
 2024年 2025年
 2025年 過去日記について
 過去日記について アクセス統計
 アクセス統計 サーバ一覧
 サーバ一覧 サイトの情報
 サイトの情報