1/** @addtogroup MCD_MCDIMPL_DAEMON_DEV
2 * @{
3 * @file
4 *
5 *
6 * <!-- Copyright Giesecke & Devrient GmbH 2009 - 2012 -->
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. The name of the author may not be used to endorse or promote
17 *    products derived from this software without specific prior
18 *    written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
21 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
24 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
26 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33#include <cstdlib>
34#include <pthread.h>
35#include "McTypes.h"
36
37#include "DeviceScheduler.h"
38#include "DeviceIrqHandler.h"
39#include "ExcDevice.h"
40#include "Connection.h"
41#include "TrustletSession.h"
42
43#include "MobiCoreDevice.h"
44#include "Mci/mci.h"
45#include "mcLoadFormat.h"
46
47
48#include "log.h"
49#include "public/MobiCoreDevice.h"
50
51
52//------------------------------------------------------------------------------
53MobiCoreDevice::MobiCoreDevice()
54{
55    mcFault = false;
56}
57
58//------------------------------------------------------------------------------
59MobiCoreDevice::~MobiCoreDevice()
60{
61    delete mcVersionInfo;
62    mcVersionInfo = NULL;
63}
64
65//------------------------------------------------------------------------------
66TrustletSession *MobiCoreDevice::getTrustletSession(uint32_t sessionId)
67{
68    for (trustletSessionIterator_t session = trustletSessions.begin();
69            session != trustletSessions.end();
70            ++session) {
71        TrustletSession *tsTmp = *session;
72        if (tsTmp->sessionId == sessionId) {
73            return tsTmp;
74        }
75    }
76    return NULL;
77}
78
79
80void MobiCoreDevice::cleanSessionBuffers(TrustletSession *session)
81{
82    CWsm_ptr pWsm = session->popBulkBuff();
83
84    while (pWsm) {
85        unlockWsmL2(pWsm->handle);
86        pWsm = session->popBulkBuff();
87    }
88}
89//------------------------------------------------------------------------------
90void MobiCoreDevice::removeTrustletSession(uint32_t sessionId)
91{
92    for (trustletSessionIterator_t session = trustletSessions.begin();
93            session != trustletSessions.end();
94            ++session) {
95        if ((*session)->sessionId == sessionId) {
96            cleanSessionBuffers(*session);
97            trustletSessions.erase(session);
98            return;
99        }
100    }
101}
102//------------------------------------------------------------------------------
103Connection *MobiCoreDevice::getSessionConnection(uint32_t sessionId, notification_t *notification)
104{
105    Connection *con = NULL;
106    TrustletSession *ts = NULL;
107
108    ts = getTrustletSession(sessionId);
109    if (ts == NULL) {
110        return NULL;
111    }
112
113    con = ts->notificationConnection;
114    if (con == NULL) {
115        ts->queueNotification(notification);
116        return NULL;
117    }
118
119    return con;
120}
121
122
123//------------------------------------------------------------------------------
124bool MobiCoreDevice::open(Connection *connection)
125{
126    // Link this device to the connection
127    connection->connectionData = this;
128    return true;
129}
130
131
132//------------------------------------------------------------------------------
133/**
134 * Close device.
135 *
136 * Removes all sessions to a connection. Though, clientLib rejects the closeDevice()
137 * command if still sessions connected to the device, this is needed to clean up all
138 * sessions if client dies.
139 */
140void MobiCoreDevice::close(Connection *connection)
141{
142    trustletSessionList_t::reverse_iterator interator;
143    static CMutex mutex;
144    // 1. Iterate through device session to find connection
145    // 2. Decide what to do with open Trustlet sessions
146    // 3. Remove & delete deviceSession from vector
147
148    // Enter critical section
149    mutex.lock();
150    for (interator = trustletSessions.rbegin();
151            interator != trustletSessions.rend();
152            interator++) {
153        TrustletSession *ts = *interator;
154
155        if (ts->deviceConnection == connection) {
156            closeSession(connection, ts->sessionId);
157        }
158    }
159    // Leave critical section
160    mutex.unlock();
161
162    // After the trustlet is done make sure to tell the driver to cleanup
163    // all the orphaned drivers
164    cleanupWsmL2();
165
166    connection->connectionData = NULL;
167}
168
169
170//------------------------------------------------------------------------------
171void MobiCoreDevice::start(void)
172{
173    // Call the device specific initialization
174    //  initDevice();
175
176    LOG_I("Starting DeviceIrqHandler...");
177    // Start the irq handling thread
178    DeviceIrqHandler::start();
179
180    if (schedulerAvailable()) {
181        LOG_I("Starting DeviceScheduler...");
182        // Start the scheduling handling thread
183        DeviceScheduler::start();
184    } else {
185        LOG_I("No DeviceScheduler available.");
186    }
187}
188
189
190//------------------------------------------------------------------------------
191void MobiCoreDevice::signalMcpNotification(void)
192{
193    mcpSessionNotification.signal();
194}
195
196
197//------------------------------------------------------------------------------
198bool MobiCoreDevice::waitMcpNotification(void)
199{
200    int counter = 5;
201    while (1) {
202        // In case of fault just return, nothing to do here
203        if (mcFault) {
204            return false;
205        }
206        // Wait 10 seconds for notification
207        if (mcpSessionNotification.wait(10) == false) {
208            // No MCP answer received and mobicore halted, dump mobicore status
209            // then throw exception
210            LOG_I("No MCP answer received in 2 seconds.");
211            if (getMobicoreStatus() == MC_STATUS_HALT) {
212                dumpMobicoreStatus();
213                mcFault = true;
214                return false;
215            } else {
216                counter--;
217                if (counter < 1) {
218                    mcFault = true;
219                    return false;
220                }
221            }
222        } else {
223            break;
224        }
225    }
226
227    // Check healthiness state of the device
228    if (DeviceIrqHandler::isExiting()) {
229        LOG_I("waitMcpNotification(): IrqHandler thread died! Joining");
230        DeviceIrqHandler::join();
231        LOG_I("waitMcpNotification(): Joined");
232        LOG_E("IrqHandler thread died!");
233        return false;
234    }
235
236    if (DeviceScheduler::isExiting()) {
237        LOG_I("waitMcpNotification(): Scheduler thread died! Joining");
238        DeviceScheduler::join();
239        LOG_I("waitMcpNotification(): Joined");
240        LOG_E("Scheduler thread died!");
241        return false;
242    }
243    return true;
244}
245
246
247//------------------------------------------------------------------------------
248mcResult_t MobiCoreDevice::openSession(
249    Connection                      *deviceConnection,
250    loadDataOpenSession_ptr         pLoadDataOpenSession,
251    MC_DRV_CMD_OPEN_SESSION_struct  *cmdOpenSession,
252    mcDrvRspOpenSessionPayload_ptr  pRspOpenSessionPayload
253)
254{
255    do {
256        addr_t tci;
257        uint32_t len;
258        uint32_t handle = cmdOpenSession->handle;
259
260        if (!findContiguousWsm(handle, &tci, &len)) {
261            LOG_E("Failed to find contiguous WSM %u", handle);
262            return MC_DRV_ERR_DAEMON_WSM_HANDLE_NOT_FOUND;
263        }
264
265        if (!lockWsmL2(handle)) {
266            LOG_E("Failed to lock contiguous WSM %u", handle);
267            return MC_DRV_ERR_DAEMON_WSM_HANDLE_NOT_FOUND;
268        }
269
270        // Write MCP open message to buffer
271        mcpMessage->cmdOpen.cmdHeader.cmdId = MC_MCP_CMD_OPEN_SESSION;
272        mcpMessage->cmdOpen.uuid = cmdOpenSession->uuid;
273        mcpMessage->cmdOpen.wsmTypeTci = WSM_CONTIGUOUS;
274        mcpMessage->cmdOpen.adrTciBuffer = (uint32_t)(tci);
275        mcpMessage->cmdOpen.ofsTciBuffer = 0;
276        mcpMessage->cmdOpen.lenTciBuffer = len;
277
278        LOG_I(" Using phys=%p, len=%d as TCI buffer",
279              (addr_t)(cmdOpenSession->tci),
280              cmdOpenSession->len);
281
282        // check if load data is provided
283        mcpMessage->cmdOpen.wsmTypeLoadData = WSM_L2;
284        mcpMessage->cmdOpen.adrLoadData = (uint32_t)pLoadDataOpenSession->baseAddr;
285        mcpMessage->cmdOpen.ofsLoadData = pLoadDataOpenSession->offs;
286        mcpMessage->cmdOpen.lenLoadData = pLoadDataOpenSession->len;
287        memcpy(&mcpMessage->cmdOpen.tlHeader, pLoadDataOpenSession->tlHeader, sizeof(*pLoadDataOpenSession->tlHeader));
288
289        // Clear the notifications queue. We asume the race condition we have
290        // seen in openSession never happens elsewhere
291        notifications = std::queue<notification_t>();
292        // Notify MC about a new command inside the MCP buffer
293        notify(SID_MCP);
294
295        // Wait till response from MC is available
296        if (!waitMcpNotification()) {
297            // Here Mobicore can be considered dead.
298            unlockWsmL2(handle);
299            return MC_DRV_ERR_DAEMON_MCI_ERROR;
300        }
301
302        // Check if the command response ID is correct
303        if ((MC_MCP_CMD_OPEN_SESSION | FLAG_RESPONSE) != mcpMessage->rspHeader.rspId) {
304            LOG_E("CMD_OPEN_SESSION got invalid MCP command response(0x%X)", mcpMessage->rspHeader.rspId);
305            // Something is messing with our MCI memory, we cannot know if the Trustlet was loaded.
306            // Had in been loaded, we are loosing track of it here.
307            unlockWsmL2(handle);
308            return MC_DRV_ERR_DAEMON_MCI_ERROR;
309        }
310
311        uint32_t mcRet = mcpMessage->rspOpen.rspHeader.result;
312
313        if (mcRet != MC_MCP_RET_OK) {
314            LOG_E("MCP OPEN returned code %d.", mcRet);
315            unlockWsmL2(handle);
316            return MAKE_MC_DRV_MCP_ERROR(mcRet);
317        }
318
319        LOG_I(" After MCP OPEN, we have %d queued notifications",
320              notifications.size());
321        // Read MC answer from MCP buffer
322        TrustletSession *trustletSession = new TrustletSession(
323            deviceConnection,
324            mcpMessage->rspOpen.sessionId);
325
326        pRspOpenSessionPayload->sessionId = trustletSession->sessionId;
327        pRspOpenSessionPayload->deviceSessionId = (uint32_t)trustletSession;
328        pRspOpenSessionPayload->sessionMagic = trustletSession->sessionMagic;
329
330        trustletSessions.push_back(trustletSession);
331
332        trustletSession->addBulkBuff(new CWsm((void *)pLoadDataOpenSession->offs, pLoadDataOpenSession->len, handle, 0));
333
334        // We have some queued notifications and we need to send them to them
335        // trustlet session
336        while (!notifications.empty()) {
337            trustletSession->queueNotification(&notifications.front());
338            notifications.pop();
339        }
340
341    } while (0);
342    return MC_DRV_OK;
343}
344
345
346//------------------------------------------------------------------------------
347TrustletSession *MobiCoreDevice::registerTrustletConnection(
348    Connection                    *connection,
349    MC_DRV_CMD_NQ_CONNECT_struct *cmdNqConnect
350)
351{
352    LOG_I(" Registering notification socket with Service session %d.",
353          cmdNqConnect->sessionId);
354    LOG_V("  Searching sessionId %d with sessionMagic %d",
355          cmdNqConnect->sessionId,
356          cmdNqConnect->sessionMagic);
357
358    for (trustletSessionIterator_t iterator = trustletSessions.begin();
359            iterator != trustletSessions.end();
360            ++iterator) {
361        TrustletSession *ts = *iterator;
362
363        if (ts != (TrustletSession *) (cmdNqConnect->deviceSessionId)) {
364            continue;
365        }
366
367        if ( (ts->sessionMagic != cmdNqConnect->sessionMagic)
368                || (ts->sessionId != cmdNqConnect->sessionId)) {
369            continue;
370        }
371
372        ts->notificationConnection = connection;
373
374        LOG_I(" Found Service session, registered connection.");
375
376        return ts;
377    }
378
379    LOG_I("registerTrustletConnection(): search failed");
380    return NULL;
381}
382
383
384//------------------------------------------------------------------------------
385mcResult_t MobiCoreDevice::closeSession(uint32_t sessionId)
386{
387    LOG_I(" Write MCP CLOSE message to MCI, notify and wait");
388
389    // Write MCP close message to buffer
390    mcpMessage->cmdClose.cmdHeader.cmdId = MC_MCP_CMD_CLOSE_SESSION;
391    mcpMessage->cmdClose.sessionId = sessionId;
392
393    // Notify MC about the availability of a new command inside the MCP buffer
394    notify(SID_MCP);
395
396    // Wait till response from MSH is available
397    if (!waitMcpNotification()) {
398        return MC_DRV_ERR_DAEMON_MCI_ERROR;
399    }
400
401    // Check if the command response ID is correct
402    if ((MC_MCP_CMD_CLOSE_SESSION | FLAG_RESPONSE) != mcpMessage->rspHeader.rspId) {
403        LOG_E("CMD_CLOSE_SESSION got invalid MCP response");
404        return MC_DRV_ERR_DAEMON_MCI_ERROR;
405    }
406
407    // Read MC answer from MCP buffer
408    uint32_t mcRet = mcpMessage->rspOpen.rspHeader.result;
409
410    if (mcRet != MC_MCP_RET_OK) {
411        LOG_E("CMD_CLOSE_SESSION error %d", mcRet);
412        return MAKE_MC_DRV_MCP_ERROR(mcRet);
413    }
414
415    return MC_DRV_OK;
416}
417
418//------------------------------------------------------------------------------
419/**
420 * TODO-2012-09-19-haenellu: Do some more checks here, otherwise rogue clientLib
421 * can close sessions from different TLCs. That is, deviceConnection is ignored below.
422 *
423 * Need connection as well as according session ID, so that a client can not
424 * close sessions not belonging to him.
425 */
426mcResult_t MobiCoreDevice::closeSession(Connection *deviceConnection, uint32_t sessionId)
427{
428    TrustletSession *ts = getTrustletSession(sessionId);
429    if (ts == NULL) {
430        LOG_E("no session found with id=%d", sessionId);
431        return MC_DRV_ERR_DAEMON_UNKNOWN_SESSION;
432    }
433
434    uint32_t mcRet = closeSession(sessionId);
435    if (mcRet != MC_DRV_OK) {
436        return mcRet;
437    }
438
439    // remove objects
440    removeTrustletSession(sessionId);
441    delete ts;
442
443    return MC_DRV_OK;
444}
445
446
447//------------------------------------------------------------------------------
448mcResult_t MobiCoreDevice::mapBulk(uint32_t sessionId, uint32_t handle, uint32_t pAddrL2,
449                                   uint32_t offsetPayload, uint32_t lenBulkMem, uint32_t *secureVirtualAdr)
450{
451    TrustletSession *ts = getTrustletSession(sessionId);
452    if (ts == NULL) {
453        LOG_E("no session found with id=%d", sessionId);
454        return MC_DRV_ERR_DAEMON_UNKNOWN_SESSION;
455    }
456
457    // TODO-2012-09-06-haenellu: Think about not ignoring the error case, ClientLib does not allow this.
458    ts->addBulkBuff(new CWsm((void *)offsetPayload, lenBulkMem, handle, (void *)pAddrL2));
459    // Write MCP map message to buffer
460    mcpMessage->cmdMap.cmdHeader.cmdId = MC_MCP_CMD_MAP;
461    mcpMessage->cmdMap.sessionId = sessionId;
462    mcpMessage->cmdMap.wsmType = WSM_L2;
463    mcpMessage->cmdMap.adrBuffer = (uint32_t)(pAddrL2);
464    mcpMessage->cmdMap.ofsBuffer = offsetPayload;
465    mcpMessage->cmdMap.lenBuffer = lenBulkMem;
466
467    // Notify MC about the availability of a new command inside the MCP buffer
468    notify(SID_MCP);
469
470    // Wait till response from MC is available
471    if (!waitMcpNotification()) {
472        return MC_DRV_ERR_DAEMON_MCI_ERROR;
473    }
474
475    // Check if the command response ID is correct
476    if (mcpMessage->rspHeader.rspId != (MC_MCP_CMD_MAP | FLAG_RESPONSE)) {
477        LOG_E("CMD_MAP got invalid MCP response");
478        return MC_DRV_ERR_DAEMON_MCI_ERROR;
479    }
480
481    uint32_t mcRet = mcpMessage->rspMap.rspHeader.result;
482
483    if (mcRet != MC_MCP_RET_OK) {
484        LOG_E("MCP MAP returned code %d.", mcRet);
485        return MAKE_MC_DRV_MCP_ERROR(mcRet);
486    }
487
488    *secureVirtualAdr = mcpMessage->rspMap.secureVirtualAdr;
489    return MC_DRV_OK;
490}
491
492
493//------------------------------------------------------------------------------
494mcResult_t MobiCoreDevice::unmapBulk(uint32_t sessionId, uint32_t handle,
495                                     uint32_t secureVirtualAdr, uint32_t lenBulkMem)
496{
497    TrustletSession *ts = getTrustletSession(sessionId);
498    if (ts == NULL) {
499        LOG_E("no session found with id=%d", sessionId);
500        return MC_DRV_ERR_DAEMON_UNKNOWN_SESSION;
501    }
502
503    // Write MCP unmap command to buffer
504    mcpMessage->cmdUnmap.cmdHeader.cmdId = MC_MCP_CMD_UNMAP;
505    mcpMessage->cmdUnmap.sessionId = sessionId;
506    mcpMessage->cmdUnmap.wsmType = WSM_L2;
507    mcpMessage->cmdUnmap.secureVirtualAdr = secureVirtualAdr;
508    mcpMessage->cmdUnmap.lenVirtualBuffer = lenBulkMem;
509
510    // Notify MC about the availability of a new command inside the MCP buffer
511    notify(SID_MCP);
512
513    // Wait till response from MC is available
514    if (!waitMcpNotification()) {
515        return MC_DRV_ERR_DAEMON_MCI_ERROR;
516    }
517
518    // Check if the command response ID is correct
519    if (mcpMessage->rspHeader.rspId != (MC_MCP_CMD_UNMAP | FLAG_RESPONSE)) {
520        LOG_E("CMD_OPEN_SESSION got invalid MCP response");
521        return MC_DRV_ERR_DAEMON_MCI_ERROR;
522    }
523
524    uint32_t mcRet = mcpMessage->rspUnmap.rspHeader.result;
525
526    if (mcRet != MC_MCP_RET_OK) {
527        LOG_E("MCP UNMAP returned code %d.", mcRet);
528        return MAKE_MC_DRV_MCP_ERROR(mcRet);
529    } else {
530        // Just remove the buffer
531        // TODO-2012-09-06-haenellu: Haven't we removed it already?
532        if (!ts->removeBulkBuff(handle))
533            LOG_I("unmapBulk(): no buffer found found with handle=%u", handle);
534    }
535
536    return MC_DRV_OK;
537}
538
539
540//------------------------------------------------------------------------------
541void MobiCoreDevice::donateRam(const uint32_t donationSize)
542{
543    // Donate additional RAM to the MobiCore
544    CWsm_ptr ram = allocateContiguousPersistentWsm(donationSize);
545    if (NULL == ram) {
546        LOG_E("Allocation of additional RAM failed");
547        return;
548    }
549    ramType_t ramType = RAM_GENERIC;
550    addr_t adrBuffer = ram->physAddr;
551    const uint32_t numPages = donationSize / (4 * 1024);
552
553
554    LOG_I("donateRam(): adrBuffer=%p, numPages=%d, ramType=%d",
555          adrBuffer,
556          numPages,
557          ramType);
558
559    do {
560        // Write MCP open message to buffer
561        mcpMessage->cmdDonateRam.cmdHeader.cmdId = MC_MCP_CMD_DONATE_RAM;
562        mcpMessage->cmdDonateRam.adrBuffer = (uint32_t) adrBuffer;
563        mcpMessage->cmdDonateRam.numPages = numPages;
564        mcpMessage->cmdDonateRam.ramType = ramType;
565
566        // Notify MC about a new command inside the MCP buffer
567        notify(SID_MCP);
568
569        // Wait till response from MC is available
570        if (!waitMcpNotification()) {
571            break;
572        }
573
574        // Check if the command response ID is correct
575        if ((MC_MCP_CMD_DONATE_RAM | FLAG_RESPONSE) != mcpMessage->rspHeader.rspId) {
576            LOG_E("donateRam(): CMD_DONATE_RAM got invalid MCP response - rspId is: %d",
577                  mcpMessage->rspHeader.rspId);
578            break;
579        }
580
581        uint32_t mcRet = mcpMessage->rspDonateRam.rspHeader.result;
582        if (MC_MCP_RET_OK != mcRet) {
583            LOG_E("donateRam(): CMD_DONATE_RAM error %d", mcRet);
584            break;
585        }
586
587        LOG_I("donateRam() succeeded.");
588
589    } while (0);
590}
591
592//------------------------------------------------------------------------------
593mcResult_t MobiCoreDevice::getMobiCoreVersion(
594    mcDrvRspGetMobiCoreVersionPayload_ptr pRspGetMobiCoreVersionPayload
595)
596{
597    // If MobiCore version info already fetched.
598    if (mcVersionInfo != NULL) {
599        pRspGetMobiCoreVersionPayload->versionInfo = *mcVersionInfo;
600        return MC_DRV_OK;
601        // Otherwise, fetch it via MCP.
602    } else {
603        // Write MCP unmap command to buffer
604        mcpMessage->cmdGetMobiCoreVersion.cmdHeader.cmdId = MC_MCP_CMD_GET_MOBICORE_VERSION;
605
606        // Notify MC about the availability of a new command inside the MCP buffer
607        notify(SID_MCP);
608
609        // Wait till response from MC is available
610        if (!waitMcpNotification()) {
611            return MC_DRV_ERR_DAEMON_MCI_ERROR;
612        }
613
614        // Check if the command response ID is correct
615        if ((MC_MCP_CMD_GET_MOBICORE_VERSION | FLAG_RESPONSE) != mcpMessage->rspHeader.rspId) {
616            LOG_E("MC_MCP_CMD_GET_MOBICORE_VERSION got invalid MCP response");
617            return MC_DRV_ERR_DAEMON_MCI_ERROR;
618        }
619
620        uint32_t  mcRet = mcpMessage->rspGetMobiCoreVersion.rspHeader.result;
621
622        if (mcRet != MC_MCP_RET_OK) {
623            LOG_E("MC_MCP_CMD_GET_MOBICORE_VERSION error %d", mcRet);
624            return MAKE_MC_DRV_MCP_ERROR(mcRet);
625        }
626
627        pRspGetMobiCoreVersionPayload->versionInfo = mcpMessage->rspGetMobiCoreVersion.versionInfo;
628
629        // Store MobiCore info for future reference.
630        mcVersionInfo = new mcVersionInfo_t();
631        *mcVersionInfo = pRspGetMobiCoreVersionPayload->versionInfo;
632        return MC_DRV_OK;
633    }
634}
635
636//------------------------------------------------------------------------------
637void MobiCoreDevice::queueUnknownNotification(
638    notification_t notification
639)
640{
641    notifications.push(notification);
642}
643
644/** @} */
645