コグノスケ


2018年 7月 2日

libtool の謎のエラー

Raspberry Pi 3 を持っているのですが、あまり速くない(当たり前ですけど)こともあり、ほとんどコンパイルには使っていませんでした。今日、久しぶりにコードのビルドに使ってみたら、変な症状にハマりました。

autoreconf は正常終了?
$ 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 がエラーで落ちる
$ ./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 をいじってみてわかったことは、以下の条件を満たしていると、このエラーが発生するようです。

  • configure.ac に LT_INIT() を記述、つまり libtool を使うように記述する
  • システムに libtool がインストールされていない
  • 更地(autoconf, automake 関連のファイルが全く無い状態)からビルドする

解決策は libtool をインストールするか、libtool を使っていないなら configure.ac から LT_INIT() を削除しても良いです。

この辺の仕組みは詳しくありませんが、libtool が無いなら無いと言ってくれれば、もう少しわかりやすいのにな…と思います。

編集者: すずき(更新: 2018年 7月 4日 23:12)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2018年 7月 4日

デスクランブラのテスト

先日、作った(2018年 6月 1日の日記参照)ISDB というか ARIB のデスクランブラの続きです。

DVB API で制御可能なチューナー(私は PT2 で確認しています)を使っている方であれば、下記のようにチューニング(コードは GitHub にあります)できます。チューニングに成功して放送が受信されると、/dev/dvb/adapter0/dvr0 からスクランブルの掛かった MPEG2-TS が出力されます。

PT2 のチューニングのテスト
例: 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つありましたが、単純に消すわけにいかなくてやや難しそうだったので、また今度にします。

ARM でも実行してみた

PC だと、CPU 利用率 10% 程度だったので、最近のマルチコア CPU なら割と余裕の負荷です。ではショボい CPU で実行するとどうなるか、試してみました。

手持ちの Raspberry Pi 3(ARM Cortex A53 x 4/1.4GHz)で実行してみたところ、CPU 利用率 25〜27% 程度でした。動かないかもしれないと思っていたので、正直意外でした。かなり健闘していると思います。

ARM には NEON という SIMD 命令がありますが、NEON を使った復号の高速化にはまだ手を出していません。今度やってみますが、SSE2 の結果を見た限りでは、絶大な効果は見込めないでしょう。きっと。

編集者: すずき(更新: 2018年 7月 5日 00:44)

コメント一覧

  • すずき 
    NEON にも対応してみましたが、やはり MULTI2 の復号はボトルネックではないらしく、CPU 負荷はほとんど変わりませんでした……。 
    (2018年07月11日 21:26:36)
open/close この記事にコメントする



2018年 7月 5日

Android と MPEG2-TS その 1

その 1その 2その 3その 4

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 を処理しているところは、下記のようになっています。

Extractor 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 の実装を見てみます。

TS 受付部分

//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() が追加するようです。

PAT/PMT 解析

//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(&sectionBits); //★★★★ 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, &sectionBits, &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 に載っていないのにスクランブルの掛かった変なストリームがあると警告が出るようになっています。

編集者: すずき(更新: 2018年 7月 17日 22:45)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2018年 7月 6日

Android と MPEG2-TS その 2

その 1その 2その 3その 4

昨日の続き。スクランブルの掛かったストリームは mParser->mCasManager に任せていました。mCasManager は ATSParser::CasManager でしたので、実装を見てみます。

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 の方にコードがあります。

processEcm() の実装

//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 という実装があります。

ClearKey の実装

//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 の解読を行っている箇所が見つけられました。エレメンタリストリームのデスクランブルはどこで行っているのでしょうね…??

編集者: すずき(更新: 2018年 7月 17日 22:45)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2018年 7月 7日

Android と MPEG2-TS その 3

その 1その 2その 3その 4

デスクランブル処理はどこにあるんでしょう??それらしい処理を辿ってみましたが、本当にこの処理が動くのか、何を切っ掛けにデスクランブル処理が働き始めるのか、動かしてみないとわからなさそうです。うーん……。

デスクランブル処理 Java 側(たぶん)

//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 に配置されているようです。

デスクランブル処理 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 にそれらしき処理があります。

Descrambler plugin の実装

//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() にて設定されています。これは後で追ってみます。

ClearKey によるデスクランブル

//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() によって、スクランブルされたデータのデスクランブルができそう、ということがわかりました。

