• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "chre/core/event_loop.h"
18 #include <cinttypes>
19 
20 #include "chre/core/event.h"
21 #include "chre/core/event_loop_manager.h"
22 #include "chre/core/nanoapp.h"
23 #include "chre/platform/assert.h"
24 #include "chre/platform/context.h"
25 #include "chre/platform/fatal_error.h"
26 #include "chre/platform/log.h"
27 #include "chre/platform/system_time.h"
28 #include "chre/util/conditional_lock_guard.h"
29 #include "chre/util/lock_guard.h"
30 #include "chre/util/system/debug_dump.h"
31 #include "chre/util/system/stats_container.h"
32 #include "chre/util/time.h"
33 #include "chre_api/chre/version.h"
34 
35 namespace chre {
36 
37 // Out of line declaration required for nonintegral static types
38 constexpr Nanoseconds EventLoop::kIntervalWakeupBucket;
39 
40 namespace {
41 
42 /**
43  * Populates a chreNanoappInfo structure using info from the given Nanoapp
44  * instance.
45  *
46  * @param app A potentially null pointer to the Nanoapp to read from
47  * @param info The structure to populate - should not be null, but this function
48  *        will handle that input
49  *
50  * @return true if neither app nor info were null, and info was populated
51  */
populateNanoappInfo(const Nanoapp * app,struct chreNanoappInfo * info)52 bool populateNanoappInfo(const Nanoapp *app, struct chreNanoappInfo *info) {
53   bool success = false;
54 
55   if (app != nullptr && info != nullptr) {
56     info->appId = app->getAppId();
57     info->version = app->getAppVersion();
58     info->instanceId = app->getInstanceId();
59     success = true;
60   }
61 
62   return success;
63 }
64 
65 }  // anonymous namespace
66 
findNanoappInstanceIdByAppId(uint64_t appId,uint16_t * instanceId) const67 bool EventLoop::findNanoappInstanceIdByAppId(uint64_t appId,
68                                              uint16_t *instanceId) const {
69   CHRE_ASSERT(instanceId != nullptr);
70   ConditionalLockGuard<Mutex> lock(mNanoappsLock, !inEventLoopThread());
71 
72   bool found = false;
73   for (const UniquePtr<Nanoapp> &app : mNanoapps) {
74     if (app->getAppId() == appId) {
75       *instanceId = app->getInstanceId();
76       found = true;
77       break;
78     }
79   }
80 
81   return found;
82 }
83 
forEachNanoapp(NanoappCallbackFunction * callback,void * data)84 void EventLoop::forEachNanoapp(NanoappCallbackFunction *callback, void *data) {
85   ConditionalLockGuard<Mutex> lock(mNanoappsLock, !inEventLoopThread());
86 
87   for (const UniquePtr<Nanoapp> &nanoapp : mNanoapps) {
88     callback(nanoapp.get(), data);
89   }
90 }
91 
invokeMessageFreeFunction(uint64_t appId,chreMessageFreeFunction * freeFunction,void * message,size_t messageSize)92 void EventLoop::invokeMessageFreeFunction(uint64_t appId,
93                                           chreMessageFreeFunction *freeFunction,
94                                           void *message, size_t messageSize) {
95   Nanoapp *nanoapp = lookupAppByAppId(appId);
96   if (nanoapp == nullptr) {
97     LOGE("Couldn't find app 0x%016" PRIx64 " for message free callback", appId);
98   } else {
99     auto prevCurrentApp = mCurrentApp;
100     mCurrentApp = nanoapp;
101     freeFunction(message, messageSize);
102     mCurrentApp = prevCurrentApp;
103   }
104 }
105 
run()106 void EventLoop::run() {
107   LOGI("EventLoop start");
108 
109   while (mRunning) {
110     // Events are delivered in a single stage: they arrive in the inbound event
111     // queue mEvents (potentially posted from another thread), then within
112     // this context these events are distributed to all interested Nanoapps,
113     // with their free callback invoked after distribution.
114     mEventPoolUsage.addValue(static_cast<uint32_t>(mEvents.size()));
115 
116     // mEvents.pop() will be a blocking call if mEvents.empty()
117     Event *event = mEvents.pop();
118     // Need size() + 1 since the to-be-processed event has already been removed.
119     mPowerControlManager.preEventLoopProcess(mEvents.size() + 1);
120     distributeEvent(event);
121 
122     mPowerControlManager.postEventLoopProcess(mEvents.size());
123   }
124 
125   // Purge the main queue of events pending distribution. All nanoapps should be
126   // prevented from sending events or messages at this point via
127   // currentNanoappIsStopping() returning true.
128   while (!mEvents.empty()) {
129     freeEvent(mEvents.pop());
130   }
131 
132   // Unload all running nanoapps
133   while (!mNanoapps.empty()) {
134     unloadNanoappAtIndex(mNanoapps.size() - 1);
135   }
136 
137   LOGI("Exiting EventLoop");
138 }
139 
startNanoapp(UniquePtr<Nanoapp> & nanoapp)140 bool EventLoop::startNanoapp(UniquePtr<Nanoapp> &nanoapp) {
141   CHRE_ASSERT(!nanoapp.isNull());
142   bool success = false;
143   auto *eventLoopManager = EventLoopManagerSingleton::get();
144   EventLoop &eventLoop = eventLoopManager->getEventLoop();
145   uint16_t existingInstanceId;
146 
147   if (nanoapp.isNull()) {
148     // no-op, invalid argument
149   } else if (nanoapp->getTargetApiVersion() <
150              CHRE_FIRST_SUPPORTED_API_VERSION) {
151     LOGE("Incompatible nanoapp (target ver 0x%" PRIx32
152          ", first supported ver 0x%" PRIx32 ")",
153          nanoapp->getTargetApiVersion(),
154          static_cast<uint32_t>(CHRE_FIRST_SUPPORTED_API_VERSION));
155   } else if (eventLoop.findNanoappInstanceIdByAppId(nanoapp->getAppId(),
156                                                     &existingInstanceId)) {
157     LOGE("App with ID 0x%016" PRIx64 " already exists as instance ID %" PRIu16,
158          nanoapp->getAppId(), existingInstanceId);
159   } else if (!mNanoapps.prepareForPush()) {
160     LOG_OOM();
161   } else {
162     nanoapp->setInstanceId(eventLoopManager->getNextInstanceId());
163     LOGD("Instance ID %" PRIu16 " assigned to app ID 0x%016" PRIx64,
164          nanoapp->getInstanceId(), nanoapp->getAppId());
165 
166     Nanoapp *newNanoapp = nanoapp.get();
167     {
168       LockGuard<Mutex> lock(mNanoappsLock);
169       mNanoapps.push_back(std::move(nanoapp));
170       // After this point, nanoapp is null as we've transferred ownership into
171       // mNanoapps.back() - use newNanoapp to reference it
172     }
173 
174     mCurrentApp = newNanoapp;
175     success = newNanoapp->start();
176     mCurrentApp = nullptr;
177     if (!success) {
178       // TODO: to be fully safe, need to purge/flush any events and messages
179       // sent by the nanoapp here (but don't call nanoappEnd). For now, we just
180       // destroy the Nanoapp instance.
181       LOGE("Nanoapp %" PRIu16 " failed to start", newNanoapp->getInstanceId());
182 
183       // Note that this lock protects against concurrent read and modification
184       // of mNanoapps, but we are assured that no new nanoapps were added since
185       // we pushed the new nanoapp
186       LockGuard<Mutex> lock(mNanoappsLock);
187       mNanoapps.pop_back();
188     } else {
189       notifyAppStatusChange(CHRE_EVENT_NANOAPP_STARTED, *newNanoapp);
190     }
191   }
192 
193   return success;
194 }
195 
unloadNanoapp(uint16_t instanceId,bool allowSystemNanoappUnload)196 bool EventLoop::unloadNanoapp(uint16_t instanceId,
197                               bool allowSystemNanoappUnload) {
198   bool unloaded = false;
199 
200   for (size_t i = 0; i < mNanoapps.size(); i++) {
201     if (instanceId == mNanoapps[i]->getInstanceId()) {
202       if (!allowSystemNanoappUnload && mNanoapps[i]->isSystemNanoapp()) {
203         LOGE("Refusing to unload system nanoapp");
204       } else {
205         // Make sure all messages sent by this nanoapp at least have their
206         // associated free callback processing pending in the event queue (i.e.
207         // there are no messages pending delivery to the host)
208         EventLoopManagerSingleton::get()
209             ->getHostCommsManager()
210             .flushMessagesSentByNanoapp(mNanoapps[i]->getAppId());
211 
212         // Mark that this nanoapp is stopping early, so it can't send events or
213         // messages during the nanoapp event queue flush
214         mStoppingNanoapp = mNanoapps[i].get();
215 
216         // Distribute all inbound events we have at this time - here we're
217         // interested in handling any message free callbacks generated by
218         // flushInboundEventQueue()
219         flushInboundEventQueue();
220 
221         // Post the unload event now (so we can reference the Nanoapp instance
222         // directly), but nanoapps won't get it until after the unload completes
223         notifyAppStatusChange(CHRE_EVENT_NANOAPP_STOPPED, *mStoppingNanoapp);
224 
225         // Finally, we are at a point where there should not be any pending
226         // events or messages sent by the app that could potentially reference
227         // the nanoapp's memory, so we are safe to unload it
228         unloadNanoappAtIndex(i);
229         mStoppingNanoapp = nullptr;
230 
231         LOGD("Unloaded nanoapp with instanceId %" PRIu16, instanceId);
232         unloaded = true;
233       }
234       break;
235     }
236   }
237 
238   return unloaded;
239 }
240 
postEventOrDie(uint16_t eventType,void * eventData,chreEventCompleteFunction * freeCallback,uint16_t targetInstanceId,uint16_t targetGroupMask)241 void EventLoop::postEventOrDie(uint16_t eventType, void *eventData,
242                                chreEventCompleteFunction *freeCallback,
243                                uint16_t targetInstanceId,
244                                uint16_t targetGroupMask) {
245   if (mRunning) {
246     if (!allocateAndPostEvent(eventType, eventData, freeCallback,
247                               kSystemInstanceId, targetInstanceId,
248                               targetGroupMask)) {
249       FATAL_ERROR("Failed to post critical system event 0x%" PRIx16, eventType);
250     }
251   } else if (freeCallback != nullptr) {
252     freeCallback(eventType, eventData);
253   }
254 }
255 
postSystemEvent(uint16_t eventType,void * eventData,SystemEventCallbackFunction * callback,void * extraData)256 bool EventLoop::postSystemEvent(uint16_t eventType, void *eventData,
257                                 SystemEventCallbackFunction *callback,
258                                 void *extraData) {
259   if (mRunning) {
260     Event *event =
261         mEventPool.allocate(eventType, eventData, callback, extraData);
262 
263     if (event == nullptr || !mEvents.push(event)) {
264       FATAL_ERROR("Failed to post critical system event 0x%" PRIx16, eventType);
265     }
266     return true;
267   }
268   return false;
269 }
270 
postLowPriorityEventOrFree(uint16_t eventType,void * eventData,chreEventCompleteFunction * freeCallback,uint16_t senderInstanceId,uint16_t targetInstanceId,uint16_t targetGroupMask)271 bool EventLoop::postLowPriorityEventOrFree(
272     uint16_t eventType, void *eventData,
273     chreEventCompleteFunction *freeCallback, uint16_t senderInstanceId,
274     uint16_t targetInstanceId, uint16_t targetGroupMask) {
275   bool eventPosted = false;
276 
277   if (mRunning) {
278     if (mEventPool.getFreeBlockCount() > kMinReservedHighPriorityEventCount) {
279       eventPosted = allocateAndPostEvent(eventType, eventData, freeCallback,
280                                          senderInstanceId, targetInstanceId,
281                                          targetGroupMask);
282       if (!eventPosted) {
283         LOGE("Failed to allocate event 0x%" PRIx16 " to instanceId %" PRIu16,
284              eventType, targetInstanceId);
285         ++mNumDroppedLowPriEvents;
286       }
287     }
288   }
289 
290   if (!eventPosted && freeCallback != nullptr) {
291     freeCallback(eventType, eventData);
292   }
293 
294   return eventPosted;
295 }
296 
stop()297 void EventLoop::stop() {
298   auto callback = [](uint16_t /*type*/, void *data, void * /*extraData*/) {
299     auto *obj = static_cast<EventLoop *>(data);
300     obj->onStopComplete();
301   };
302 
303   // Stop accepting new events and tell the main loop to finish
304   postSystemEvent(static_cast<uint16_t>(SystemCallbackType::Shutdown),
305                   /*eventData=*/this, callback, /*extraData=*/nullptr);
306 }
307 
onStopComplete()308 void EventLoop::onStopComplete() {
309   mRunning = false;
310 }
311 
findNanoappByInstanceId(uint16_t instanceId) const312 Nanoapp *EventLoop::findNanoappByInstanceId(uint16_t instanceId) const {
313   ConditionalLockGuard<Mutex> lock(mNanoappsLock, !inEventLoopThread());
314   return lookupAppByInstanceId(instanceId);
315 }
316 
populateNanoappInfoForAppId(uint64_t appId,struct chreNanoappInfo * info) const317 bool EventLoop::populateNanoappInfoForAppId(
318     uint64_t appId, struct chreNanoappInfo *info) const {
319   ConditionalLockGuard<Mutex> lock(mNanoappsLock, !inEventLoopThread());
320   Nanoapp *app = lookupAppByAppId(appId);
321   return populateNanoappInfo(app, info);
322 }
323 
populateNanoappInfoForInstanceId(uint16_t instanceId,struct chreNanoappInfo * info) const324 bool EventLoop::populateNanoappInfoForInstanceId(
325     uint16_t instanceId, struct chreNanoappInfo *info) const {
326   ConditionalLockGuard<Mutex> lock(mNanoappsLock, !inEventLoopThread());
327   Nanoapp *app = lookupAppByInstanceId(instanceId);
328   return populateNanoappInfo(app, info);
329 }
330 
currentNanoappIsStopping() const331 bool EventLoop::currentNanoappIsStopping() const {
332   return (mCurrentApp == mStoppingNanoapp || !mRunning);
333 }
334 
logStateToBuffer(DebugDumpWrapper & debugDump) const335 void EventLoop::logStateToBuffer(DebugDumpWrapper &debugDump) const {
336   debugDump.print("\nEvent Loop:\n");
337   debugDump.print("  Max event pool usage: %" PRIu32 "/%zu\n",
338                   mEventPoolUsage.getMax(), kMaxEventCount);
339   debugDump.print("  Number of low priority events dropped: %" PRIu32 "\n",
340                   mNumDroppedLowPriEvents);
341   debugDump.print("  Mean event pool usage: %" PRIu32 "/%zu\n",
342                   mEventPoolUsage.getMean(), kMaxEventCount);
343 
344   Nanoseconds timeSince =
345       SystemTime::getMonotonicTime() - mTimeLastWakeupBucketCycled;
346   uint64_t timeSinceMins =
347       timeSince.toRawNanoseconds() / kOneMinuteInNanoseconds;
348   uint64_t durationMins =
349       kIntervalWakeupBucket.toRawNanoseconds() / kOneMinuteInNanoseconds;
350   debugDump.print("  Nanoapp host wakeup tracking: cycled %" PRIu64
351                   "mins ago, bucketDuration=%" PRIu64 "mins\n",
352                   timeSinceMins, durationMins);
353 
354   debugDump.print("\nNanoapps:\n");
355   for (const UniquePtr<Nanoapp> &app : mNanoapps) {
356     app->logStateToBuffer(debugDump);
357   }
358 }
359 
allocateAndPostEvent(uint16_t eventType,void * eventData,chreEventCompleteFunction * freeCallback,uint16_t senderInstanceId,uint16_t targetInstanceId,uint16_t targetGroupMask)360 bool EventLoop::allocateAndPostEvent(uint16_t eventType, void *eventData,
361                                      chreEventCompleteFunction *freeCallback,
362                                      uint16_t senderInstanceId,
363                                      uint16_t targetInstanceId,
364                                      uint16_t targetGroupMask) {
365   bool success = false;
366 
367   Event *event =
368       mEventPool.allocate(eventType, eventData, freeCallback, senderInstanceId,
369                           targetInstanceId, targetGroupMask);
370   if (event != nullptr) {
371     success = mEvents.push(event);
372   }
373 
374   return success;
375 }
376 
deliverNextEvent(const UniquePtr<Nanoapp> & app,Event * event)377 void EventLoop::deliverNextEvent(const UniquePtr<Nanoapp> &app, Event *event) {
378   // TODO: cleaner way to set/clear this? RAII-style?
379   mCurrentApp = app.get();
380   app->processEvent(event);
381   mCurrentApp = nullptr;
382 }
383 
distributeEvent(Event * event)384 void EventLoop::distributeEvent(Event *event) {
385   bool eventDelivered = false;
386   for (const UniquePtr<Nanoapp> &app : mNanoapps) {
387     if ((event->targetInstanceId == chre::kBroadcastInstanceId &&
388          app->isRegisteredForBroadcastEvent(event)) ||
389         event->targetInstanceId == app->getInstanceId()) {
390       eventDelivered = true;
391       deliverNextEvent(app, event);
392     }
393   }
394   // Log if an event unicast to a nanoapp isn't delivered, as this is could be
395   // a bug (e.g. something isn't properly keeping track of when nanoapps are
396   // unloaded), though it could just be a harmless transient issue (e.g. race
397   // condition with nanoapp unload, where we post an event to a nanoapp just
398   // after queues are flushed while it's unloading)
399   if (!eventDelivered && event->targetInstanceId != kBroadcastInstanceId &&
400       event->targetInstanceId != kSystemInstanceId) {
401     LOGW("Dropping event 0x%" PRIx16 " from instanceId %" PRIu16 "->%" PRIu16,
402          event->eventType, event->senderInstanceId, event->targetInstanceId);
403   }
404   CHRE_ASSERT(event->isUnreferenced());
405   freeEvent(event);
406 }
407 
flushInboundEventQueue()408 void EventLoop::flushInboundEventQueue() {
409   while (!mEvents.empty()) {
410     distributeEvent(mEvents.pop());
411   }
412 }
413 
freeEvent(Event * event)414 void EventLoop::freeEvent(Event *event) {
415   if (event->hasFreeCallback()) {
416     // TODO: find a better way to set the context to the creator of the event
417     mCurrentApp = lookupAppByInstanceId(event->senderInstanceId);
418     event->invokeFreeCallback();
419     mCurrentApp = nullptr;
420   }
421 
422   mEventPool.deallocate(event);
423 }
424 
lookupAppByAppId(uint64_t appId) const425 Nanoapp *EventLoop::lookupAppByAppId(uint64_t appId) const {
426   for (const UniquePtr<Nanoapp> &app : mNanoapps) {
427     if (app->getAppId() == appId) {
428       return app.get();
429     }
430   }
431 
432   return nullptr;
433 }
434 
lookupAppByInstanceId(uint16_t instanceId) const435 Nanoapp *EventLoop::lookupAppByInstanceId(uint16_t instanceId) const {
436   // The system instance ID always has nullptr as its Nanoapp pointer, so can
437   // skip iterating through the nanoapp list for that case
438   if (instanceId != kSystemInstanceId) {
439     for (const UniquePtr<Nanoapp> &app : mNanoapps) {
440       if (app->getInstanceId() == instanceId) {
441         return app.get();
442       }
443     }
444   }
445 
446   return nullptr;
447 }
448 
notifyAppStatusChange(uint16_t eventType,const Nanoapp & nanoapp)449 void EventLoop::notifyAppStatusChange(uint16_t eventType,
450                                       const Nanoapp &nanoapp) {
451   auto *info = memoryAlloc<chreNanoappInfo>();
452   if (info == nullptr) {
453     LOG_OOM();
454   } else {
455     info->appId = nanoapp.getAppId();
456     info->version = nanoapp.getAppVersion();
457     info->instanceId = nanoapp.getInstanceId();
458 
459     postEventOrDie(eventType, info, freeEventDataCallback);
460   }
461 }
462 
unloadNanoappAtIndex(size_t index)463 void EventLoop::unloadNanoappAtIndex(size_t index) {
464   const UniquePtr<Nanoapp> &nanoapp = mNanoapps[index];
465 
466   // Lock here to prevent the nanoapp instance from being accessed between the
467   // time it is ended and fully erased
468   LockGuard<Mutex> lock(mNanoappsLock);
469 
470   // Let the app know it's going away
471   mCurrentApp = nanoapp.get();
472   nanoapp->end();
473 
474   // Cleanup resources.
475 #ifdef CHRE_WIFI_SUPPORT_ENABLED
476   const uint32_t numDisabledWifiSubscriptions =
477       EventLoopManagerSingleton::get()
478           ->getWifiRequestManager()
479           .disableAllSubscriptions(nanoapp.get());
480   logDanglingResources("WIFI subscriptions", numDisabledWifiSubscriptions);
481 #endif  // CHRE_WIFI_SUPPORT_ENABLED
482 
483 #ifdef CHRE_GNSS_SUPPORT_ENABLED
484   const uint32_t numDisabledGnssSubscriptions =
485       EventLoopManagerSingleton::get()
486           ->getGnssManager()
487           .disableAllSubscriptions(nanoapp.get());
488   logDanglingResources("GNSS subscriptions", numDisabledGnssSubscriptions);
489 #endif  // CHRE_GNSS_SUPPORT_ENABLED
490 
491 #ifdef CHRE_SENSORS_SUPPORT_ENABLED
492   const uint32_t numDisabledSensorSubscriptions =
493       EventLoopManagerSingleton::get()
494           ->getSensorRequestManager()
495           .disableAllSubscriptions(nanoapp.get());
496   logDanglingResources("Sensor subscriptions", numDisabledSensorSubscriptions);
497 #endif  // CHRE_SENSORS_SUPPORT_ENABLED
498 
499 #ifdef CHRE_AUDIO_SUPPORT_ENABLED
500   const uint32_t numDisabledAudioRequests =
501       EventLoopManagerSingleton::get()
502           ->getAudioRequestManager()
503           .disableAllAudioRequests(nanoapp.get());
504   logDanglingResources("Audio requests", numDisabledAudioRequests);
505 #endif  // CHRE_AUDIO_SUPPORT_ENABLED
506 
507 #ifdef CHRE_BLE_SUPPORT_ENABLED
508   const uint32_t numDisabledBleScans = EventLoopManagerSingleton::get()
509                                            ->getBleRequestManager()
510                                            .disableActiveScan(nanoapp.get());
511   logDanglingResources("BLE scan", numDisabledBleScans);
512 #endif  // CHRE_BLE_SUPPORT_ENABLED
513 
514   const uint32_t numCancelledTimers =
515       getTimerPool().cancelAllNanoappTimers(nanoapp.get());
516   logDanglingResources("timers", numCancelledTimers);
517 
518   const uint32_t numFreedBlocks =
519       EventLoopManagerSingleton::get()->getMemoryManager().nanoappFreeAll(
520           nanoapp.get());
521   logDanglingResources("heap blocks", numFreedBlocks);
522 
523   mCurrentApp = nullptr;
524 
525   // Destroy the Nanoapp instance
526   mNanoapps.erase(index);
527 }
528 
handleNanoappWakeupBuckets()529 void EventLoop::handleNanoappWakeupBuckets() {
530   Nanoseconds now = SystemTime::getMonotonicTime();
531   Nanoseconds duration = now - mTimeLastWakeupBucketCycled;
532   if (duration > kIntervalWakeupBucket) {
533     size_t numBuckets = static_cast<size_t>(
534         duration.toRawNanoseconds() / kIntervalWakeupBucket.toRawNanoseconds());
535     mTimeLastWakeupBucketCycled = now;
536     for (auto &nanoapp : mNanoapps) {
537       nanoapp->cycleWakeupBuckets(numBuckets);
538     }
539   }
540 }
541 
logDanglingResources(const char * name,uint32_t count)542 void EventLoop::logDanglingResources(const char *name, uint32_t count) {
543   if (count > 0) {
544     LOGE("App 0x%016" PRIx64 " had %" PRIu32 " remaining %s at unload",
545          mCurrentApp->getAppId(), count, name);
546   }
547 }
548 
549 }  // namespace chre
550