/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct TaskPool { struct Task data[MAX_TASKS]; }; static struct TaskPool mTaskPool; static struct EvtQueue *mEvtsInternal; static struct SlabAllocator* mMiscInternalThingsSlab; static struct TaskList mFreeTasks; static struct TaskList mTasks; static struct Task *mCurrentTask; static struct Task *mSystemTask; static TaggedPtr *mCurEvtEventFreeingInfo = NULL; //used as flag for retaining. NULL when none or already retained static inline void list_init(struct TaskList *l) { l->prev = l->next = NO_NODE; } struct Task *osGetCurrentTask() { return mCurrentTask; } struct Task *osSetCurrentTask(struct Task *task) { struct Task *old = mCurrentTask; while (true) { old = mCurrentTask; if (atomicCmpXchgPtr((uintptr_t*)&mCurrentTask, (uintptr_t)old, (uintptr_t)task)) { break; } } return old; } // beyond this point, noone shall access mCurrentTask directly static inline bool osTaskTestFlags(struct Task *task, uint32_t mask) { return (atomicReadByte(&task->flags) & mask) != 0; } bool osAppIsChre(uint16_t tid) { struct Task *task = osTaskFindByTid(tid); return task && osTaskIsChre(task); } uint32_t osAppChreVersion(uint16_t tid) { struct Task *task = osTaskFindByTid(tid); if (task) return osTaskChreVersion(task); else return 0; } static inline uint32_t osTaskClrSetFlags(struct Task *task, uint32_t clrMask, uint32_t setMask) { while (true) { uint8_t flags = atomicReadByte(&task->flags); uint8_t newFlags = (flags & ~clrMask) | setMask; if (atomicCmpXchgByte(&task->flags, flags, newFlags)) return newFlags; } } static inline uint32_t osTaskAddIoCount(struct Task *task, int32_t delta) { uint8_t count = atomicAddByte(&task->ioCount, delta); count += delta; // old value is returned, so we add it again return count; } static inline uint32_t osTaskGetIoCount(struct Task *task) { return atomicReadByte(&task->ioCount); } uint8_t osTaskIndex(struct Task *task) { // we don't need signed diff here: this way we simplify boundary check size_t idx = task - &mTaskPool.data[0]; return idx >= MAX_TASKS || &mTaskPool.data[idx] != task ? NO_NODE : idx; } static inline struct Task *osTaskByIdx(size_t idx) { return idx >= MAX_TASKS ? NULL : &mTaskPool.data[idx]; } uint32_t osGetCurrentTid() { struct Task *task = osGetCurrentTask(); if (task == NULL) { return UINT32_MAX; } return task->tid; } uint32_t osSetCurrentTid(uint32_t tid) { struct Task *task = osTaskByIdx(TID_TO_TASK_IDX(tid)); if (task && task->tid == tid) { struct Task *preempted = osSetCurrentTask(task); return preempted->tid; } return osGetCurrentTid(); } static inline struct Task *osTaskListPeekHead(struct TaskList *listHead) { TaskIndex idx = listHead->next; return idx == NO_NODE ? NULL : &mTaskPool.data[idx]; } #ifdef DEBUG static void dumpListItems(const char *p, struct TaskList *listHead) { int i = 0; struct Task *task; osLog(LOG_ERROR, "List: %s (%p) [%u;%u]\n", p, listHead, listHead ? listHead->prev : NO_NODE, listHead ? listHead->next : NO_NODE ); if (!listHead) return; for_each_task(listHead, task) { osLog(LOG_ERROR, " item %d: task=%p TID=%04X [%u;%u;%u]\n", i, task, task->tid, task->list.prev, osTaskIndex(task), task->list.next ); ++i; } } static void dumpTaskList(const char *f, struct Task *task, struct TaskList *listHead) { osLog(LOG_ERROR, "%s: pool: %p; task=%p [%u;%u;%u]; listHead=%p [%u;%u]\n", f, &mTaskPool, task, task ? task->list.prev : NO_NODE, osTaskIndex(task), task ? task->list.next : NO_NODE, listHead, listHead ? listHead->prev : NO_NODE, listHead ? listHead->next : NO_NODE ); dumpListItems("Tasks", &mTasks); dumpListItems("Free Tasks", &mFreeTasks); } #else #define dumpTaskList(a,b,c) #endif static inline void osTaskListRemoveTask(struct TaskList *listHead, struct Task *task) { if (task && listHead) { struct TaskList *cur = &task->list; TaskIndex left_idx = cur->prev; TaskIndex right_idx = cur->next; struct TaskList *left = left_idx == NO_NODE ? listHead : &mTaskPool.data[left_idx].list; struct TaskList *right = right_idx == NO_NODE ? listHead : &mTaskPool.data[right_idx].list; cur->prev = cur->next = NO_NODE; left->next = right_idx; right->prev = left_idx; } else { dumpTaskList(__func__, task, listHead); } } static inline void osTaskListAddTail(struct TaskList *listHead, struct Task *task) { if (task && listHead) { struct TaskList *cur = &task->list; TaskIndex last_idx = listHead->prev; TaskIndex new_idx = osTaskIndex(task); struct TaskList *last = last_idx == NO_NODE ? listHead : &mTaskPool.data[last_idx].list; cur->prev = last_idx; cur->next = NO_NODE; last->next = new_idx; listHead->prev = new_idx; } else { dumpTaskList(__func__, task, listHead); } } static struct Task *osAllocTask() { struct Task *task = osTaskListPeekHead(&mFreeTasks); if (task) { osTaskListRemoveTask(&mFreeTasks, task); uint16_t tid = task->tid; memset(task, 0, sizeof(*task)); task->tid = tid; } return task; } static void osFreeTask(struct Task *task) { if (task) { task->flags = 0; task->ioCount = 0; osTaskListAddTail(&mFreeTasks, task); } } static void osRemoveTask(struct Task *task) { osTaskListRemoveTask(&mTasks, task); } static void osAddTask(struct Task *task) { osTaskListAddTail(&mTasks, task); } struct Task* osTaskFindByTid(uint32_t tid) { TaskIndex idx = TID_TO_TASK_IDX(tid); return idx < MAX_TASKS ? &mTaskPool.data[idx] : NULL; } static inline bool osTaskInit(struct Task *task) { struct Task *preempted = osSetCurrentTask(task); bool done = cpuAppInit(task->app, &task->platInfo, task->tid); osSetCurrentTask(preempted); return done; } static void osTaskRelease(struct Task *task) { uint32_t taskTid = task->tid; uint32_t platErr, sensorErr; int timErr, heapErr; uint64_t appId; if (task->app) appId = task->app->hdr.appId; else appId = 0; platErr = platFreeResources(taskTid); // HW resources cleanup (IRQ, DMA etc) sensorErr = sensorFreeAll(taskTid); timErr = timTimerCancelAll(taskTid); heapErr = heapFreeAll(taskTid); if (platErr || sensorErr || timErr || heapErr) osLog(LOG_WARN, "released app ID 0x%" PRIx64 "; plat:%08" PRIx32 " sensor:%08" PRIx32 " tim:%d heap:%d; TID %04" PRIX32 "\n", appId, platErr, sensorErr, timErr, heapErr, taskTid); else osLog(LOG_INFO, "released app ID 0x%" PRIx64 "; TID %04" PRIX32 "\n", appId, taskTid); } static inline void osTaskEnd(struct Task *task) { if (!osTaskTestFlags(task, FL_TASK_ABORTED)) { struct Task *preempted = osSetCurrentTask(task); cpuAppEnd(task->app, &task->platInfo); osSetCurrentTask(preempted); } // task was supposed to release it's resources, // but we do our cleanup anyway // NOTE: we don't need to unsubscribe from events osTaskRelease(task); } static inline void osTaskHandle(struct Task *task, uint16_t evtType, uint16_t fromTid, const void* evtData) { struct Task *preempted = osSetCurrentTask(task); cpuAppHandle(task->app, &task->platInfo, EVENT_WITH_ORIGIN(evtType, osTaskIsChre(task) ? fromTid : 0), evtData); osSetCurrentTask(preempted); } void osTaskInvokeMessageFreeCallback(struct Task *task, void (*freeCallback)(void *, size_t), void *message, uint32_t messageSize) { if (!task || !freeCallback) return; cpuAppInvoke(task->app, &task->platInfo, (void (*)(uintptr_t,uintptr_t))freeCallback, (uintptr_t)message, (uintptr_t)messageSize); } void osTaskInvokeEventFreeCallback(struct Task *task, void (*freeCallback)(uint16_t, void *), uint16_t event, void *data) { if (!task || !freeCallback) return; cpuAppInvoke(task->app, &task->platInfo, (void (*)(uintptr_t,uintptr_t))freeCallback, (uintptr_t)event, (uintptr_t)data); } static void osPrivateEvtFreeF(void *event) { union SeosInternalSlabData *act = event; uint16_t fromTid = act->privateEvt.fromTid; struct Task *srcTask = osTaskFindByTid(fromTid); TaggedPtr evtFreeInfo = act->privateEvt.evtFreeInfo; uint32_t evtType = act->privateEvt.evtType; void *evtData = act->privateEvt.evtData; slabAllocatorFree(mMiscInternalThingsSlab, event); if (!srcTask) { osLog(LOG_ERROR, "ERROR: Failed to find task to free event: evtType=%08" PRIX32 "\n", evtType); return; } if (taggedPtrIsPtr(evtFreeInfo) && taggedPtrToPtr(evtFreeInfo)) { if (osTaskIsChre(srcTask) && (evtType >> 16) == EVT_PRIVATE_CLASS_CHRE) { osChreFreeEvent(fromTid, (void (*)(uint16_t, void *))taggedPtrToPtr(evtFreeInfo), evtType & EVT_MASK, evtData); } else { // this is for internal non-CHRE tasks, and CHRE tasks // System may schedule non-CHRE events on behalf of CHRE app; // this is the place we release them struct Task *preempted = osSetCurrentTask(srcTask); ((EventFreeF)taggedPtrToPtr(evtFreeInfo))(evtData); osSetCurrentTask(preempted); } } else if (taggedPtrIsUint(evtFreeInfo)) { // this is for external non-CHRE tasks struct AppEventFreeData fd = {.evtType = evtType, .evtData = evtData}; osTaskHandle(srcTask, EVT_APP_FREE_EVT_DATA, OS_SYSTEM_TID, &fd); } osTaskAddIoCount(srcTask, -1); } static void handleEventFreeing(uint32_t evtType, void *evtData, TaggedPtr evtFreeData) // watch out, this is synchronous { struct Task *srcTask = osTaskFindByTid(EVENT_GET_ORIGIN(evtType)); if (!srcTask) { osLog(LOG_ERROR, "ERROR: Failed to find task to free event: evtType=%08" PRIX32 "\n", evtType); return; } // release non-CHRE event; we can't determine if this is CHRE or non-CHRE event, but // this method is only called to release non-CHRE events, so we make use of that fact if (taggedPtrIsPtr(evtFreeData) && taggedPtrToPtr(evtFreeData)) { // this is for internal non-CHRE tasks, and CHRE tasks // System may schedule non-CHRE events on behalf of CHRE app; // this is the place we release them struct Task *preempted = osSetCurrentTask(srcTask); ((EventFreeF)taggedPtrToPtr(evtFreeData))(evtData); osSetCurrentTask(preempted); } else if (taggedPtrIsUint(evtFreeData)) { // this is for external non-CHRE tasks struct AppEventFreeData fd = {.evtType = EVENT_GET_EVENT(evtType), .evtData = evtData}; osTaskHandle(srcTask, EVT_APP_FREE_EVT_DATA, OS_SYSTEM_TID, &fd); } osTaskAddIoCount(srcTask, -1); } static void osInit(void) { heapInit(); platInitialize(); osLog(LOG_INFO, "SEOS Initializing\n"); cpuInitLate(); /* create the queues */ if (!(mEvtsInternal = evtQueueAlloc(512, handleEventFreeing))) { osLog(LOG_INFO, "events failed to init\n"); return; } mMiscInternalThingsSlab = slabAllocatorNew(sizeof(union SeosInternalSlabData), alignof(union SeosInternalSlabData), 64 /* for now? */); if (!mMiscInternalThingsSlab) { osLog(LOG_INFO, "deferred actions list failed to init\n"); return; } } static struct Task* osTaskFindByAppID(uint64_t appID) { struct Task *task; for_each_task(&mTasks, task) { if (task->app && task->app->hdr.appId == appID) return task; } return NULL; } void osSegmentIteratorInit(struct SegmentIterator *it) { uint32_t sz; uint8_t *start = platGetSharedAreaInfo(&sz); it->shared = (const struct Segment *)(start); it->sharedEnd = (const struct Segment *)(start + sz); it->seg = NULL; } bool osAppSegmentSetState(const struct AppHdr *app, uint32_t segState) { bool done; struct Segment *seg = osGetSegment(app); uint8_t state = segState; if (!seg) return false; mpuAllowRamExecution(true); mpuAllowRomWrite(true); done = BL.blProgramShared(&seg->state, &state, sizeof(state), BL_FLASH_KEY1, BL_FLASH_KEY2); mpuAllowRomWrite(false); mpuAllowRamExecution(false); return done; } bool osSegmentSetSize(struct Segment *seg, uint32_t size) { bool ret = true; if (!seg) return false; if (size > SEG_SIZE_MAX) { seg->state = SEG_ST_ERASED; size = SEG_SIZE_MAX; ret = false; } seg->size[0] = size; seg->size[1] = size >> 8; seg->size[2] = size >> 16; return ret; } struct Segment *osSegmentGetEnd() { uint32_t size; uint8_t *start = platGetSharedAreaInfo(&size); return (struct Segment *)(start + size); } uint32_t osSegmentGetFree() { struct SegmentIterator it; const struct Segment *storageSeg = NULL; osSegmentIteratorInit(&it); while (osSegmentIteratorNext(&it)) { if (osSegmentGetState(it.seg) == SEG_ST_EMPTY) { storageSeg = it.seg; break; } } if (!storageSeg || storageSeg > it.sharedEnd) return 0; return (uint8_t *)it.sharedEnd - (uint8_t *)storageSeg; } struct Segment *osGetSegment(const struct AppHdr *app) { uint32_t size; uint8_t *start = platGetSharedAreaInfo(&size); return (struct Segment *)((uint8_t*)app && (uint8_t*)app >= start && (uint8_t*)app < (start + size) ? (uint8_t*)app - sizeof(struct Segment) : NULL); } bool osEraseShared() { wdtDisableClk(); mpuAllowRamExecution(true); mpuAllowRomWrite(true); (void)BL.blEraseShared(BL_FLASH_KEY1, BL_FLASH_KEY2); mpuAllowRomWrite(false); mpuAllowRamExecution(false); wdtEnableClk(); return true; } bool osWriteShared(void *dest, const void *src, uint32_t len) { bool ret; mpuAllowRamExecution(true); mpuAllowRomWrite(true); ret = BL.blProgramShared(dest, src, len, BL_FLASH_KEY1, BL_FLASH_KEY2); mpuAllowRomWrite(false); mpuAllowRamExecution(false); if (!ret) osLog(LOG_ERROR, "osWriteShared: blProgramShared return false\n"); return ret; } struct AppHdr *osAppSegmentCreate(uint32_t size) { struct SegmentIterator it; const struct Segment *storageSeg = NULL; struct AppHdr *app; osSegmentIteratorInit(&it); while (osSegmentIteratorNext(&it)) { if (osSegmentGetState(it.seg) == SEG_ST_EMPTY) { storageSeg = it.seg; break; } } if (!storageSeg || osSegmentSizeGetNext(storageSeg, size) > it.sharedEnd) return NULL; app = osSegmentGetData(storageSeg); osAppSegmentSetState(app, SEG_ST_RESERVED); return app; } bool osAppSegmentClose(struct AppHdr *app, uint32_t segDataSize, uint32_t segState) { struct Segment seg; // this is enough for holding padding to uint32_t and the footer uint8_t footer[sizeof(uint32_t) + FOOTER_SIZE]; int footerLen; bool ret; uint32_t totalSize; uint8_t *start = platGetSharedAreaInfo(&totalSize); uint8_t *end = start + totalSize; int32_t fullSize = segDataSize + sizeof(seg); // without footer or padding struct Segment *storageSeg = osGetSegment(app); // sanity check if (segDataSize >= SEG_SIZE_MAX) return false; // physical limits check if (osSegmentSizeAlignedWithFooter(segDataSize) + sizeof(struct Segment) > totalSize) return false; // available space check: we could truncate size, instead of disallowing it, // but we know that we performed validation on the size before, in *Create call, // and it was fine, so this must be a programming error, and so we fail. // on a side note: size may grow or shrink compared to original estimate. // typically it shrinks, since we skip some header info and padding, as well // as signature blocks, but it is possible that at some point we may produce // more data for some reason. At that time the logic here may need to change if (osSegmentSizeGetNext(storageSeg, segDataSize) > (struct Segment*)end) return false; seg.state = segState; osSegmentSetSize(&seg, segDataSize); ret = osWriteShared((uint8_t*)storageSeg, (uint8_t*)&seg, sizeof(seg)); footerLen = (-fullSize) & 3; memset(footer, 0x00, footerLen); wdtDisableClk(); struct SegmentFooter segFooter = { .crc = ~soft_crc32(storageSeg, fullSize, ~0), }; wdtEnableClk(); memcpy(&footer[footerLen], &segFooter, sizeof(segFooter)); footerLen += sizeof(segFooter); if (ret && footerLen) ret = osWriteShared((uint8_t*)storageSeg + fullSize, footer, footerLen); return ret; } bool osAppWipeData(struct AppHdr *app) { struct Segment *seg = osGetSegment(app); int32_t size = osSegmentGetSize(seg); uint8_t *p = (uint8_t*)app; uint32_t state = osSegmentGetState(seg); uint8_t buf[256]; bool done = true; if (!seg || size == SEG_SIZE_INVALID || state == SEG_ST_EMPTY) { osLog(LOG_ERROR, "%s: can't erase segment: app=%p; seg=%p" "; size=%" PRIu32 "; state=%" PRIu32 "\n", __func__, app, seg, size, state); return false; } size = osSegmentSizeAlignedWithFooter(size); memset(buf, 0, sizeof(buf)); while (size > 0) { uint32_t flashSz = size > sizeof(buf) ? sizeof(buf) : size; // keep trying to zero-out stuff even in case of intermittent failures. // flash write may occasionally fail on some byte, but it is not good enough // reason to not rewrite other bytes bool res = osWriteShared(p, buf, flashSz); done = done && res; size -= flashSz; p += flashSz; } return done; } static inline bool osAppIsValid(const struct AppHdr *app) { return app->hdr.magic == APP_HDR_MAGIC && app->hdr.fwVer == APP_HDR_VER_CUR && (app->hdr.fwFlags & FL_APP_HDR_APPLICATION) != 0 && app->hdr.payInfoType == LAYOUT_APP; } static bool osExtAppIsValid(const struct AppHdr *app, uint32_t len) { return osAppIsValid(app) && len >= sizeof(*app) && osAppSegmentGetState(app) == SEG_ST_VALID && osAppSegmentCalcCrcResidue(app) == CRC_RESIDUE && !(app->hdr.fwFlags & FL_APP_HDR_INTERNAL); } static bool osIntAppIsValid(const struct AppHdr *app) { return osAppIsValid(app) && osAppSegmentGetState(app) == SEG_STATE_INVALID && (app->hdr.fwFlags & FL_APP_HDR_INTERNAL) != 0; } static inline bool osExtAppErase(const struct AppHdr *app) { return osAppSegmentSetState(app, SEG_ST_ERASED); } static struct Task *osLoadApp(const struct AppHdr *app) { struct Task *task; task = osAllocTask(); if (!task) { osLog(LOG_WARN, "External app id %016" PRIX64 " @ %p cannot be used as too many apps already exist.\n", app->hdr.appId, app); return NULL; } task->app = app; bool done = (app->hdr.fwFlags & FL_APP_HDR_INTERNAL) ? cpuInternalAppLoad(task->app, &task->platInfo) : cpuAppLoad(task->app, &task->platInfo); if (!done) { osLog(LOG_WARN, "App @ %p ID %016" PRIX64 " failed to load\n", app, app->hdr.appId); osFreeTask(task); task = NULL; } return task; } static void osUnloadApp(struct Task *task) { // this is called on task that has stopped running, or had never run cpuAppUnload(task->app, &task->platInfo); osFreeTask(task); } static bool osStartApp(const struct AppHdr *app) { bool done = false; struct Task *task; if ((task = osLoadApp(app)) != NULL) { task->subbedEvtListSz = MAX_EMBEDDED_EVT_SUBS; task->subbedEvents = task->subbedEventsInt; osTaskMakeNewTid(task); // print external NanoApp info to facilitate NanoApp debugging if (!(task->app->hdr.fwFlags & FL_APP_HDR_INTERNAL)) osLog(LOG_INFO, "loaded app ID 0x%" PRIx64 " at flash base 0x%" PRIxPTR " ram base 0x%" PRIxPTR "; TID %04" PRIX16 "\n", task->app->hdr.appId, (uintptr_t) task->app, (uintptr_t) task->platInfo.data, task->tid); done = osTaskInit(task); if (!done) { osLog(LOG_WARN, "App @ %p ID %016" PRIX64 " failed to init\n", task->app, task->app->hdr.appId); osUnloadApp(task); } else { osAddTask(task); (void)osEnqueueEvt(EVT_APP_BEGIN, task, NULL); } } return done; } static bool osStopTask(struct Task *task, bool abort) { struct Task *preempted; if (!task) return false; if (osTaskTestFlags(task, FL_TASK_STOPPED)) return true; preempted = osSetCurrentTask(mSystemTask); osRemoveTask(task); osTaskClrSetFlags(task, 0, FL_TASK_STOPPED); if (abort) osTaskClrSetFlags(task, 0, FL_TASK_ABORTED); else if (osTaskGetIoCount(task)) osTaskHandle(task, EVT_APP_STOP, OS_SYSTEM_TID, NULL); osEnqueueEvt(EVT_APP_END, task, NULL); osSetCurrentTask(preempted); return true; } void osTaskAbort(struct Task *task) { osStopTask(task, true); } static bool matchAutoStart(const void *cookie, const struct AppHdr *app) { bool match = (bool)cookie; if (app->hdr.fwFlags & FL_APP_HDR_CHRE) { if (app->hdr.chreApiMajor == 0xFF && app->hdr.chreApiMinor == 0xFF) return match; else if ((app->hdr.chreApiMajor < 0x01) || (app->hdr.chreApiMajor == 0x01 && app->hdr.chreApiMinor < 0x01)) return match; else return !match; } else { return match; } } static bool matchAppId(const void *data, const struct AppHdr *app) { uint64_t appId, vendor, seqId, curAppId; memcpy(&appId, data, sizeof(appId)); vendor = APP_ID_GET_VENDOR(appId); seqId = APP_ID_GET_SEQ_ID(appId); curAppId = app->hdr.appId; if ((vendor == APP_VENDOR_ANY || vendor == APP_ID_GET_VENDOR(curAppId)) && (seqId == APP_SEQ_ID_ANY || seqId == APP_ID_GET_SEQ_ID(curAppId))) { return true; } else { return false; } } static bool osExtAppFind(struct SegmentIterator *it, appMatchFunc func, const void *data) { const struct AppHdr *app; const struct Segment *seg; while (osSegmentIteratorNext(it)) { seg = it->seg; if (!seg) break; if (seg->state == SEG_ST_EMPTY) break; if (seg->state != SEG_ST_VALID) continue; app = osSegmentGetData(seg); if (func(data, app)) return true; } return false; } static uint32_t osExtAppStopEraseApps(appMatchFunc func, const void *data, bool doErase) { const struct AppHdr *app; int32_t len; struct SegmentIterator it; uint32_t stopCount = 0; uint32_t eraseCount = 0; uint32_t appCount = 0; uint32_t taskCount = 0; struct MgmtStatus stat = { .value = 0 }; struct Task *task; osSegmentIteratorInit(&it); while (osExtAppFind(&it, func, data)) { app = osSegmentGetData(it.seg); len = osSegmentGetSize(it.seg); if (!osExtAppIsValid(app, len)) continue; appCount++; /* it is safe to erase a running app; * erase merely sets a flag in the header, * and app keeps running until it is stopped */ if (doErase && osExtAppErase(app)) eraseCount++; task = osTaskFindByAppID(app->hdr.appId); if (task) { taskCount++; if (osStopTask(task, false)) stopCount++; } } SET_COUNTER(stat.app, appCount); SET_COUNTER(stat.task, taskCount); SET_COUNTER(stat.op, stopCount); SET_COUNTER(stat.erase, eraseCount); return stat.value; } uint32_t osExtAppStopAppsByAppId(uint64_t appId) { return osExtAppStopEraseApps(matchAppId, &appId, false); } uint32_t osExtAppEraseAppsByAppId(uint64_t appId) { return osExtAppStopEraseApps(matchAppId, &appId, true); } static void osScanExternal() { struct SegmentIterator it; osSegmentIteratorInit(&it); while (osSegmentIteratorNext(&it)) { switch (osSegmentGetState(it.seg)) { case SEG_ST_EMPTY: // everything looks good osLog(LOG_INFO, "External area is good\n"); return; case SEG_ST_ERASED: case SEG_ST_VALID: // this is valid stuff, ignore break; case SEG_ST_RESERVED: default: // something is wrong: erase everything osLog(LOG_ERROR, "External area is damaged. Erasing\n"); osEraseShared(); return; } } } static uint32_t osExtAppStartApps(appMatchFunc func, void *data) { const struct AppHdr *app; int32_t len; struct SegmentIterator it; struct SegmentIterator checkIt; uint32_t startCount = 0; uint32_t eraseCount = 0; uint32_t appCount = 0; uint32_t taskCount = 0; struct MgmtStatus stat = { .value = 0 }; osScanExternal(); osSegmentIteratorInit(&it); while (osExtAppFind(&it, func, data)) { app = osSegmentGetData(it.seg); len = osSegmentGetSize(it.seg); // skip erased or malformed apps if (!osExtAppIsValid(app, len)) continue; appCount++; checkIt = it; // find the most recent copy while (osExtAppFind(&checkIt, matchAppId, &app->hdr.appId)) { if (osExtAppErase(app)) // erase the old one, so we skip it next time eraseCount++; app = osSegmentGetData(checkIt.seg); } if (osTaskFindByAppID(app->hdr.appId)) { // this either the most recent external app with the same ID, // or internal app with the same id; in both cases we do nothing taskCount++; continue; } if (osStartApp(app)) startCount++; } SET_COUNTER(stat.app, appCount); SET_COUNTER(stat.task, taskCount); SET_COUNTER(stat.op, startCount); SET_COUNTER(stat.erase, eraseCount); return stat.value; } uint32_t osExtAppStartAppsByAppId(uint64_t appId) { return osExtAppStartApps(matchAppId, &appId); } static void osStartTasks(void) { const struct AppHdr *app; uint32_t i, nApps; struct Task* task; uint32_t status = 0; uint32_t taskCnt = 0; osLog(LOG_DEBUG, "Initializing task pool...\n"); list_init(&mTasks); list_init(&mFreeTasks); for (i = 0; i < MAX_TASKS; ++i) { task = &mTaskPool.data[i]; list_init(&task->list); osFreeTask(task); } mSystemTask = osAllocTask(); // this is a dummy task; holder of TID 0; all system code will run with TID 0 osSetCurrentTask(mSystemTask); osLog(LOG_DEBUG, "System task is: %p\n", mSystemTask); /* first enum all internal apps, making sure to check for dupes */ osLog(LOG_DEBUG, "Starting internal apps...\n"); for (i = 0, app = platGetInternalAppList(&nApps); i < nApps; i++, app++) { if (!osIntAppIsValid(app)) { osLog(LOG_WARN, "Invalid internal app @ %p ID %016" PRIX64 "header version: %" PRIu16 "\n", app, app->hdr.appId, app->hdr.fwVer); continue; } if (!(app->hdr.fwFlags & FL_APP_HDR_INTERNAL)) { osLog(LOG_WARN, "Internal app is not marked: [%p]: flags: 0x%04" PRIX16 "; ID: %016" PRIX64 "; ignored\n", app, app->hdr.fwFlags, app->hdr.appId); continue; } if ((task = osTaskFindByAppID(app->hdr.appId))) { osLog(LOG_WARN, "Internal app ID %016" PRIX64 "@ %p attempting to update internal app @ %p; app @%p ignored.\n", app->hdr.appId, app, task->app, app); continue; } if (osStartApp(app)) taskCnt++; } osLog(LOG_DEBUG, "Starting external apps...\n"); status = osExtAppStartApps(matchAutoStart, (void *)true); osLog(LOG_DEBUG, "Started %" PRIu32 " internal apps; EXT status: %08" PRIX32 "\n", taskCnt, status); } static void osInternalEvtHandle(uint32_t evtType, void *evtData) { union SeosInternalSlabData *da = (union SeosInternalSlabData*)evtData; struct Task *task, *ssTask; uint32_t i, j; uint16_t tid = EVENT_GET_ORIGIN(evtType); uint16_t evt = EVENT_GET_EVENT(evtType), newEvt; struct Task *srcTask = osTaskFindByTid(tid); struct Task *preempted = osSetCurrentTask(srcTask); struct AppEventStartStop ssMsg; switch (evt) { case EVT_SUBSCRIBE_TO_EVT: case EVT_UNSUBSCRIBE_TO_EVT: /* get task */ task = osTaskFindByTid(da->evtSub.tid); if (!task) break; for (j = 0; j < da->evtSub.numEvts; j++) { /* find if subscribed to this evt */ for (i = 0; i < task->subbedEvtCount && task->subbedEvents[i] != da->evtSub.evts[j]; i++); /* if unsub & found -> unsub */ if (evt == EVT_UNSUBSCRIBE_TO_EVT && i != task->subbedEvtCount) task->subbedEvents[i] = task->subbedEvents[--task->subbedEvtCount]; /* if sub & not found -> sub */ else if (evt == EVT_SUBSCRIBE_TO_EVT && i == task->subbedEvtCount) { if (task->subbedEvtListSz == task->subbedEvtCount) { /* enlarge the list */ uint32_t newSz = (task->subbedEvtListSz * 3 + 1) / 2; uint32_t *newList = heapAlloc(sizeof(uint32_t[newSz])); /* grow by 50% */ if (newList) { memcpy(newList, task->subbedEvents, sizeof(uint32_t[task->subbedEvtListSz])); if (task->subbedEvents != task->subbedEventsInt) heapFree(task->subbedEvents); task->subbedEvents = newList; task->subbedEvtListSz = newSz; } } if (task->subbedEvtListSz > task->subbedEvtCount) { /* have space ? */ task->subbedEvents[task->subbedEvtCount++] = da->evtSub.evts[j]; } } } break; case EVT_APP_BEGIN: case EVT_APP_END: ssTask = evtData; ssMsg.appId = ssTask->app->hdr.appId; ssMsg.version = ssTask->app->hdr.appVer; ssMsg.tid = ssTask->tid; if (evt == EVT_APP_BEGIN) { newEvt = EVT_APP_STARTED; } else { newEvt = EVT_APP_STOPPED; osTaskEnd(ssTask); osUnloadApp(ssTask); } /* send this event to all tasks who want it */ for_each_task(&mTasks, task) { if (task != ssTask) { for (i = 0; i < task->subbedEvtCount; i++) { if (task->subbedEvents[i] == newEvt) { osTaskHandle(task, newEvt, OS_SYSTEM_TID, &ssMsg); break; } } } } break; case EVT_DEFERRED_CALLBACK: da->deferred.callback(da->deferred.cookie); break; case EVT_PRIVATE_EVT: task = osTaskFindByTid(da->privateEvt.toTid); evtType = da->privateEvt.evtType & EVT_MASK; evtData = da->privateEvt.evtData; if (task) { //private events cannot be retained TaggedPtr *tmp = mCurEvtEventFreeingInfo; mCurEvtEventFreeingInfo = NULL; osTaskHandle(task, evtType, da->privateEvt.fromTid, da->privateEvt.evtData); mCurEvtEventFreeingInfo = tmp; } break; } osSetCurrentTask(preempted); } void abort(void) { /* this is necessary for va_* funcs... */ osLog(LOG_ERROR, "Abort called"); while(1); } bool osRetainCurrentEvent(TaggedPtr *evtFreeingInfoP) { if (!mCurEvtEventFreeingInfo) return false; *evtFreeingInfoP = *mCurEvtEventFreeingInfo; mCurEvtEventFreeingInfo = NULL; return true; } void osFreeRetainedEvent(uint32_t evtType, void *evtData, TaggedPtr *evtFreeingInfoP) { //TODO: figure the way to calculate src tid here to pass to handleEventFreeing handleEventFreeing(evtType, evtData, *evtFreeingInfoP); } void osMainInit(void) { cpuInit(); cpuIntsOff(); osInit(); timInit(); sensorsInit(); syscallInit(); osApiExport(mMiscInternalThingsSlab); osChreApiExport(); apIntInit(); cpuIntsOn(); wdtInit(); osStartTasks(); //broadcast app start to all already-loaded apps (void)osEnqueueEvt(EVT_APP_START, NULL, NULL); } void osMainDequeueLoop(void) { TaggedPtr evtFreeingInfo; uint32_t evtType, j; void *evtData; struct Task *task; uint16_t tid, evt; /* get an event */ if (!evtQueueDequeue(mEvtsInternal, &evtType, &evtData, &evtFreeingInfo, true)) return; /* by default we free them when we're done with them */ mCurEvtEventFreeingInfo = &evtFreeingInfo; tid = EVENT_GET_ORIGIN(evtType); evt = EVENT_GET_EVENT(evtType); if (evt < EVT_NO_FIRST_USER_EVENT) { /* handle deferred actions and other reserved events here */ osInternalEvtHandle(evtType, evtData); } else { /* send this event to all tasks who want it */ for_each_task(&mTasks, task) { for (j = 0; j < task->subbedEvtCount; j++) { if (task->subbedEvents[j] == evt) { osTaskHandle(task, evt, tid, evtData); break; } } } } /* free it */ if (mCurEvtEventFreeingInfo) handleEventFreeing(evtType, evtData, evtFreeingInfo); /* avoid some possible errors */ mCurEvtEventFreeingInfo = NULL; } void __attribute__((noreturn)) osMain(void) { osMainInit(); while (true) { osMainDequeueLoop(); platPeriodic(); } } static void osDeferredActionFreeF(void* event) { slabAllocatorFree(mMiscInternalThingsSlab, event); } static bool osEventsSubscribeUnsubscribeV(bool sub, uint32_t numEvts, va_list ap) { struct Task *task = osGetCurrentTask(); union SeosInternalSlabData *act; int i; if (!sub && osTaskTestFlags(task, FL_TASK_STOPPED)) // stopping, so this is a no-op return true; if (numEvts > MAX_EVT_SUB_CNT) return false; act = slabAllocatorAlloc(mMiscInternalThingsSlab); if (!act) return false; act->evtSub.tid = task->tid; act->evtSub.numEvts = numEvts; for (i = 0; i < numEvts; i++) act->evtSub.evts[i] = va_arg(ap, uint32_t); return osEnqueueEvtOrFree(sub ? EVT_SUBSCRIBE_TO_EVT : EVT_UNSUBSCRIBE_TO_EVT, act, osDeferredActionFreeF); } static bool osEventsSubscribeUnsubscribe(bool sub, uint32_t numEvts, ...) { bool ret; va_list ap; va_start(ap, numEvts); ret = osEventsSubscribeUnsubscribeV(sub, numEvts, ap); va_end(ap); return ret; } bool osEventSubscribe(uint32_t tid, uint32_t evtType) { (void)tid; return osEventsSubscribeUnsubscribe(true, 1, evtType); } bool osEventUnsubscribe(uint32_t tid, uint32_t evtType) { (void)tid; return osEventsSubscribeUnsubscribe(false, 1, evtType); } bool osEventsSubscribe(uint32_t numEvts, ...) { bool ret; va_list ap; va_start(ap, numEvts); ret = osEventsSubscribeUnsubscribeV(true, numEvts, ap); va_end(ap); return ret; } bool osEventsUnsubscribe(uint32_t numEvts, ...) { bool ret; va_list ap; va_start(ap, numEvts); ret = osEventsSubscribeUnsubscribeV(false, numEvts, ap); va_end(ap); return ret; } static bool osEnqueueEvtCommon(uint32_t evt, void *evtData, TaggedPtr evtFreeInfo, bool urgent) { struct Task *task = osGetCurrentTask(); uint32_t evtType = EVENT_WITH_ORIGIN(evt, osGetCurrentTid()); osTaskAddIoCount(task, 1); if (osTaskTestFlags(task, FL_TASK_STOPPED) || !evtQueueEnqueue(mEvtsInternal, evtType, evtData, evtFreeInfo, urgent)) { osTaskAddIoCount(task, -1); return false; } return true; } void osRemovePendingEvents(bool (*match)(uint32_t evtType, const void *evtData, void *context), void *context) { evtQueueRemoveAllMatching(mEvtsInternal, match, context); } bool osEnqueueEvt(uint32_t evtType, void *evtData, EventFreeF evtFreeF) { return osEnqueueEvtCommon(evtType, evtData, taggedPtrMakeFromPtr(evtFreeF), false); } bool osEnqueueEvtOrFree(uint32_t evtType, void *evtData, EventFreeF evtFreeF) { bool success = osEnqueueEvt(evtType, evtData, evtFreeF); if (!success && evtFreeF) evtFreeF(evtData); return success; } bool osEnqueueEvtAsApp(uint32_t evtType, void *evtData, bool freeData) { // compatibility with existing external apps if (evtType & EVENT_TYPE_BIT_DISCARDABLE_COMPAT) evtType |= EVENT_TYPE_BIT_DISCARDABLE; return osEnqueueEvtCommon(evtType, evtData, freeData ? taggedPtrMakeFromUint(osGetCurrentTid()) : taggedPtrMakeFromPtr(NULL), false); } bool osDefer(OsDeferCbkF callback, void *cookie, bool urgent) { union SeosInternalSlabData *act = slabAllocatorAlloc(mMiscInternalThingsSlab); if (!act) return false; act->deferred.callback = callback; act->deferred.cookie = cookie; if (osEnqueueEvtCommon(EVT_DEFERRED_CALLBACK, act, taggedPtrMakeFromPtr(osDeferredActionFreeF), urgent)) return true; slabAllocatorFree(mMiscInternalThingsSlab, act); return false; } static bool osEnqueuePrivateEvtEx(uint32_t evtType, void *evtData, TaggedPtr evtFreeInfo, uint32_t toTid) { union SeosInternalSlabData *act = slabAllocatorAlloc(mMiscInternalThingsSlab); bool result; if (!act) { osLog(LOG_ERROR, "[seos] ERROR: osEnqueuePrivateEvtEx: call to slabAllocatorAlloc() failed\n"); return false; } struct Task *task = osGetCurrentTask(); osTaskAddIoCount(task, 1); act->privateEvt.evtType = evtType; act->privateEvt.evtData = evtData; act->privateEvt.evtFreeInfo = evtFreeInfo; act->privateEvt.fromTid = task->tid; act->privateEvt.toTid = toTid; osSetCurrentTask(mSystemTask); result = osEnqueueEvtOrFree(EVT_PRIVATE_EVT, act, osPrivateEvtFreeF); osSetCurrentTask(task); return result; } // only called to send events for CHRE apps bool osEnqueuePrivateEvtNew(uint16_t evtType, void *evtData, void (*evtFreeCallback)(uint16_t evtType, void *evtData), uint32_t toTid) { if (!osEnqueuePrivateEvtEx(evtType | (EVT_PRIVATE_CLASS_CHRE << 16), evtData, taggedPtrMakeFromPtr(evtFreeCallback), toTid)) { osChreFreeEvent(osGetCurrentTid(), evtFreeCallback, evtType, evtData); return false; } return true; } bool osEnqueuePrivateEvt(uint32_t evtType, void *evtData, EventFreeF evtFreeF, uint32_t toTid) { return osEnqueuePrivateEvtEx(evtType & EVT_MASK, evtData, taggedPtrMakeFromPtr(evtFreeF), toTid); } bool osEnqueuePrivateEvtAsApp(uint32_t evtType, void *evtData, uint32_t toTid) { return osEnqueuePrivateEvtEx(evtType & EVT_MASK, evtData, taggedPtrMakeFromUint(osGetCurrentTid()), toTid); } bool osTidById(const uint64_t *appId, uint32_t *tid) { struct Task *task; for_each_task(&mTasks, task) { if (task->app && !memcmp(&task->app->hdr.appId, appId, sizeof(*appId))) { *tid = task->tid; return true; } } return false; } bool osAppInfoById(uint64_t appId, uint32_t *appIdx, uint32_t *appVer, uint32_t *appSize) { uint32_t i = 0; struct Task *task; for_each_task(&mTasks, task) { const struct AppHdr *app = task->app; if (app && app->hdr.appId == appId) { *appIdx = i; *appVer = app->hdr.appVer; *appSize = app->sect.rel_end; return true; } i++; } return false; } bool osAppInfoByIndex(uint32_t appIdx, uint64_t *appId, uint32_t *appVer, uint32_t *appSize) { struct Task *task; int i = 0; for_each_task(&mTasks, task) { if (i != appIdx) { ++i; } else { const struct AppHdr *app = task->app; *appId = app->hdr.appId; *appVer = app->hdr.appVer; *appSize = app->sect.rel_end; return true; } } return false; } bool osExtAppInfoByIndex(uint32_t appIdx, uint64_t *appId, uint32_t *appVer, uint32_t *appSize) { struct Task *task; int i = 0; for_each_task(&mTasks, task) { const struct AppHdr *app = task->app; if (!(app->hdr.fwFlags & FL_APP_HDR_INTERNAL)) { if (i != appIdx) { ++i; } else { *appId = app->hdr.appId; *appVer = app->hdr.appVer; *appSize = app->sect.rel_end; return true; } } } return false; } void osLogv(char clevel, uint32_t flags, const char *str, va_list vl) { void *userData = platLogAllocUserData(); platLogPutcharF(userData, clevel); cvprintf(platLogPutcharF, flags, userData, str, vl); platLogFlush(userData); } void osLog(enum LogLevel level, const char *str, ...) { va_list vl; va_start(vl, str); osLogv((char)level, 0, str, vl); va_end(vl); } //Google's public key for Google's apps' signing const uint8_t __attribute__ ((section (".pubkeys"))) _RSA_KEY_GOOGLE[] = { 0xd9, 0xcd, 0x83, 0xae, 0xb5, 0x9e, 0xe4, 0x63, 0xf1, 0x4c, 0x26, 0x6a, 0x1c, 0xeb, 0x4c, 0x12, 0x5b, 0xa6, 0x71, 0x7f, 0xa2, 0x4e, 0x7b, 0xa2, 0xee, 0x02, 0x86, 0xfc, 0x0d, 0x31, 0x26, 0x74, 0x1e, 0x9c, 0x41, 0x43, 0xba, 0x16, 0xe9, 0x23, 0x4d, 0xfc, 0xc4, 0xca, 0xcc, 0xd5, 0x27, 0x2f, 0x16, 0x4c, 0xe2, 0x85, 0x39, 0xb3, 0x0b, 0xcb, 0x73, 0xb6, 0x56, 0xc2, 0x98, 0x83, 0xf6, 0xfa, 0x7a, 0x6e, 0xa0, 0x9a, 0xcc, 0x83, 0x97, 0x9d, 0xde, 0x89, 0xb2, 0xa3, 0x05, 0x46, 0x0c, 0x12, 0xae, 0x01, 0xf8, 0x0c, 0xf5, 0x39, 0x32, 0xe5, 0x94, 0xb9, 0xa0, 0x8f, 0x19, 0xe4, 0x39, 0x54, 0xad, 0xdb, 0x81, 0x60, 0x74, 0x63, 0xd5, 0x80, 0x3b, 0xd2, 0x88, 0xf4, 0xcb, 0x6b, 0x47, 0x28, 0x80, 0xb0, 0xd1, 0x89, 0x6d, 0xd9, 0x62, 0x88, 0x81, 0xd6, 0xc0, 0x13, 0x88, 0x91, 0xfb, 0x7d, 0xa3, 0x7f, 0xa5, 0x40, 0x12, 0xfb, 0x77, 0x77, 0x4c, 0x98, 0xe4, 0xd3, 0x62, 0x39, 0xcc, 0x63, 0x34, 0x76, 0xb9, 0x12, 0x67, 0xfe, 0x83, 0x23, 0x5d, 0x40, 0x6b, 0x77, 0x93, 0xd6, 0xc0, 0x86, 0x6c, 0x03, 0x14, 0xdf, 0x78, 0x2d, 0xe0, 0x9b, 0x5e, 0x05, 0xf0, 0x93, 0xbd, 0x03, 0x1d, 0x17, 0x56, 0x88, 0x58, 0x25, 0xa6, 0xae, 0x63, 0xd2, 0x01, 0x43, 0xbb, 0x7e, 0x7a, 0xa5, 0x62, 0xdf, 0x8a, 0x31, 0xbd, 0x24, 0x1b, 0x1b, 0xeb, 0xfe, 0xdf, 0xd1, 0x31, 0x61, 0x4a, 0xfa, 0xdd, 0x6e, 0x62, 0x0c, 0xa9, 0xcd, 0x08, 0x0c, 0xa1, 0x1b, 0xe7, 0xf2, 0xed, 0x36, 0x22, 0xd0, 0x5d, 0x80, 0x78, 0xeb, 0x6f, 0x5a, 0x58, 0x18, 0xb5, 0xaf, 0x82, 0x77, 0x4c, 0x95, 0xce, 0xc6, 0x4d, 0xda, 0xca, 0xef, 0x68, 0xa6, 0x6d, 0x71, 0x4d, 0xf1, 0x14, 0xaf, 0x68, 0x25, 0xb8, 0xf3, 0xff, 0xbe, }; #ifdef DEBUG //debug key whose privatekey is checked in as misc/debug.privkey const uint8_t __attribute__ ((section (".pubkeys"))) _RSA_KEY_GOOGLE_DEBUG[] = { 0x2d, 0xff, 0xa6, 0xb5, 0x65, 0x87, 0xbe, 0x61, 0xd1, 0xe1, 0x67, 0x10, 0xa1, 0x9b, 0xc6, 0xca, 0xc8, 0xb1, 0xf0, 0xaa, 0x88, 0x60, 0x9f, 0xa1, 0x00, 0xa1, 0x41, 0x9a, 0xd8, 0xb4, 0xd1, 0x74, 0x9f, 0x23, 0x28, 0x0d, 0xc2, 0xc4, 0x37, 0x15, 0xb1, 0x4a, 0x80, 0xca, 0xab, 0xb9, 0xba, 0x09, 0x7d, 0xf8, 0x44, 0xd6, 0xa2, 0x72, 0x28, 0x12, 0x91, 0xf6, 0xa5, 0xea, 0xbd, 0xf8, 0x81, 0x6b, 0xd2, 0x3c, 0x50, 0xa2, 0xc6, 0x19, 0x54, 0x48, 0x45, 0x8d, 0x92, 0xac, 0x01, 0xda, 0x14, 0x32, 0xdb, 0x05, 0x82, 0x06, 0x30, 0x25, 0x09, 0x7f, 0x5a, 0xbb, 0x86, 0x64, 0x70, 0x98, 0x64, 0x1e, 0xe6, 0xca, 0x1d, 0xc1, 0xcb, 0xb6, 0x23, 0xd2, 0x62, 0x00, 0x46, 0x97, 0xd5, 0xcc, 0xe6, 0x36, 0x72, 0xec, 0x2e, 0x43, 0x1f, 0x0a, 0xaf, 0xf2, 0x51, 0xe1, 0xcd, 0xd2, 0x98, 0x5d, 0x7b, 0x64, 0xeb, 0xd1, 0x35, 0x4d, 0x59, 0x13, 0x82, 0x6c, 0xbd, 0xc4, 0xa2, 0xfc, 0xad, 0x64, 0x73, 0xe2, 0x71, 0xb5, 0xf4, 0x45, 0x53, 0x6b, 0xc3, 0x56, 0xb9, 0x8b, 0x3d, 0xeb, 0x00, 0x48, 0x6e, 0x29, 0xb1, 0xb4, 0x8e, 0x2e, 0x43, 0x39, 0xef, 0x45, 0xa0, 0xb8, 0x8b, 0x5f, 0x80, 0xb5, 0x0c, 0xc3, 0x03, 0xe3, 0xda, 0x51, 0xdc, 0xec, 0x80, 0x2c, 0x0c, 0xdc, 0xe2, 0x71, 0x0a, 0x14, 0x4f, 0x2c, 0x22, 0x2b, 0x0e, 0xd1, 0x8b, 0x8f, 0x93, 0xd2, 0xf3, 0xec, 0x3a, 0x5a, 0x1c, 0xba, 0x80, 0x54, 0x23, 0x7f, 0xb0, 0x54, 0x8b, 0xe3, 0x98, 0x22, 0xbb, 0x4b, 0xd0, 0x29, 0x5f, 0xce, 0xf2, 0xaa, 0x99, 0x89, 0xf2, 0xb7, 0x5d, 0x8d, 0xb2, 0x72, 0x0b, 0x52, 0x02, 0xb8, 0xa4, 0x37, 0xa0, 0x3b, 0xfe, 0x0a, 0xbc, 0xb3, 0xb3, 0xed, 0x8f, 0x8c, 0x42, 0x59, 0xbe, 0x4e, 0x31, 0xed, 0x11, 0x9b, }; #endif