ドキュメントにそう書いてあるし、当たり前なんですけどね。新たにデスクランブラを足そうと思う人は、中身も知らないと足せないです。

編集者: すずき(更新: 2018年 7月 17日 22:46)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2018年 7月 8日

Android と MPEG2-TS その 4

その 1その 2その 3その 4

もはや自分以外の誰得の内容なのか、わかりませんが、気にせず書きます。

その 3 にて「DescramblerImpl を生成するのは MediaCasService::createDescrambler() のみ?のように見えます。」と書きました。この関数に至る経路がわかると、デスクランブラがどの暗号系を選択するのか?いつ選択するのか?などがわかるようになります。

で、今回は createDescrambler() が呼ばれる経路を見つけたのでメモします。しかしながら Extractor の setMediaCas() を呼び出すのは誰なのか?まだ謎のままなので、いまいちスッキリしませんけども。

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 の実装を見てみます。

ATSParser setMediaCas()

//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 を見てみましょう。

CasManager setMediaCas()

//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 です。

ProgramCasManager setMediaCas()

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つしかなさそうです。

MediaCasService setMediaCas()

//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() を呼ぶと、

  • ATSParser::CasManager::parsePID() が呼び出している mICas->processEcm() の mICas が設定される。
    つまり ECM の処理方法が決まる
  • DescramblerImpl::mPluginHolder が設定される。
    つまりデスクランブルの処理方法が決まる
編集者: すずき(更新: 2018年 7月 17日 22:44)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2018年 7月 15日

AArch64 向け Linux 開発環境の構築 その 1

目次: GCC を調べる - まとめリンク

AArch64(64 ビット ARM)向けの Linux 開発環境を構築しました。超有名ツールの組み合わせだし、簡単だろうと思っていたのですが、意外とハマって 1日掛かってしまったのでメモしておきます。使用するツールは下記の通りです。

  • クロスコンパイラ: crosstool-NG
  • カーネル: linux-next
  • ルートファイルシステム: buildroot
  • エミュレータ: qemu

クロスコンパイラ

クロスコンパイラを生成するため crosstool-NG(GitHub へのリンク)を使います。crosstool-NG は ARM 向け以外にもクロスコンパイラやツールチェインの構築が簡単にできて便利です。

私の環境(Debian Testing)だと libtool-bin パッケージがインストールされていなくてハマったので、インストールしておくと良いかもしれません。

crosstool-NG コード取得、セットアップ
$ 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 オプションを付けています。

crosstool-NG ビルド
$ ./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 以下は下記のようになっているはずです。

crosstool-NG 生成物
$ 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 を選択したほうが良いかもしれません(ビルド時に警告が出てくると邪魔なので…)。

linux-next コード取得、セットアップ、ビルド
$ 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 以下は下記のようになっているはずです。

linux-next 生成物
$ cd arch/arm64/boot

$ ls
Image  Image.gz  Makefile  dts  install.sh

ビルドが終わると、arch/arm64/boot 以下に Image という名前のファイルが作られていると思います。このイメージファイルを後で使います。

編集者: すずき(更新: 2021年 5月 20日 16:44)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2018年 7月 16日

AArch64 向け Linux 開発環境の構築 その 2

目次: GCC を調べる - まとめリンク

昨日の続きです。AArch64(64 ビット ARM)向けの Linux 開発環境を構築しました。使用するツールは下記の通りです。

  • クロスコンパイラ: crosstool-NG
  • カーネル: linux-next
  • ルートファイルシステム: buildroot
  • エミュレータ: qemu

カーネルのビルドまで終わりましたので、次はルートファイルシステムです。

ルートファイルシステム

ルートファイルシステムの構築には buildroot を使います(Git リポジトリへのリンク)。

buildroot コード取得、セットアップ、ビルド
$ 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 以下は下記のようになっているはずです。

buildroot 生成物
$ 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 実行
$ 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 を指定し忘れると、起動はしますが下記のようなメッセージを表示して停止します。

Linux が panic で止まる
$ 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 オプションか、指定しているファイルをご確認ください。

編集者: すずき(更新: 2021年 5月 20日 16:44)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2018年 7月 17日

エアコンが臭い

「エアコンの嫌なニオイが完全に消えた」 "窓全開、16度で 1時間つけっぱなし" で本当にニオイが取れる理由 - ねとらぼ を読んで。

