| /* |
| * 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 <plat/inc/eeData.h> |
| #include <plat/inc/plat.h> |
| #include <plat/inc/bl.h> |
| #include <platform.h> |
| #include <hostIntf.h> |
| #include <inttypes.h> |
| #include <syscall.h> |
| #include <sensors.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <printf.h> |
| #include <eventQ.h> |
| #include <apInt.h> |
| #include <timer.h> |
| #include <osApi.h> |
| #include <seos.h> |
| #include <heap.h> |
| #include <slab.h> |
| #include <cpu.h> |
| #include <util.h> |
| #include <mpu.h> |
| #include <nanohubPacket.h> |
| #include <atomic.h> |
| |
| #include <nanohub/nanohub.h> |
| #include <nanohub/crc.h> |
| |
| #define NO_NODE (TaskIndex)(-1) |
| #define for_each_task(listHead, task) for (task = osTaskByIdx((listHead)->next); task; task = osTaskByIdx(task->list.next)) |
| #define MAKE_NEW_TID(task) task->tid = ((task->tid + TASK_TID_INCREMENT) & TASK_TID_COUNTER_MASK) | \ |
| (osTaskIndex(task) & TASK_TID_IDX_MASK); |
| #define TID_TO_TASK_IDX(tid) (tid & TASK_TID_IDX_MASK) |
| |
| #define FL_TASK_STOPPED 1 |
| |
| #define EVT_SUBSCRIBE_TO_EVT 0x00000000 |
| #define EVT_UNSUBSCRIBE_TO_EVT 0x00000001 |
| #define EVT_DEFERRED_CALLBACK 0x00000002 |
| #define EVT_PRIVATE_EVT 0x00000003 |
| |
| #define EVENT_WITH_ORIGIN(evt, origin) (((evt) & EVT_MASK) | ((origin) << (32 - TASK_TID_BITS))) |
| #define EVENT_GET_ORIGIN(evt) ((evt) >> (32 - TASK_TID_BITS)) |
| #define EVENT_GET_EVENT(evt) ((evt) & (EVT_MASK & ~EVENT_TYPE_BIT_DISCARDABLE)) |
| |
| /* |
| * Since locking is difficult to do right for adding/removing listeners and such |
| * since it can happen in interrupt context and not, and one such operation can |
| * interrupt another, and we do have a working event queue, we enqueue all the |
| * requests and then deal with them in the main code only when the event bubbles |
| * up to the front of the queue. This allows us to not need locks around the |
| * data structures. |
| */ |
| |
| SET_PACKED_STRUCT_MODE_ON |
| struct TaskList { |
| TaskIndex prev; |
| TaskIndex next; |
| } ATTRIBUTE_PACKED; |
| SET_PACKED_STRUCT_MODE_OFF |
| |
| struct Task { |
| /* App entry points */ |
| const struct AppHdr *app; |
| |
| /* per-platform app info */ |
| struct PlatAppInfo platInfo; |
| |
| /* for some basic number of subbed events, the array is stored directly here. after that, a heap chunk is used */ |
| uint32_t subbedEventsInt[MAX_EMBEDDED_EVT_SUBS]; |
| uint32_t *subbedEvents; /* NULL for invalid tasks */ |
| |
| struct TaskList list; |
| |
| /* task pointer will not change throughout task lifetime, |
| * however same task pointer may be reused for a new task; to eliminate the ambiguity, |
| * TID is maintained for each task such that new tasks will be guaranteed to receive different TID */ |
| uint16_t tid; |
| |
| uint8_t subbedEvtCount; |
| uint8_t subbedEvtListSz; |
| uint8_t flags; |
| uint8_t ioCount; |
| |
| }; |
| |
| struct TaskPool { |
| struct Task data[MAX_TASKS]; |
| }; |
| |
| union InternalThing { |
| struct { |
| uint32_t tid; |
| uint32_t evt; |
| } evtSub; |
| struct { |
| OsDeferCbkF callback; |
| void *cookie; |
| } deferred; |
| struct { |
| uint32_t evtType; |
| void *evtData; |
| TaggedPtr evtFreeInfo; |
| uint32_t toTid; |
| } privateEvt; |
| union OsApiSlabItem osApiItem; |
| }; |
| |
| 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; |
| } |
| |
| static inline struct Task *osGetCurrentTask() |
| { |
| return mCurrentTask; |
| } |
| |
| static 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; |
| } |
| |
| 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); |
| } |
| |
| static inline 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); |
| } |
| |
| static inline 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 inline void osTaskEnd(struct Task *task) |
| { |
| struct Task *preempted = osSetCurrentTask(task); |
| uint16_t tid = task->tid; |
| |
| cpuAppEnd(task->app, &task->platInfo); |
| |
| // task was supposed to release it's resources, |
| // but we do our cleanup anyway |
| osSetCurrentTask(mSystemTask); |
| platFreeResources(tid); // HW resources cleanup (IRQ, DMA etc) |
| sensorUnregisterAll(tid); |
| timTimerCancelAll(tid); |
| heapFreeAll(tid); |
| // NOTE: we don't need to unsubscribe from events |
| osSetCurrentTask(preempted); |
| } |
| |
| static inline void osTaskHandle(struct Task *task, uint32_t evtType, const void* evtData) |
| { |
| struct Task *preempted = osSetCurrentTask(task); |
| cpuAppHandle(task->app, &task->platInfo, evtType, evtData); |
| osSetCurrentTask(preempted); |
| } |
| |
| static void handleEventFreeing(uint32_t evtType, void *evtData, TaggedPtr evtFreeData) // watch out, this is synchronous |
| { |
| if ((taggedPtrIsPtr(evtFreeData) && !taggedPtrToPtr(evtFreeData)) || |
| (taggedPtrIsUint(evtFreeData) && !taggedPtrToUint(evtFreeData))) |
| return; |
| |
| if (taggedPtrIsPtr(evtFreeData)) |
| ((EventFreeF)taggedPtrToPtr(evtFreeData))(evtData); |
| else { |
| struct AppEventFreeData fd = {.evtType = evtType, .evtData = evtData}; |
| struct Task* task = osTaskFindByTid(taggedPtrToUint(evtFreeData)); |
| |
| if (!task) |
| osLog(LOG_ERROR, "EINCEPTION: Failed to find app to call app to free event sent to app(s).\n"); |
| else |
| osTaskHandle(task, EVT_APP_FREE_EVT_DATA, &fd); |
| } |
| } |
| |
| 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 InternalThing), alignof(union InternalThing), 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); |
| } |
| |
| 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() |
| { |
| mpuAllowRamExecution(true); |
| mpuAllowRomWrite(true); |
| (void)BL.blEraseShared(BL_FLASH_KEY1, BL_FLASH_KEY2); |
| mpuAllowRomWrite(false); |
| mpuAllowRamExecution(false); |
| 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); |
| |
| #ifdef SEGMENT_CRC_SUPPORT |
| struct SegmentFooter segFooter { |
| .crc = ~crc32(storageSeg, fullSize, ~0), |
| }; |
| memcpy(&footer[footerLen], &segFooter, sizeof(segFooter)); |
| footerLen += sizeof(segFooter); |
| #endif |
| |
| 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) |
| { |
| //TODO: when CRC support is ready, add CRC check here |
| return osAppIsValid(app) && |
| len >= sizeof(*app) && |
| osAppSegmentGetState(app) == SEG_ST_VALID && |
| !(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; |
| MAKE_NEW_TID(task); |
| |
| 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); |
| } |
| } |
| |
| return done; |
| } |
| |
| static bool osStopTask(struct Task *task) |
| { |
| if (!task) |
| return false; |
| |
| osTaskClrSetFlags(task, 0, FL_TASK_STOPPED); |
| osRemoveTask(task); |
| |
| if (osTaskGetIoCount(task)) { |
| osTaskHandle(task, EVT_APP_STOP, NULL); |
| osEnqueueEvtOrFree(EVT_APP_END, task, NULL); |
| } else { |
| osTaskEnd(task); |
| osUnloadApp(task); |
| } |
| |
| return true; |
| } |
| |
| static bool osExtAppFind(struct SegmentIterator *it, uint64_t appId) |
| { |
| uint64_t vendor = APP_ID_GET_VENDOR(appId); |
| uint64_t seqId = APP_ID_GET_SEQ_ID(appId); |
| uint64_t curAppId; |
| const struct AppHdr *app; |
| const struct Segment *seg; |
| |
| while (osSegmentIteratorNext(it)) { |
| seg = it->seg; |
| if (seg->state == SEG_ST_EMPTY) |
| break; |
| if (seg->state != SEG_ST_VALID) |
| continue; |
| app = osSegmentGetData(seg); |
| 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; |
| } |
| |
| return false; |
| } |
| |
| static uint32_t osExtAppStopEraseApps(uint64_t appId, bool doErase) |
| { |
| const struct AppHdr *app; |
| int32_t len; |
| struct Task *task; |
| struct SegmentIterator it; |
| uint32_t stopCount = 0; |
| uint32_t eraseCount = 0; |
| uint32_t appCount = 0; |
| uint32_t taskCount = 0; |
| struct MgmtStatus stat = { .value = 0 }; |
| |
| osSegmentIteratorInit(&it); |
| while (osExtAppFind(&it, appId)) { |
| app = osSegmentGetData(it.seg); |
| len = osSegmentGetSize(it.seg); |
| if (!osExtAppIsValid(app, len)) |
| continue; |
| appCount++; |
| task = osTaskFindByAppID(app->hdr.appId); |
| if (task) |
| taskCount++; |
| if (task && task->app == app) { |
| if (osStopTask(task)) |
| stopCount++; |
| else |
| continue; |
| if (doErase && osExtAppErase(app)) |
| eraseCount++; |
| } |
| } |
| 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 osExtAppStopApps(uint64_t appId) |
| { |
| return osExtAppStopEraseApps(appId, false); |
| } |
| |
| uint32_t osExtAppEraseApps(uint64_t appId) |
| { |
| return osExtAppStopEraseApps(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; |
| } |
| } |
| } |
| |
| uint32_t osExtAppStartApps(uint64_t appId) |
| { |
| 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, appId)) { |
| 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, 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; |
| } |
| |
| 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(APP_ID_ANY); |
| osLog(LOG_DEBUG, "Started %" PRIu32 " internal apps; EXT status: %08" PRIX32 "\n", taskCnt, status); |
| } |
| |
| static void osInternalEvtHandle(uint32_t evtType, void *evtData) |
| { |
| union InternalThing *da = (union InternalThing*)evtData; |
| struct Task *task; |
| uint32_t i; |
| |
| switch (evtType) { |
| case EVT_SUBSCRIBE_TO_EVT: |
| case EVT_UNSUBSCRIBE_TO_EVT: |
| /* get task */ |
| task = osTaskFindByTid(da->evtSub.tid); |
| if (!task) |
| break; |
| |
| /* find if subscribed to this evt */ |
| for (i = 0; i < task->subbedEvtCount && task->subbedEvents[i] != da->evtSub.evt; i++); |
| |
| /* if unsub & found -> unsub */ |
| if (evtType == EVT_UNSUBSCRIBE_TO_EVT && i != task->subbedEvtCount) |
| task->subbedEvents[i] = task->subbedEvents[--task->subbedEvtCount]; |
| /* if sub & not found -> sub */ |
| else if (evtType == 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.evt; |
| } |
| } |
| break; |
| |
| case EVT_APP_END: |
| task = evtData; |
| osTaskEnd(task); |
| osUnloadApp(task); |
| break; |
| |
| case EVT_DEFERRED_CALLBACK: |
| da->deferred.callback(da->deferred.cookie); |
| break; |
| |
| case EVT_PRIVATE_EVT: |
| task = osTaskFindByTid(da->privateEvt.toTid); |
| if (task) { |
| //private events cannot be retained |
| TaggedPtr *tmp = mCurEvtEventFreeingInfo; |
| mCurEvtEventFreeingInfo = NULL; |
| |
| osTaskHandle(task, da->privateEvt.evtType, da->privateEvt.evtData); |
| |
| mCurEvtEventFreeingInfo = tmp; |
| } |
| |
| handleEventFreeing(da->privateEvt.evtType, da->privateEvt.evtData, da->privateEvt.evtFreeInfo); |
| break; |
| } |
| } |
| |
| 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) |
| { |
| handleEventFreeing(evtType, evtData, *evtFreeingInfoP); |
| } |
| |
| void osMainInit(void) |
| { |
| cpuInit(); |
| cpuIntsOff(); |
| osInit(); |
| timInit(); |
| sensorsInit(); |
| syscallInit(); |
| osApiExport(mMiscInternalThingsSlab); |
| apIntInit(); |
| cpuIntsOn(); |
| 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; |
| |
| /* get an event */ |
| if (!evtQueueDequeue(mEvtsInternal, &evtType, &evtData, &evtFreeingInfo, true)) |
| return; |
| |
| evtType = EVENT_GET_EVENT(evtType); |
| tid = EVENT_GET_ORIGIN(evtType); |
| task = osTaskFindByTid(tid); |
| if (task) |
| osTaskAddIoCount(task, -1); |
| |
| /* by default we free them when we're done with them */ |
| mCurEvtEventFreeingInfo = &evtFreeingInfo; |
| |
| if (evtType < 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] == evtType) { |
| osTaskHandle(task, evtType, 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(); |
| } |
| } |
| |
| static void osDeferredActionFreeF(void* event) |
| { |
| slabAllocatorFree(mMiscInternalThingsSlab, event); |
| } |
| |
| static bool osEventSubscribeUnsubscribe(uint32_t tid, uint32_t evtType, bool sub) |
| { |
| union InternalThing *act = slabAllocatorAlloc(mMiscInternalThingsSlab); |
| |
| if (!act) |
| return false; |
| act->evtSub.evt = evtType; |
| act->evtSub.tid = tid; |
| |
| return osEnqueueEvtOrFree(sub ? EVT_SUBSCRIBE_TO_EVT : EVT_UNSUBSCRIBE_TO_EVT, act, osDeferredActionFreeF); |
| } |
| |
| bool osEventSubscribe(uint32_t tid, uint32_t evtType) |
| { |
| (void)tid; |
| return osEventSubscribeUnsubscribe(osGetCurrentTid(), evtType, true); |
| } |
| |
| bool osEventUnsubscribe(uint32_t tid, uint32_t evtType) |
| { |
| (void)tid; |
| return osEventSubscribeUnsubscribe(osGetCurrentTid(), evtType, false); |
| } |
| |
| static bool osEnqueueEvtCommon(uint32_t evtType, void *evtData, TaggedPtr evtFreeInfo) |
| { |
| struct Task *task = osGetCurrentTask(); |
| |
| if (osTaskTestFlags(task, FL_TASK_STOPPED)) { |
| handleEventFreeing(evtType, evtData, evtFreeInfo); |
| return true; |
| } |
| |
| evtType = EVENT_WITH_ORIGIN(evtType, osGetCurrentTid()); |
| osTaskAddIoCount(task, 1); |
| |
| if (evtQueueEnqueue(mEvtsInternal, evtType, evtData, evtFreeInfo, false)) |
| return true; |
| |
| osTaskAddIoCount(task, -1); |
| return false; |
| } |
| |
| bool osEnqueueEvt(uint32_t evtType, void *evtData, EventFreeF evtFreeF) |
| { |
| return osEnqueueEvtCommon(evtType, evtData, taggedPtrMakeFromPtr(evtFreeF)); |
| } |
| |
| 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, uint32_t fromAppTid) |
| { |
| // compatibility with existing external apps |
| if (evtType & EVENT_TYPE_BIT_DISCARDABLE_COMPAT) |
| evtType |= EVENT_TYPE_BIT_DISCARDABLE; |
| |
| (void)fromAppTid; |
| return osEnqueueEvtCommon(evtType, evtData, taggedPtrMakeFromUint(osGetCurrentTid())); |
| } |
| |
| bool osDefer(OsDeferCbkF callback, void *cookie, bool urgent) |
| { |
| union InternalThing *act = slabAllocatorAlloc(mMiscInternalThingsSlab); |
| if (!act) |
| return false; |
| |
| act->deferred.callback = callback; |
| act->deferred.cookie = cookie; |
| |
| if (evtQueueEnqueue(mEvtsInternal, 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 InternalThing *act = slabAllocatorAlloc(mMiscInternalThingsSlab); |
| if (!act) |
| return false; |
| |
| act->privateEvt.evtType = evtType; |
| act->privateEvt.evtData = evtData; |
| act->privateEvt.evtFreeInfo = evtFreeInfo; |
| act->privateEvt.toTid = toTid; |
| |
| return osEnqueueEvtOrFree(EVT_PRIVATE_EVT, act, osDeferredActionFreeF); |
| } |
| |
| bool osEnqueuePrivateEvt(uint32_t evtType, void *evtData, EventFreeF evtFreeF, uint32_t toTid) |
| { |
| return osEnqueuePrivateEvtEx(evtType, evtData, taggedPtrMakeFromPtr(evtFreeF), toTid); |
| } |
| |
| bool osEnqueuePrivateEvtAsApp(uint32_t evtType, void *evtData, uint32_t fromAppTid, uint32_t toTid) |
| { |
| (void)fromAppTid; |
| return osEnqueuePrivateEvtEx(evtType, evtData, taggedPtrMakeFromUint(osGetCurrentTid()), toTid); |
| } |
| |
| bool osTidById(uint64_t appId, uint32_t *tid) |
| { |
| struct Task *task; |
| |
| for_each_task(&mTasks, task) { |
| if (task->app && task->app->hdr.appId == 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; |
| } |
| |
| void osLogv(enum LogLevel level, const char *str, va_list vl) |
| { |
| void *userData = platLogAllocUserData(); |
| |
| platLogPutcharF(userData, level); |
| cvprintf(platLogPutcharF, userData, str, vl); |
| |
| platLogFlush(userData); |
| } |
| |
| void osLog(enum LogLevel level, const char *str, ...) |
| { |
| va_list vl; |
| |
| va_start(vl, str); |
| osLogv(level, 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 |