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と返ってくれば成功です。
シリアルの設定を変更するコマンドもあります。興味があれば仕様書を見てくださいまし。
目次: 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が表示されないのかなと思ってググってみたら、ボーレートがまさかの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カーネルに入れ替えてみましょうか。
目次: 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ボード持ってないので、詳細はわかりません。
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)が作れません。困った。
現状、ブートローダーを変更したい積極的な理由も特にないので、ブートローダーのビルドはまた今度ですね。