こんなの嘘だろ…、と思ってうちの 8年モノ(2010年製)のエアコンにやってみたら、素晴らしい効き目でした。運転開始から冷風がでるまでの、放置した雑巾のような臭いが、完全に消えました。

この効果はいつまで持つのか気になるので、また臭くなった時に備えて、いつ浄化したか思い出せるようにしておきます。

騒音に注意

ちなみにエアコンを 16度で全開運転すると、壊れるんじゃないかと思うくらい、室外機が唸り始めて、非常にうるさいです。

暑いけれど、昼にやった方が良いと思います。夜やると近所迷惑です。

メモ: 技術系の話は Facebook から転記しておくことにした。

編集者: すずき(更新: 2018年 7月 17日 22:53)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2018年 7月 21日

Bluetooth UART 変換

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 と返ってくれば成功です。

シリアルの設定を変更するコマンドもあります。興味があれば仕様書を見てくださいまし。

編集者: すずき(更新: 2018年 7月 22日 04:22)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2018年 7月 22日

ROCK64 購入

目次: ROCK64/ROCKPro64 - まとめリンク

Rockchip RK3328 搭載のシングルボードコンピュータ、ROCK64 4GB 版を買いました。秋月電子で 6,500円くらいでした。メモリの少ない 1GB 版にすると、さらにお安いです。

ROCK64 は SD カードから起動できます。OS イメージは開発元が用意している Etcher を使うと(GitHub からダウンロードできます)ダウンロード+SD カードへの書き込みが簡単にできます。便利です。

しかし手持ちの SD カードが 4GB だったため、Etcher に 8GB のカードを入れろと言われてしまい、OS イメージが書き込めませんでした。

最近は暑すぎて、電器屋に行くのさえ辛いですが、こればかりは行くしかないですね。昼の外出は危険だと思い、夕方にしましたが、それでも暑いです。

汗だくになりつつ 16GB の SD カードを買ってきて Debian を書き込み、起動しましたがシリアルが出ません。何でだ。

ROCK64 の爆速 UART

どうして 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 をロードしているようです。

リセットから U-Boot 手前までのログ
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 の起動ログ
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 カーネルに入れ替えてみましょうか。

編集者: すずき(更新: 2020年 10月 30日 01:19)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2018年 7月 23日

ROCK64 と U-Boot の distro boot

目次: ROCK64/ROCKPro64 - まとめリンク

昨日(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 ボード持ってないので、詳細はわかりません。

Upstream カーネル

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)が作れません。困った。

現状、ブートローダーを変更したい積極的な理由も特にないので、ブートローダーのビルドはまた今度ですね。

編集者: すずき(更新: 2020年 10月 30日 01:20)

コメント一覧

  • t4 
    > U-Boot SPL は廃止されてしまいましたし...

    廃止されてないけど、どこを見たんでしょう? 
    (2018年07月29日 22:33:03)
  • すずき 
    >t4 さん
    ご指摘ありがとうございます。
    トップディレクトリ下にある spl というディレクトリも、u-boot-spl.bin も生成されないため、廃止されたのかと思いました。 
    (2018年07月30日 15:33:41)
  • t4 
    https://github.com/rockchip-linux/u-boot
    https://github.com/ayufan-rock64/linux-u-boot
    http://git.denx.de/u-boot.git/

    一応 1番上がこのデバイスの本家だけど
    他所は、本家の品質が悪るすぎて Pull-Request を蹴ってるような状況
    どこからGetしたのかな?
    本家のヤツには tpl/spl はそのまま残ってる(品質は別として)

    本家はものは、ビルドが通らないとか、クラッシュするとか、
    真っ当な issue を報告しても無視して即刻 Close とか
    ハッキリ言って Pull-Request を蹴られても当然の酷いレベル
    linux-kernel とか その他諸々も、 総じてそんな感じ。

    そんな理由もあって
    よほどのアドバンテージが無い限り、大概の処は 古いやつを 使ってる
    んでも、彼らはデバイスメーカーしか知らない情報を持ってるからね
     
    (2018年07月31日 10:06:36)
  • すずき 
    > t4 さん
    使っているのは、
    http://git.denx.de/u-boot.git/
    です。

    > 本家はものは、ビルドが通らないとか、クラッシュするとか、
    > 真っ当な issue を報告しても無視して即刻 Close とか
    > ハッキリ言って Pull-Request を蹴られても当然の酷いレベル
    > linux-kernel とか その他諸々も、 総じてそんな感じ。

    なんと、そうなんですか。情報ありがとうございます。


    > そんな理由もあって
    > よほどのアドバンテージが無い限り、大概の処は 古いやつを 使ってる
    > んでも、彼らはデバイスメーカーしか知らない情報を持ってるからね

    デバイスメーカーしか知らない情報で、公開できる範囲の情報を
    Linux や U-Boot の Upstream に公開してくれている、
    と思っていたのですが、そううまくは行っていないということですね。

    Linux はとりあえず
    git://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
    が動作するようなので、ブートローダはそっとしておいて、こちらで遊ぼうかと思っています。
     
    (2018年07月31日 10:36:16)
  • t4 
    誰でも見れる情報として公開されているのはこれぐらい
    http://opensource.rock-chips.com/images/9/97/Rockchip_RK3328TRM_V1.1-Part1-20170321.pdf
    中をみて見てみれば解るけど、この情報で弄れるのはベーシックなI/O程度がせいぜい
    込み入ったI/O usb, ether, gpu, vpu の類は何も無し。

    データシート/アプリケーション・ノートの代わりに、ソース・コードを公開
    って方針なんだろうけど
    まぁ、詳細資料は出したくない ってのは理解できる

    しかし、
    公開のサンプル・コードが上手く動かない場合、リバースしながら尚且つ推測もしなければならない
    リバースは良しとしても、推測には限界があるよね。
    真摯に issue に対応してくれれば、資料非公開でも一向に構わないんだが…

    git.denx.de: 確か昨年蹴られてたね reason: build-error
    issue を報告した人達: オマエの処には2度と報告しない
    Armbianの人: 怒ってた、俺らのポーティング終わる前にぶっ壊れる(新しいヤツが)

    グチっぽくなったけど、
    それでも pi とに比べれば資料は多い方だとは思うな

    ---
    お付き合い、ありがとうございました。
     
    (2018年08月01日 12:26:53)
  • すずき 
    > t4 さん

    > 誰でも見れる情報として公開されているのはこれぐらい
    > http://opensource.rock-chips.com/images/9/97/Rockchip_RK3328TRM_V1.1-Part1-20170321.pdf
    > 中をみて見てみれば解るけど、この情報で弄れるのはベーシックなI/O程度がせいぜい
    > 込み入ったI/O usb, ether, gpu, vpu の類は何も無し。

    私も、ビデオデコーダや映像出力系の仕様を見たかったのですが、
    Rockchip OSS Wiki には、残念ながら載っていないです。

    グラフィクスも Mali Utgard 系ですが、
    ARM は Mali のドライバ(ユーザ空間側)を公開していないので、
    カーネルモジュールだけあっても、どうにもなりません…。


    > データシート/アプリケーション・ノートの代わりに、ソース・コードを公開
    > って方針なんだろうけど
    > まぁ、詳細資料は出したくない ってのは理解できる

    確かに、社内の抵抗は強そうです。


    > しかし、
    > 公開のサンプル・コードが上手く動かない場合、リバースしながら尚且つ推測もしなければならない
    > リバースは良しとしても、推測には限界があるよね。
    > 真摯に issue に対応してくれれば、資料非公開でも一向に構わないんだが…
    >
    > git.denx.de: 確か昨年蹴られてたね reason: build-error
    > issue を報告した人達: オマエの処には2度と報告しない
    > Armbianの人: 怒ってた、俺らのポーティング終わる前にぶっ壊れる(新しいヤツが)

    Rockchip は OSS とのコラボがうまく行っている方だと思っていたのですが、
    実情はなかなか、そううまくは行かないのですね。

    邪推ですが Rockchip 社が OSS 貢献する社員にどれくらい
    好感触を持つか次第で OSS との付き合い方は変わりそうですね。


    > グチっぽくなったけど、
    > それでも pi とに比べれば資料は多い方だとは思うな
    >
    > ---
    > お付き合い、ありがとうございました。

    こちらこそ、貴重な情報ありがとうございました。

    安価なワンボードコンピュータは普及しましたが、
    完全にオープンになった HW はまだ先なのでしょうね。
    いつかどこかが作る(古い SoC を公開するとか、そんな形でも)
    と期待しています。
     
    (2018年08月01日 20:28:39)
open/close この記事にコメントする



こんてんつ

open/close wiki
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報