diff --git a/framework/subsystems/ogsmd/modems/__init__.py b/framework/subsystems/ogsmd/modems/__init__.py
index c52851b..88904fe 100644
--- a/framework/subsystems/ogsmd/modems/__init__.py
+++ b/framework/subsystems/ogsmd/modems/__init__.py
@@ -24,6 +24,7 @@ modemmap = { \
     "muxed4line":        "Muxed4Line",
     "option":            "Option",
     "qualcomm_msm":      "QualcommMsm",
+    "qualcomm_msm_cdma": "QualcommMsmCdma",
     "sierra":            "Sierra",
     "singleline":        "SingleLine",
     "ti_calypso":        "TiCalypso",
@@ -65,6 +66,9 @@ def modemFactory( modemtype ):
     elif modemtype == "qualcomm_msm":
         from qualcomm_msm.modem import QualcommMsm as Modem
         import qualcomm_msm.mediator as mediator
+    elif modemtype == "qualcomm_msm_cdma":
+        from qualcomm_msm_cdma.modem import QualcommMsmCdma as Modem
+        import qualcomm_msm_cdma.mediator as mediator
     else:
         assert False, "must never reach this"
         sys.exit( -1 )
diff --git a/framework/subsystems/ogsmd/modems/abstractcdma/__init__.py b/framework/subsystems/ogsmd/modems/abstractcdma/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/framework/subsystems/ogsmd/modems/abstractcdma/calling.py b/framework/subsystems/ogsmd/modems/abstractcdma/calling.py
new file mode 100644
index 0000000..2c24278
--- /dev/null
+++ b/framework/subsystems/ogsmd/modems/abstractcdma/calling.py
@@ -0,0 +1,304 @@
+#!/usr/bin/env python
+"""
+freesmartphone.org ogsmd - Python Implementation
+
+(C) 2008-2009 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
+(C) 2008-2009 Openmoko, Inc.
+GPLv2 or later
+
+Package: ogsmd.modems.abstractcdma
+Module: calling
+
+New style abstract call handling
+"""
+
+__version__ = "0.9.1.2"
+MODULE_NAME = "ogsmd.callhandler"
+
+from ogsmd import error
+from ogsmd.gsm import const
+
+import logging
+logger = logging.getLogger( MODULE_NAME )
+
+#=========================================================================#
+class CallHandler( object ):
+#=========================================================================#
+
+    _instance = None
+
+    @classmethod
+    def getInstance( klass, dbus_object=None ):
+        if klass._instance is None and dbus_object is not None:
+            klass._instance = CallHandler( dbus_object )
+        return klass._instance
+
+    def __init__( self, dbus_object ):
+        self._object = dbus_object
+        self._calls = {}
+        self._calls[1] = { "status": "release" }
+        self._calls[2] = { "status": "release" }
+
+        self.unsetHook()
+
+    def setHook( self, hook ):
+        self._hook = hook
+
+    def unsetHook( self ):
+        self._hook = lambda *args, **kwargs: None
+
+    def isBusy( self ):
+        return self._calls[1]["status"] != "release" or self._calls[2]["status"] != "release"
+
+    def status( self ):
+        return self._calls[1]["status"], self._calls[2]["status"]
+
+    #
+    # called from mediators
+    #
+
+    def initiate( self, dialstring, commchannel ):
+        result = self.feedUserInput( "initiate", dialstring, commchannel )
+        self._hook( "initiate", result )
+        return result
+
+    def activate( self, index, commchannel ):
+        result = self.feedUserInput( "activate", index=index, channel=commchannel )
+        self._hook( "activate", result )
+        return result
+
+    def release( self, index, commchannel ):
+        result = self.feedUserInput( "release", index=index, channel=commchannel )
+        self._hook( "release", result )
+        return result
+
+    def releaseAll( self, commchannel ):
+        result = self.feedUserInput( "dropall", channel=commchannel )
+        self._hook( "dropall", result )
+        return result
+
+    def hold( self, commchannel ):
+        result = self.feedUserInput( "hold", channel=commchannel )
+        self._hook( "hold", result )
+        return result
+
+    #
+    # called from unsolicited response delegates
+    #
+
+    def ring( self ):
+        for callId, info in self._calls.items():
+            if info["status"] == "incoming":
+                self._updateStatus( callId )
+                break # can't be more than one call incoming at once (GSM limitation)
+                # FIXME is the above comment really true?
+
+    def statusChangeFromNetwork( self, callId, info ):
+        lastStatus = self._calls[callId].copy()
+        self._calls[callId].update( info )
+
+        if self._calls[callId]["status"] == "release":
+            # release signal always without properties
+            self._calls[callId] = { "status": "release" }
+
+        if self._calls[callId]["status"] != "incoming":
+            # suppress sending the same signal twice
+            if lastStatus != self._calls[callId]:
+                self._updateStatus( callId )
+        else:
+            self._updateStatus( callId )
+
+    def statusChangeFromNetworkByStatus( self, status, info ):
+        calls = [call for call in self._calls.items() if call[1]["status"] == status]
+        if not len(calls) == 1:
+            raise error.InternalException( "non-unique call state '%'" % status )
+        self.statusChangeFromNetwork( calls[0][0], info )
+
+    #
+    # internal
+    #
+
+    def _updateStatus( self, callId ):
+        """send dbus signal indicating call status for a callId"""
+        self._object.CallStatus( callId, self._calls[callId]["status"], self._calls[callId] )
+
+    def feedUserInput( self, action, *args, **kwargs ):
+        # simple actions
+        # FIXME might rather want to consider using the state machine, since that would be more clear
+        if action == "dropall":
+            kwargs["channel"].enqueue( 'H' )
+            return True
+        try:
+            state = "state_%s_%s" % ( self._calls[1]["status"], self._calls[2]["status"] )
+            method = getattr( self, state )
+        except AttributeError:
+            logger.exception( "unhandled state '%s' in state machine. calls are %s" % ( state, repr(self._calls) ) )
+            raise error.InternalException( "unhandled state '%s' in state machine. calls are %s" % ( state, repr(self._calls) ) )
+        else:
+            return method( action, *args, **kwargs )
+
+    #
+    # deal with responses from call control commands
+    #
+    def responseFromChannel( self, request, response ):
+        logger.debug( "response from channel to %s = %s", request, response )
+
+    def errorFromChannel( self, request, response ):
+        logger.error( "error from channel to %s = %s", request, response )
+
+    #
+    # synchronize status
+    #
+    def syncStatus( self, request, response ):
+        CallListCalls( Object.instance(), self.syncStatus_ok, self.syncStatus_err )
+
+    def syncStatus_ok( self, calls ):
+        if len( calls ) > 1:
+            logger.warning( "unhandled case" )
+            return
+        # synthesize status change from network
+        callid, status, properties = calls[0]
+        self.statusChangeFromNetwork( callid, {"status": status} )
+
+    def syncStatus_err( self, request, error ):
+        logger.error( "error from channel to %s = %s", request, error )
+
+    #
+    # state machine actions following. micro states:
+    #
+    # release: line idle, call has been released
+    # incoming: remote party is calling, network is alerting us
+    # outgoing: local party is calling, network is alerting remote party
+    # active: local and remote party talking
+    # held: remote party held
+
+    # An important command here is +CHLD=<n>
+    # <n>  Description
+    # -----------------
+    #  0   Release all held calls or set the busy state for the waiting call.
+    #  1   Release all active calls.
+    #  1x  Release only call x.
+    #  2   Put active calls on hold (and activate the waiting or held call).
+    #  2x  Put active calls on hold and activate call x.
+    #  3   Add the held calls to the active conversation.
+    #  4   Add the held calls to the active conversation, and then detach the local subscriber from the conversation.
+
+    #
+    # action with 1st call, 2nd call released
+    #
+    def state_release_release( self, action, *args, **kwargs ):
+        if action == "initiate":
+            dialstring, commchannel = args
+            commchannel.enqueue( "D%s" % dialstring, self.responseFromChannel, self.errorFromChannel )
+            return 1
+
+    def state_incoming_release( self, action, *args, **kwargs ):
+        if action == "release" and kwargs["index"] == 1:
+            kwargs["channel"].enqueue( 'H' )
+            return True
+        elif action == "activate" and kwargs["index"] == 1:
+            # FIXME handle data calls here
+            kwargs["channel"].enqueue( 'A' )
+            return True
+
+    def state_outgoing_release( self, action, *args, **kwargs ):
+        if action == "release" and kwargs["index"] == 1:
+            command = self._object.modem.data( "cancel-outgoing-call" )
+            kwargs["channel"].enqueue( command )
+            return True
+
+    def state_active_release( self, action, *args, **kwargs ):
+        if action == "release" and kwargs["index"] == 1:
+            kwargs["channel"].enqueue( 'H' )
+            return True
+        elif action == "hold":
+            # put active call on hold without accepting any waiting or held
+            # this is not supported by all modems / networks
+            self.channel = kwargs["channel"]
+            kwargs["channel"].enqueue( "+CHLD=2", self.syncStatus )
+            return True
+
+    # FIXME add state_release_active
+
+    def state_held_release( self, action, *args, **kwargs ):
+        # state not supported by all modems
+        if action == "release" and kwargs["index"] == 1:
+            kwargs["channel"].enqueue( 'H' )
+            return True
+        elif action == "activate" and kwargs["index"] == 1:
+            # activate held call
+            self.channel = kwargs["channel"]
+            kwargs["channel"].enqueue( "+CHLD=2", self.syncStatus )
+            return True
+
+    #
+    # 1st call active, 2nd call call incoming or on hold
+    #
+    def state_active_incoming( self, action, *args, **kwargs ):
+        if action == "release":
+            if kwargs["index"] == 1:
+                # release active call, waiting call becomes active
+                kwargs["channel"].enqueue( "+CHLD=1" )
+                return True
+            elif kwargs["index"] == 2:
+                # reject waiting call, sending busy signal
+                kwargs["channel"].enqueue( "+CHLD=0" )
+                return True
+        elif action == "activate":
+            if kwargs["index"] == 2:
+                # put active call on hold, take waiting call
+                kwargs["channel"].enqueue( "+CHLD=2" )
+                return True
+        elif action == "conference":
+            # put active call on hold, take waiting call, add held call to conversation
+            kwargs["channel"].enqueue( "+CHLD=2;+CHLD=3" )
+            return True
+
+    def state_active_held( self, action, *args, **kwargs ):
+        if action == "release":
+            if kwargs["index"] == 1:
+                # release active call, (auto)activate the held call
+                kwargs["channel"].enqueue( "+CHLD=11" )
+                return True
+            elif kwargs["index"] == 2:
+                # release held call
+                kwargs["channel"].enqueue( "+CHLD=12" )
+                return True
+        elif action == "activate":
+            if kwargs["index"] == 2:
+                # put active call on hold, activate held call
+                kwargs["channel"].enqueue( "+CHLD=2" )
+                return True
+        elif action == "conference":
+            kwargs["channel"].enqueue( "+CHLD=3" )
+            return True
+        elif action == "connect":
+            kwargs["channel"].enqueue( "+CHLD=4" )
+            return True
+
+    def state_held_active( self, action, *args, **kwargs ):
+        # should be the same as the reversed state
+        return state_active_held( self, action, *args, **kwargs )
+
+    # both calls active
+    def state_active_active( self, action, *args, **kwargs ):
+        if action == "release":
+            if kwargs["index"] == 1:
+                # release only call 1
+                kwargs["channel"].enqueue( "+CHLD=11" )
+                return True
+            elif kwargs["index"] == 2:
+                kwargs["channel"].enqueue( "+CHLD=12" )
+                return True
+        elif action == "activate":
+            if kwargs["index"] == 1:
+                # put 2nd call on hold
+                kwargs["channel"].enqueue( "+CHLD=21" )
+                return True
+            elif kwargs["index"] == 2:
+                # put 1st call on hold
+                kwargs["channel"].enqueue( "+CHLD=22" )
+                return True
+        elif action == "connect":
+            kwargs["channel"].enqueue( "+CHLD=4" )
+            return True
diff --git a/framework/subsystems/ogsmd/modems/abstractcdma/channel.py b/framework/subsystems/ogsmd/modems/abstractcdma/channel.py
new file mode 100644
index 0000000..c5966bc
--- /dev/null
+++ b/framework/subsystems/ogsmd/modems/abstractcdma/channel.py
@@ -0,0 +1,174 @@
+#!/usr/bin/env python
+"""
+The Open GSM Daemon - Python Implementation
+
+(C) 2007-2008 M. Dietrich
+(C) 2008 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
+(C) 2008 Openmoko, Inc.
+GPLv2 or later
+
+Package: ogsmd.modems.abstractcdma
+Module: channel
+"""
+
+__version__ = "0.9.1"
+MODULE_NAME = "ogsmd.modems.abstractcmda.channel"
+
+from ogsmd.gsm.decor import logged
+from ogsmd.gsm.channel import AtCommandChannel
+import gobject
+
+import logging
+logger = logging.getLogger( MODULE_NAME )
+
+#=========================================================================#
+class AbstractCdmaModemChannel( AtCommandChannel ):
+#=========================================================================#
+
+    def __init__( self, *args, **kwargs ):
+        AtCommandChannel.__init__( self, *args, **kwargs )
+        self.callback = None
+
+        # NOTE: might make it a weak-reference, so that garbage collection
+        #       does not get disturbed by the cirular modem/channel reference
+        self._modem = kwargs["modem"]
+        self._commands = {}
+        self._populateCommands()
+        self._sendCommands( "init" )
+
+        # FIXME add warm start handling (querying CFUN and CPIN status) here:
+        # 1. Query CFUN to check whether we are powered
+        # 2. Query CPIN to check whether we are READY
+        # 3. Try to read a message or a phonebook entry
+        # 4. If that works, send the SIM ready signal
+
+    def modemStateAntennaOn( self ):
+        """
+        Called, when the modem signalizes the antenna being powered on.
+        """
+        self._sendCommands( "antenna" )
+
+    def modemStateSimUnlocked( self ):
+        """
+        Called, when the modem signalizes the SIM being unlocked.
+
+        Override this in your concrete class to issue sending
+        org.freesmartphone.GSM.SIM.ReadyStatus( true ) eventually.
+        """
+
+        # FIXME we might want to make this a 'pure virtual' method
+
+        # don't hammer modem too early with the additional commands
+        # FIXME it's actually modem specific whether we can send the command directly
+        # after +CPIN: READY or not, so we should not have this here
+        gobject.timeout_add_seconds( 10, self._sendCommands, "sim" )
+
+    def modemStateSimReady( self ):
+        """
+        Called, when the modem signalizes the SIM data can be read.
+        """
+
+        # we don't have sim so it is always ready and we don't send any sim commands
+        # gobject.timeout_add_seconds( 1, self._sendCommands, "sim" )
+
+    def suspend( self, ok_callback, error_callback ):
+        """
+        Called, when the channel needs to configure the modem for suspending.
+        """
+        def done( request, response, self=self, ok_callback=ok_callback ):
+            ok_callback( self )
+        self._sendCommandsNotifyDone( "suspend", done )
+
+    def resume( self, ok_callback, error_callback ):
+        def done( request, response, self=self, ok_callback=ok_callback ):
+            ok_callback( self )
+        self._sendCommandsNotifyDone( "resume", done )
+
+    #
+    # internal API
+    #
+    def _sendCommands( self, state ):
+        commands = self._commands[state]
+        if commands:
+            for command in commands:
+                try:
+                    commandstring = command()
+                except TypeError: # not a callable
+                    commandstring = command
+                self.enqueue( commandstring )
+
+    def _sendCommandsNotifyDone( self, state, done_callback ):
+        # FIXME no error handling, just checking when the results are through
+        commands = self._commands[state]
+        if commands:
+            for command in commands[:-1]:
+                try:
+                    commandstring = command()
+                except TypeError: # not a callable
+                    commandstring = command
+                self.enqueue( commandstring )
+            command = commands[-1]
+            try:
+                commandstring = command()
+            except TypeError:
+                commandstring = command
+            self.enqueue( commandstring, done_callback, done_callback )
+        else:
+            done_callback( "", "" )
+
+    def _populateCommands( self ):
+        """
+        Populate the command queues to be sent on modem state changes.
+        """
+
+        c = []
+        # reset
+        c.append( '' )                  # wakeup
+        c.append( 'Z' )                 # soft reset
+        c.append( 'E0V1' )              # echo off, verbose result on
+        # error and result reporting reporting
+        c.append( '+CMEE=1' )           # report mobile equipment errors: in numerical format
+        c.append( '+CRC=1' )            # cellular result codes: enable extended format
+#       c.append( '+CSCS="UCS2"' )      # character set conversion: use UCS2
+#       c.append( '+CSDH=1' )           # show text mode parameters: show values
+#       c.append( '+CSNS=0' )           # single numbering scheme: voice
+        # sms
+#       c.append( '+CMGF=0' )           # message format: enable pdu mode, disable text mode
+        # unsolicited
+        c.append( '+CLIP=0' )           # calling line identification presentation: disable
+        c.append( '+COLP=0' )           # connected line identification presentation: disable
+        c.append( '+CCWA=0' )           # call waiting: disable
+        self._commands["init"] = c
+
+        c = []
+#       c.append( '+CSMS=1' )           # GSM Phase 2+ commands: enable
+
+
+#       We don't have a sim card, commenting this out for now
+
+#       def sms_and_cb( self=self ):
+#           if self._modem.data( "sim-buffers-sms" ):
+#               return "+CNMI=%s" % self._modem.data( "sms-buffered-cb" )
+#           else:
+#               return "+CNMI=%s" % self._modem.data( "sms-direct-cb" )
+#       c.append( sms_and_cb )
+        self._commands["sim"] = c
+
+        c = []
+        self._commands["antenna"] = c
+
+        c = []
+        self._commands["suspend"] = c
+
+        c = []
+        self._commands["resume"] = c
+
+    def setIntermediateResponseCallback( self, callback ):
+        assert self.callback is None, "callback already set"
+        self.callback = callback
+
+    def handleUnsolicitedResponse( self, response ):
+        if self.callback is not None:
+            self.callback( response )
+        else:
+            logger.warning( "UNHANDLED INTERMEDIATE: %s", response )
diff --git a/framework/subsystems/ogsmd/modems/abstractcdma/mediator.py b/framework/subsystems/ogsmd/modems/abstractcdma/mediator.py
new file mode 100644
index 0000000..aa4ba0a
--- /dev/null
+++ b/framework/subsystems/ogsmd/modems/abstractcdma/mediator.py
@@ -0,0 +1,1733 @@
+##!/usr/bin/env python
+"""
+The Open GSM Daemon - Python Implementation
+
+(C) 2008-2009 Daniel Willmann <daniel@totalueberwachung.de>
+(C) 2008-2009 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
+(C) 2008-2009 Openmoko, Inc.
+GPLv2 or later
+
+Package: ogsmd.modems.abstractcdma
+Module: mediator
+
+TODO:
+ * refactor to using yield more often
+ * refactor to using more regexps
+ * refactor modem error handling (not command error handling), this is not
+   something we need to do for each and every command. Might do it for the yield
+   stuff, then gradually migrate functions to yield
+ * decouple from calling dbus result, we might want to reuse these functions in
+   non-exported methods as well
+ * recover from traceback in parsing / compiling result code
+ * refactor parameter validation
+"""
+
+__version__ = "0.9.19.0"
+MODULE_NAME = "ogsmd.modems.abstractcdma.mediator"
+
+from ogsmd import error as DBusError
+from ogsmd.gsm import const, convert
+from ogsmd.gsm.decor import logged
+from ogsmd.helpers import safesplit
+from ogsmd.modems import currentModem
+import ogsmd.gsm.sms
+
+import gobject
+import re, time, calendar
+
+import logging
+logger = logging.getLogger( MODULE_NAME )
+
+#=========================================================================#
+class AbstractMediator( object ):
+#=========================================================================#
+    @logged
+    def __init__( self, dbus_object, dbus_ok, dbus_error, **kwargs ):
+        assert self.__class__.__name__ != "AbstractMediator", "can not instanciate abstract base class"
+        self._object = dbus_object
+        self._ok = dbus_ok
+        self._error = dbus_error
+        self.__dict__.update( **kwargs )
+        self._commchannel = None
+
+    def trigger( self ):
+        assert False, "pure virtual function called"
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        if response[-1].startswith( "ERROR" ) or response[-1].startswith( "NO CARRIER" ):
+            self._error( DBusError.DeviceFailed( "command %s failed" % request ) )
+        elif response[-1].startswith( "+CM" ) or response[-1].startswith( "+EXT" ):
+            self._handleCmeCmsExtError( response[-1] )
+        elif response[-1].startswith( "OK" ):
+            self._ok()
+        else:
+            assert False, "should never reach that"
+
+    @logged
+    def errorFromChannel( self, request, err ):
+        category, details = err
+        if category == "timeout":
+            self._error( DBusError.DeviceTimeout( "device did not answer within %d seconds" % details ) )
+        else:
+            self._error( DBusError.DeviceFailed( "%s: %s" % ( category, repr(details ) ) ) )
+
+    @logged
+    def __del__( self, *args, **kwargs ):
+        pass
+
+    def _rightHandSide( self, line ):
+        try:
+            result = line.split( ':', 1 )[1]
+        except IndexError:
+            result = line
+        return result.strip()
+
+    # FIXME compute errors based on actual class name to ease generic error parsing. Examples:
+    #       1.) CME 3 ("Not allowed") is sent upon trying to
+    #       register to a network, as well as trying to read a phonebook
+    #       entry from the SIM with an index out of bounds -- we must
+    #       not map these two to the same org.freesmartphone.GSM error.
+    #       2.) CME 32 ("Network not allowed") => SimBlocked is sent if we
+    #       are not already registered. This may be misleading.
+
+
+    @logged
+    def _handleCmeCmsExtError( self, line ):
+        category, text = const.parseError( line )
+        code = int( line.split( ':', 1 )[1] )
+        e = DBusError.DeviceFailed( "Unhandled %s ERROR: %s" % ( category, text ) )
+
+        if category == "CME":
+            if code == 3:
+                # seen as result of +COPS=0 or +CLCK=... w/ auth state = SIM PIN
+                # seen as result of +CPBR w/ index out of bounds
+                e = DBusError.NetworkUnauthorized()
+            elif code == 4:
+                # seen as result of +CCFC=4,2
+                e = DBusError.NetworkNotSupported()
+            elif code == 10:
+                e = DBusError.SimNotPresent()
+            elif code == 16:
+                e = DBusError.SimAuthFailed( "SIM Authorization code not accepted" )
+            elif code in ( 21, 22 ): # invalid phonebook index, phonebook entry not found
+                e = DBusError.SimInvalidIndex()
+            elif code == 30:
+                e = DBusError.NetworkNotPresent()
+            elif code in ( 32, 262 ): # 32 if SIM card is not activated
+                e = DBusError.SimBlocked( text )
+            elif code in ( 5, 6, 7, 11, 12, 15, 17, 18, 48 ):
+                e = DBusError.SimAuthFailed( text )
+            elif code == 100:
+                e = DBusError.SimNotReady( "Antenna powered off or SIM not unlocked yet" )
+            # TODO launch idle task that sends an new auth status signal
+
+        elif category == "CMS":
+            if code == 310:
+                e = DBusError.SimNotPresent()
+            elif code in ( 311, 312, 316, 317, 318 ):
+                e = DBusError.SimAuthFailed()
+            elif code == 321: # invalid message index
+                e = DBusError.SimNotFound()
+            elif code == 322:
+                e = DBusError.SimMemoryFull()
+
+        elif category == "EXT":
+            if code == 0:
+                if isinstance( self, SimMediator ):
+                    e = DBusError.SimInvalidIndex() # invalid parameter on phonebook index e.g.
+                else:
+                    e = DBusError.InvalidParameter()
+
+        else:
+            assert False, "should never reach that"
+
+        self._error( e )
+
+#=========================================================================#
+class AbstractYieldSupport( object ):
+#=========================================================================#
+    """
+    This class adds support for simplifying control flow
+    by using Python generators to implement coroutines.
+    By inheriting from this class, you can use the following syntax:
+
+    def trigger( self ):
+        for iteration in ( 1,2,3,4 ):
+            request, response, error = yield( "+CFUN=1" )
+            if error is None:
+                self._ok( response )
+            else:
+                self.errorFromChannel( request, error )
+    """
+
+    def __init__( self, *args, **kwargs ):
+        self.generator = self.trigger()
+        if self.generator is not None:
+            toEnqueue = self.generator.next()
+            if type( toEnqueue ) == type( tuple() ):
+                command, prefixes = toEnqueue
+                self._commchannel.enqueue( command, self.genResponseFromChannel, self.genErrorFromChannel, prefixes )
+            else:
+                self._commchannel.enqueue( toEnqueue, self.genResponseFromChannel, self.genErrorFromChannel )
+
+    def trigger( self ):
+        assert False, "pure virtual method called"
+
+    @logged
+    def genResponseFromChannel( self, request, response ):
+        try:
+            toEnqueue = self.generator.send( ( request, response, None ) )
+        except StopIteration:
+            pass
+        else:
+            if type( toEnqueue ) == type( tuple() ):
+                command, prefixes = toEnqueue
+                self._commchannel.enqueue( command, self.genResponseFromChannel, self.genErrorFromChannel, prefixes )
+            else:
+                self._commchannel.enqueue( toEnqueue, self.genResponseFromChannel, self.genErrorFromChannel )
+
+    @logged
+    def genErrorFromChannel( self, request, error ):
+        try:
+            toEnqueue = self.generator.send( ( request, None, error ) )
+        except StopIteration:
+            pass
+        else:
+            if type( toEnqueue ) == type( tuple() ):
+                command, prefixes = toEnqueue
+                self._commchannel.enqueue( command, self.genResponseFromChannel, self.genErrorFromChannel, prefixes )
+            else:
+                self._commchannel.enqueue( toEnqueue, self.genResponseFromChannel, self.genErrorFromChannel )
+
+#=========================================================================#
+class DeviceMediator( AbstractMediator, AbstractYieldSupport ):
+#=========================================================================#
+    def __init__( self, *args, **kwargs ):
+        AbstractMediator.__init__( self, *args, **kwargs )
+        # this is a bit ugly, but how should we get the channel elsewhere?
+        self._commchannel = self._object.modem.communicationChannel( "DeviceMediator" )
+        AbstractYieldSupport.__init__( self, *args, **kwargs )
+
+#=========================================================================#
+class SimMediator( AbstractMediator, AbstractYieldSupport ):
+#=========================================================================#
+    def __init__( self, *args, **kwargs ):
+        AbstractMediator.__init__( self, *args, **kwargs )
+        # this is a bit ugly, but how should we get the channel elsewhere?
+        self._commchannel = self._object.modem.communicationChannel( "SimMediator" )
+        AbstractYieldSupport.__init__( self, *args, **kwargs )
+
+#=========================================================================#
+class SmsMediator( AbstractMediator, AbstractYieldSupport ):
+#=========================================================================#
+    def __init__( self, *args, **kwargs ):
+        AbstractMediator.__init__( self, *args, **kwargs )
+        # this is a bit ugly, but how should we get the channel elsewhere?
+        self._commchannel = self._object.modem.communicationChannel( "SmsMediator" )
+        AbstractYieldSupport.__init__( self, *args, **kwargs )
+
+#=========================================================================#
+class NetworkMediator( AbstractMediator, AbstractYieldSupport ):
+#=========================================================================#
+    def __init__( self, *args, **kwargs ):
+        AbstractMediator.__init__( self, *args, **kwargs )
+        # this is a bit ugly, but how should we get the channel elsewhere?
+        self._commchannel = self._object.modem.communicationChannel( "NetworkMediator" )
+        AbstractYieldSupport.__init__( self, *args, **kwargs )
+
+#=========================================================================#
+class CallMediator( AbstractMediator, AbstractYieldSupport ):
+#=========================================================================#
+    def __init__( self, *args, **kwargs ):
+        AbstractMediator.__init__( self, *args, **kwargs )
+        # this is a bit ugly, but how should we get the channel elsewhere?
+        self._commchannel = self._object.modem.communicationChannel( "CallMediator" )
+        AbstractYieldSupport.__init__( self, *args, **kwargs )
+
+#=========================================================================#
+class PdpMediator( AbstractMediator, AbstractYieldSupport ):
+#=========================================================================#
+    def __init__( self, *args, **kwargs ):
+        AbstractMediator.__init__( self, *args, **kwargs )
+        # this is a bit ugly, but how should we get the channel elsewhere?
+        self._commchannel = self._object.modem.communicationChannel( "PdpMediator" )
+        AbstractYieldSupport.__init__( self, *args, **kwargs )
+
+#=========================================================================#
+class CbMediator( AbstractMediator, AbstractYieldSupport ):
+#=========================================================================#
+    def __init__( self, *args, **kwargs ):
+        AbstractMediator.__init__( self, *args, **kwargs )
+        # this is a bit ugly, but how should we get the channel elsewhere?
+        self._commchannel = self._object.modem.communicationChannel( "CbMediator" )
+        AbstractYieldSupport.__init__( self, *args, **kwargs )
+
+#=========================================================================#
+class DebugMediator( AbstractMediator, AbstractYieldSupport ):
+#=========================================================================#
+    def __init__( self, *args, **kwargs ):
+        AbstractMediator.__init__( self, *args, **kwargs )
+        # this is a bit ugly, but how should we get the channel elsewhere?
+        self._commchannel = self._object.modem.communicationChannel( "DebugMediator" )
+        AbstractYieldSupport.__init__( self, *args, **kwargs )
+
+#=========================================================================#
+class MonitorMediator( AbstractMediator, AbstractYieldSupport ):
+#=========================================================================#
+    def __init__( self, *args, **kwargs ):
+        AbstractMediator.__init__( self, *args, **kwargs )
+        # this is a bit ugly, but how should we get the channel elsewhere?
+        self._commchannel = self._object.modem.communicationChannel( "MonitorMediator" )
+        AbstractYieldSupport.__init__( self, *args, **kwargs )
+
+#
+# import singletons
+#
+from .calling import CallHandler
+from .pdp import Pdp
+
+#
+# Device Mediators
+#
+
+#=========================================================================#
+class DeviceGetInfo( DeviceMediator ):
+#=========================================================================#
+    def trigger( self ):
+        # According to GSM 07.07, it's legal to not answer quoting the prefixes for these four informational
+        # requests, hence we allow all prefixes. NOTE: Yes, this opens a slight possibility of unsolicited
+        # creeping unnoticed into. To fix this properly, we would need to enhance the prefixmap to also specify
+        # something like: [ "+CGMR", "+CGMM", "+CGMI", "+CGSN", "plaintext" ], "plaintext" being everything
+        # else that does _not_ look like a response.
+        self._commchannel.enqueue( "+CGMR;+CGMM;+CGMI;+CGSN", self.responseFromChannel, self.errorFromChannel, prefixes=[""] )
+
+    def responseFromChannel( self, request, response ):
+        if response[-1] != "OK":
+            DeviceMediator.responseFromChannel( self, request, response )
+        else:
+            result = {}
+            if len( response ) > 1:
+                result["revision"] = self._rightHandSide( response[0] )
+            if len( response ) > 2:
+                result["model"] = self._rightHandSide( response[1] )
+            if len( response ) > 3:
+                result["manufacturer"] = self._rightHandSide( response[2] )
+            if len( response ) > 4:
+                result["imei"] = self._rightHandSide( response[3] )
+            self._ok( result )
+
+#=========================================================================#
+class DeviceGetAntennaPower( DeviceMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+CFUN?", self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        if ( response[-1] == "OK" ):
+            self._ok( not self._rightHandSide( response[0] ) == "0" )
+        else:
+            DeviceMediator.responseFromChannel( self, request, response )
+
+#=========================================================================#
+class DeviceSetAntennaPower( DeviceMediator ):
+#=========================================================================#
+
+    def trigger( self ):
+       self._commchannel.enqueue( "+CFUN=%d" % self.power, self.responseFromChannel, self.errorFromChannel, timeout=currentModem().timeout("CFUN") )
+
+#=========================================================================#
+class DeviceGetFeatures( DeviceMediator ):
+#=========================================================================#
+    def trigger( self ):
+        result = {}
+        request, response, error = yield( "+GCAP", [""] ) # free format allowed as per GSM 07.07
+        if error is None and response[-1] == "OK":
+            if "GSM" in response[0]:
+                result["GSM"] = "TA" # terminal adapter
+            else:
+                result["GSM"] = "?" # some modems lie about their GSM capabilities
+
+            if "FCLASS" in response[0]:
+                result["FAX"] = "" # basic capability, checking for details in a second
+
+        request, response, error = yield( "+FCLASS?", [""] ) # free format allowed as per GSM 07.07
+
+        if error is None and response[-1] == "OK":
+            result["FAX"] = self._rightHandSide( response[0] ).strip( '"' )
+
+        request, response, error = yield( "+CGCLASS?", [""] ) # free format allowed as per GSM 07.07
+
+        if error is None and response[-1] == "OK":
+            result["GPRS"] = self._rightHandSide( response[0] ).strip( '"' )
+        else:
+            result["GPRS"] = "?"
+
+        self._ok( result )
+
+#=========================================================================#
+class DeviceGetSimBuffersSms( DeviceMediator ):
+#=========================================================================#
+    def trigger( self ):
+        # CNMI needs to be issued on the unsolicited channel, otherwise +CMT wont go there
+        commchannel = self._object.modem.communicationChannel( "UnsolicitedMediator" )
+        commchannel.enqueue( "+CNMI?", self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        if ( response[-1] == "OK" ):
+            mode, mt, bm, ds, bfr = safesplit( self._rightHandSide( response[0] ), ',' )
+            sim_buffers_sms = ( int( mt ) == 1 )
+            self._ok( sim_buffers_sms )
+        else:
+            DeviceMediator.responseFromChannel( self, request, response )
+
+#=========================================================================#
+class DeviceSetSimBuffersSms( DeviceMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._object.modem.setData( "sim-buffers-sms", self.sim_buffers_sms )
+        if self._object.modem.data( "sim-buffers-sms" ):
+            params = self._object.modem.data( "sms-buffered-cb" )
+        else:
+            params = self._object.modem.data( "sms-direct-cb" )
+        # CNMI needs to be issued on the unsolicited channel, otherwise +CMT wont go there
+        commchannel = self._object.modem.communicationChannel( "UnsolicitedMediator" )
+        commchannel.enqueue( "+CNMI=%s" % params, self.responseFromChannel, self.errorFromChannel )
+
+#=========================================================================#
+class DeviceGetSpeakerVolume( DeviceMediator ):
+#=========================================================================#
+    def trigger( self ):
+        low, high = self._object.modem.data( "speaker-volume-range", ( None, None ) )
+        if low is None:
+            request, response, error = yield( "+CLVL=?" )
+
+            if error is None and response[-1] == "OK":
+                low, high = self._rightHandSide( response[0] ).strip( "()" ).split( '-' )
+                low, high = int(low), int(high)
+                self._object.modem.setData( "speaker-volume-range", ( low, high ) )
+
+            else:
+                # command not supported, assume 0-255
+                self._object.modem.setData( "speaker-volume-range", ( 0, 255 ) )
+
+        # send it
+        request, response, error = yield( "+CLVL?" )
+
+        if response[-1] == "OK" and response[0].startswith( "+CLVL" ):
+            low, high = self._object.modem.data( "speaker-volume-range", ( None, None ) )
+            value = int( self._rightHandSide( response[0] ) ) * 100 / ( high-low )
+            self._ok( value )
+        else:
+            DeviceMediator.responseFromChannel( self, request, response )
+
+#=========================================================================#
+class DeviceSetSpeakerVolume( DeviceMediator ):
+#=========================================================================#
+    def trigger( self ):
+        if 0 <= self.modem_volume <= 100:
+
+            low, high = self._object.modem.data( "speaker-volume-range", ( None, None ) )
+            if low is None:
+                request, response, error = yield( "+CLVL=?" )
+                if error is None and response[-1] == "OK":
+                    low, high = self._rightHandSide( response[0] ).strip( "()" ).split( '-' )
+                    low, high = int(low), int(high)
+                    self._object.modem.setData( "speaker-volume-range", ( low, high ) )
+                else:
+                    # command not supported, assume 0-255
+                    self._object.modem.setData( "speaker-volume-range", ( 0, 255 ) )
+
+            value = low + self.modem_volume * (high-low) / 100
+
+            request, response, error = yield( "+CLVL=%d" % value )
+            if error is not None:
+                self.errorFromChannel( request, error )
+            else:
+                self.responseFromChannel( request, response )
+
+        else:
+            self._error( DBusError.InvalidParameter( "Volume needs to be within [ 0, 100 ]." ) )
+
+#=========================================================================#
+class DeviceGetMicrophoneMuted( DeviceMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+CMUT?", self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        if response[-1] == "OK" and response[0].startswith( "+CMUT" ):
+            value = int( self._rightHandSide( response[0] ) )
+            self._ok( value == 1 )
+        else:
+            DeviceMediator.responseFromChannel( self, request, response )
+
+#=========================================================================#
+class DeviceSetMicrophoneMuted( DeviceMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+CMUT=%d" % self.muted, self.responseFromChannel, self.errorFromChannel )
+
+#=========================================================================#
+class DeviceGetPowerStatus( DeviceMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+CBC", self.responseFromChannel, self.errorFromChannel )
+
+    def responseFromChannel( self, request, response ):
+        if response[-1] != "OK":
+            DeviceMediator.responseFromChannel( self, request, response )
+        else:
+            values = safesplit( self._rightHandSide( response[0] ), ',' )
+            if len( values ) > 0:
+                status = const.DEVICE_POWER_STATUS.get( int(values[0]), "unknown" )
+            else:
+                status = "unknown"
+            if len( values ) > 1:
+                level = int(values[1])
+            else:
+                level = -1
+
+            self._ok( status, level )
+
+#=========================================================================#
+class DeviceSetRTC( DeviceMediator ):
+#=========================================================================#
+    def trigger( self ):
+        # FIXME: gather timezone offset and supply
+        timezone = "+00"
+        timestring = time.strftime("%y/%m/%d,%H:%M:%S" + timezone)
+        self._commchannel.enqueue( "+CCLK=\"%s\"" % timestring, self.responseFromChannel, self.errorFromChannel )
+
+#=========================================================================#
+class DeviceGetRTC( DeviceMediator ): # i
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+CCLK?", self.responseFromChannel, self.errorFromChannel )
+
+    def responseFromChannel( self, request, response ):
+        if response[-1] != "OK":
+            DeviceMediator.responseFromChannel( self, request, response )
+        else:
+            dat, tim = self._rightHandSide( response[0] ).strip( '"' ).split( ',' )
+            # timezone not yet supported
+            if tim[-3] == '+':
+                tim = tim[-3]
+            # some modems strip the leading zero for one-digit chars, hence we need to split and reassemble on our own
+            year, month, day = dat.split( '/' )
+            hour, minute, second = tim.split( ':' )
+
+            timestruct = time.strptime( "%02d/%02d/%02d,%02d:%02d:%02d" % ( int(year), int(month), int(day), int(hour), int(minute), int(second) ), "%y/%m/%d,%H:%M:%S" )
+            self._ok( calendar.timegm( timestruct ) )
+
+#
+# SIM Mediators
+#
+
+#=========================================================================#
+class SimGetAuthStatus( SimMediator ):
+#=========================================================================#
+    # FIXME: Add SIM PIN known/unknown logic here in order to prepare for changing SetAntennaPower() semantics
+    def trigger( self ):
+        self._commchannel.enqueue( "+CPIN?", self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        if response[-1] == "OK":
+            pin_state = self._rightHandSide( response[0] ).strip( '"' ) # some modems include "
+            self._ok( pin_state )
+        else:
+            SimMediator.responseFromChannel( self, request, response )
+
+#=========================================================================#
+class SimSendAuthCode( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( '+CPIN="%s"' % self.code, self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        if response[-1] == "OK":
+            self._ok()
+            # send auth status signal
+            if response[0].startswith( "+CPIN" ):
+                self._object.AuthStatus( self._rightHandSide( response[0] ) )
+        else:
+            SimMediator.responseFromChannel( self, request, response )
+
+#=========================================================================#
+class SimUnlock( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( '+CPIN="%s","%s"' % ( self.puk, self.new_pin ), self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        if response[-1] == "OK":
+            self._ok()
+            # send auth status signal
+            if response[0].startswith( "+CPIN" ):
+                self._object.AuthStatus( self._rightHandSide( response[0] ) )
+        else:
+            SimMediator.responseFromChannel( self, request, response )
+
+#=========================================================================#
+class SimChangeAuthCode( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( '+CPWD="SC","%s","%s"' % ( self.old_pin, self.new_pin ), self.responseFromChannel, self.errorFromChannel )
+
+#=========================================================================#
+class SimGetAuthCodeRequired( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( '+CLCK="SC",2', self.responseFromChannel, self.errorFromChannel )
+
+    def responseFromChannel( self, request, response ):
+        if response[-1] != "OK":
+            SimMediator.responseFromChannel( self, request, response )
+        else:
+            # we only look at the status parameter, we ignore the class parameters
+            values = self._rightHandSide( response[0] ).split( ',' )
+            self._ok( values[0] == "1" )
+
+#=========================================================================#
+class SimSetAuthCodeRequired( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( '+CLCK="SC",%d,"%s"' % ( self.required, self.pin ), self.responseFromChannel, self.errorFromChannel )
+
+#=========================================================================#
+class SimGetSimInfo( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        result = {}
+
+        # imsi
+        request, response, error = yield( "+CIMI", [""] ) # free format allowed as per GSM 07.07
+        if error is not None:
+            self.errorFromChannel( request, error )
+        else:
+            if response[-1] != "OK":
+                SimMediator.responseFromChannel( self, request, response )
+            else:
+                # not using self._rightHandSide() here since some modems
+                # do not include the +CIMI: prefix
+                imsi = result["imsi"] = response[0].replace( "+CIMI: ", "" ).strip( '"' )
+                code, name = const.mccToCountryCode( int( imsi[:3] ) )
+                result["dial_prefix"] = code
+                result["country"] = name
+
+        request, response, error = yield( "+CNUM" )
+
+        if error is not None:
+            self.errorFromChannel( request, error )
+        else:
+            if response[-1] != "OK":
+                # it's perfectly ok for the subscriber number to be not present on the SIM
+                self._ok( result )
+            else:
+                subscriber_numbers = []
+                for line in response[:-1]:
+                    alpha, number, ntype = safesplit( self._rightHandSide( line ), "," )
+                    subscriber_numbers.append( ( alpha.replace( '"', "" ), const.phonebookTupleToNumber( number, int(ntype) ) ) )
+
+                result["subscriber_numbers"] = subscriber_numbers
+                self._ok( result )
+
+#=========================================================================#
+class SimSendGenericSimCommand( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        message = "%d,%s" % ( len( self.command ), self.command )
+        self._commchannel.enqueue( "+CSIM=%s" % message, self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        if response[-1] != "OK":
+            SimMediator.responseFromChannel( self, request, response )
+        else:
+            length, result = safesplit( self._rightHandSide( response[0] ), ',' )
+            self._ok( result )
+
+#=========================================================================#
+class SimSendRestrictedSimCommand( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        message = "%d,%d,%d,%d,%d,%s" % ( self.command, self.fileid, self.p1, self.p2, self.p3, self.data )
+        self._commchannel.enqueue( "+CRSM=%s" % message, self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        if response[-1] != "OK":
+            SimMediator.responseFromChannel( self, request, response )
+        else:
+            values = safesplit( self._rightHandSide( response[0] ), ',' )
+            if len( values ) == 2:
+                result = [ int(values[0]), int(values[1]), "" ]
+            elif len( values ) == 3:
+                result = [ int(values[0]), int(values[1]), values[2] ]
+            else:
+                assert False, "parsing error"
+            self._ok( *result )
+
+#=========================================================================#
+class SimGetHomeZones( SimMediator ): # a(siii)
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+CRSM=176,28512,0,0,123", self.responseFromChannel, self.errorFromChannel )
+
+    def addHomeZone( self, data, number, result ):
+        #print data[0:52]
+        if int( data[0:2], 16 ) == number:
+            x = int( data[2:10], 16 )
+            y = int( data[10:18], 16 )
+            r = int( data[18:26], 16 )
+            nameraw = data[28:52]
+            name = ""
+            for index in xrange( 0, 24, 2 ):
+                c = int(nameraw[index:index+2],16)
+                if 32 < c < 128:
+                    name += chr(c)
+                else:
+                    break
+            if x+y+r:
+                result.append( [ name, x, y, r ] )
+
+    def responseFromChannel( self, request, response ):
+        if response[-1] != "OK":
+            SimMediator.responseFromChannel( self, request, response )
+        try:
+            sw1, sw2, payload = safesplit( self._rightHandSide( response[0] ), "," )
+        except ValueError: # response did not include a payload
+            self._ok( [] )
+        else:
+            if int(sw1) != 144 or int(sw2) != 0: # command succeeded as per GSM 11.11, 9.4.1
+                self._ok( [] )
+            else:
+                result = []
+                for i in xrange( 4 ):
+                    self.addHomeZone( payload[34+52*i:34+52*(i+1)], i+1, result )
+                self._ok( result )
+
+#=========================================================================#
+class SimGetIssuer( SimMediator ): # s
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+CRSM=176,28486,0,0,17", self.responseFromChannel, self.errorFromChannel )
+
+    def responseFromChannel( self, request, response ):
+        if response[-1] != "OK":
+            SimMediator.responseFromChannel( self, request, response )
+        try:
+            sw1, sw2, payload = safesplit( self._rightHandSide( response[0] ), "," )
+        except ValueError: # response did not include a payload
+            self._error( DBusError.SimNotFound( "Elementary record not present or unreadable" ) )
+        else:
+            if int(sw1) != 144 or int(sw2) != 0: # command succeeded as per GSM 11.11, 9.4.1
+                self._error( DBusError.SimNotFound( "Elementary record not present or unreadable" ) )
+            else:
+                nameraw = payload[2:]
+                name = ""
+                for index in xrange( 0, 24, 2 ):
+                    c = int(nameraw[index:index+2],16)
+                    if 32 < c < 128:
+                        name += chr(c)
+                    else:
+                        break
+                self._ok( name )
+
+#=========================================================================#
+class SimGetProviderList( SimMediator ): # a{ss}
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+COPN", self.responseFromChannel, self.errorFromChannel )
+
+    def responseFromChannel( self, request, response ):
+        if response[-1] != "OK":
+            SimMediator.responseFromChannel( self, request, response )
+        else:
+            charset = currentModem()._charsets["DEFAULT"]
+            result = {}
+            for line in response[:-1]:
+                mccmnc, name = safesplit( self._rightHandSide( line ), ',' )
+                # Some modems contain provider tables with illegal characters
+                try:
+                    uname = name.strip('" ').decode(charset)
+                except UnicodeError:
+                    # Should we even add this to the list if we cannot decode it?
+                    # XXX: It looks like this should actually be decodable, it's just (again)
+                    # a problem with different charsets...
+                    uname = "<undecodable>"
+                result[ mccmnc.strip( '" ').decode(charset) ] = uname
+            return self._ok( result )
+
+#=========================================================================#
+class SimListPhonebooks( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+CPBS=?", self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        charset = currentModem()._charsets["DEFAULT"]
+        if ( response[-1] == "OK" ):
+            result = []
+            for pb in re.findall( const.PAT_STRING, response[0] ):
+                try:
+                    result.append( const.PHONEBOOK_CATEGORY.revlookup(pb.decode(charset)) )
+                except KeyError:
+                    pass
+            self._ok( result )
+        else:
+            SimMediator.responseFromChannel( self, request, response )
+
+            #
+            # FIXME: we should try harder here -- if a modem does not support
+            # +CBPS=?, then we could iterate through our list of known phonebooks
+            # and try to select it +CPBS="..." and build the list up from these results
+
+#=========================================================================#
+class SimGetPhonebookInfo( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        charset = currentModem()._charsets["DEFAULT"]
+        try:
+            self.pbcategory = const.PHONEBOOK_CATEGORY[self.category]
+        except KeyError:
+            self._error( DBusError.InvalidParameter( "valid categories are %s" % const.PHONEBOOK_CATEGORY.keys() ) )
+        else:
+            self._commchannel.enqueue( '+CPBS="%s";+CPBR=?' % self.pbcategory.encode(charset), self.responseFromChannel, self.errorFromChannel )
+
+    def responseFromChannel( self, request, response ):
+        if response[-1] != "OK":
+            SimMediator.responseFromChannel( self, request, response )
+        else:
+            result = {}
+            match = const.PAT_PHONEBOOK_INFO.match( self._rightHandSide( response[0] ) )
+            result["min_index"] = int(match.groupdict()["lowest"])
+            result["max_index"] = int(match.groupdict()["highest"])
+
+            try:
+                result["number_length"] = int(match.groupdict()["numlen"])
+                result["name_length"] = int(match.groupdict()["textlen"])
+            except KeyError:
+                pass
+
+            # store in modem class for later use
+            self._object.modem.setPhonebookIndices( self.pbcategory, result["min_index"], result["max_index"] )
+
+            self._ok( result )
+
+#=========================================================================#
+class SimRetrievePhonebook( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        charset = currentModem()._charsets["DEFAULT"]
+        try:
+            self.pbcategory = const.PHONEBOOK_CATEGORY[self.category]
+        except KeyError:
+            self._error( DBusError.InvalidParameter( "valid categories are %s" % const.PHONEBOOK_CATEGORY.keys() ) )
+        else:
+            minimum, maximum = self._object.modem.phonebookIndices( self.pbcategory )
+            if minimum is None: # don't know yet
+                SimGetPhonebookInfo( self._object, self.tryAgain, self.reportError, category=self.category )
+            else:
+                self._commchannel.enqueue( '+CPBS="%s";+CPBR=%d,%d' % ( self.pbcategory.encode(charset), minimum, maximum ), self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        defcharset = currentModem()._charsets["DEFAULT"]
+        charset = currentModem()._charsets["PHONEBOOK"]
+        if response[-1] != "OK":
+            SimMediator.responseFromChannel( self, request, response )
+        else:
+            result = []
+            for entry in response[:-1]:
+                index, number, ntype, name = safesplit( self._rightHandSide( entry ), ',' )
+                index = int( index )
+                number = number.strip( '"' ).decode(defcharset)
+                ntype = int( ntype )
+                name = name.strip('"').decode(charset)
+                result.append( ( index, name, const.phonebookTupleToNumber( number, ntype ) ) )
+            self._ok( result )
+
+    def tryAgain( self, result ):
+        charset = currentModem()._charsets["DEFAULT"]
+        minimum, maximum = self._object.modem.phonebookIndices( self.pbcategory )
+        if minimum is None: # still?
+            raise DBusError.InternalException( "can't get valid phonebook indices for phonebook %s from modem" % self.pbcategory )
+        else:
+            self._commchannel.enqueue( '+CPBS="%s";+CPBR=%d,%d' % ( self.pbcategory.encode(charset), minimum, maximum ), self.responseFromChannel, self.errorFromChannel )
+
+    def reportError( self, result ):
+        self._error( result )
+
+#=========================================================================#
+class SimDeleteEntry( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        try:
+            self.pbcategory = const.PHONEBOOK_CATEGORY[self.category]
+        except KeyError:
+            self._error( DBusError.InvalidParameter( "valid categories are %s" % const.PHONEBOOK_CATEGORY.keys() ) )
+        else:
+            self._commchannel.enqueue( '+CPBS="%s";+CPBW=%d,,,' % ( self.pbcategory, self.index ), self.responseFromChannel, self.errorFromChannel )
+
+#=========================================================================#
+class SimStoreEntry( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        charset = currentModem()._charsets["PHONEBOOK"]
+        defcharset = currentModem()._charsets["DEFAULT"]
+        try:
+            self.pbcategory = const.PHONEBOOK_CATEGORY[self.category]
+        except KeyError:
+            self._error( DBusError.InvalidParameter( "valid categories are %s" % const.PHONEBOOK_CATEGORY.keys() ) )
+        else:
+            number, ntype = currentModem().numberToPhonebookTuple( self.number )
+            name = self.name.strip('"').encode(charset)
+            self._commchannel.enqueue( '+CPBS="%s";+CPBW=%d,"%s",%d,"%s"' % ( self.pbcategory.encode(defcharset), self.index, number, ntype, name ), self.responseFromChannel, self.errorFromChannel )
+
+#=========================================================================#
+class SimRetrieveEntry( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        charset = currentModem()._charsets["DEFAULT"]
+        try:
+            self.pbcategory = const.PHONEBOOK_CATEGORY[self.category]
+        except KeyError:
+            self._error( DBusError.InvalidParameter( "valid categories are %s" % const.PHONEBOOK_CATEGORY.keys() ) )
+        else:
+            self._commchannel.enqueue( '+CPBS="%s";+CPBR=%d' % ( self.pbcategory.encode(charset), self.index ), self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        charset = currentModem()._charsets["PHONEBOOK"]
+        if response[-1] != "OK":
+            SimMediator.responseFromChannel( self, request, response )
+        else:
+            if len( response ) == 1:
+                self._ok( "", "" )
+            else:
+                if response[0].startswith( "+CPBR" ):
+                    index, number, ntype, name = safesplit( self._rightHandSide( response[0] ), ',' )
+                    index = int( index )
+                    number = number.strip( '"' )
+                    ntype = int( ntype )
+                    name = name.strip('"').decode(charset)
+                    self._ok( name, const.phonebookTupleToNumber( number, ntype ) )
+
+#=========================================================================#
+class SimGetServiceCenterNumber( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+CSCA?", self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        if ( response[-1] == "OK" ):
+            result = safesplit( self._rightHandSide( response[0] ), ',' )
+            if len( result ) == 2:
+                number, ntype = result
+            else:
+                number, ntype = result, 145
+            number = number.replace( '+', '' ) # normalize
+            self._ok( const.phonebookTupleToNumber( number.strip( '"' ), int(ntype) ) )
+        else:
+            SimMediator.responseFromChannel( self, request, response )
+
+#=========================================================================#
+class SimGetMessagebookInfo( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( '+CPMS="SM","SM","SM"', self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        if response[-1] != "OK":
+            SimMediator.responseFromChannel( self, request, response )
+        else:
+            afull, amax, bfull, bmax, cfull, cmax = safesplit( self._rightHandSide( response[0] ), ',' )
+            result = {}
+            # FIXME Can we safely ignore all but the first tuple always?
+            result.update( first=1, last=int(amax), used=int(afull) )
+            self._ok( result )
+
+#=========================================================================#
+class SimRetrieveMessagebook( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        try:
+            category = const.SMS_PDU_STATUS_IN[self.category]
+        except KeyError:
+            self._error( DBusError.InvalidParameter( "valid categories are %s" % const.SMS_PDU_STATUS_IN.keys() ) )
+        else:
+            self._commchannel.enqueue( '+CMGL=%i' % category, self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        # some modems (TI Calypso for a start) do return +CMS Error: 321 here (not found)
+        # if you request a category for which no messages are found
+        if response[-1] in ( "OK", "+CMS ERROR: 321" ):
+            result = []
+            inbody = False
+            for line in response[:-1]:
+                #print "parsing line", line
+                if line.startswith( "+CMGL" ):
+                    #print "line is header line"
+                    header = const.PAT_SMS_PDU_HEADER.match( self._rightHandSide(line) )
+                    index = int(header.groupdict()["index"])
+                    status = const.SMS_PDU_STATUS_OUT[int(header.groupdict()["status"])]
+                    if "read" in status:
+                      direction = "guess-deliver"
+                    else:
+                      direction = "guess-submit"
+                    length = int(header.groupdict()["pdulen"])
+                    inbody = True
+                elif inbody == True:
+                    # Now we decode the actual PDU
+                    inbody = False
+                    try:
+                        sms = ogsmd.gsm.sms.SMS.decode( line, direction )
+                    except UnicodeError:
+                        # Report an error so ogsmd doesn't bail out and we can
+                        # see which PDU makes trouble
+                        result.append( ( index, status, "Error decoding", "Error decoding", {} ) )
+                    else:
+                        result.append( ( index, status, str(sms.addr), sms.ud, sms.properties ) )
+                else:
+                    logger.warning( "SinRetrieveMessagebook encountered strange answer to AT+CMGL: '%s'" % line )
+            self._ok( result )
+        else:
+            SimMediator.responseFromChannel( self, request, response )
+
+#=========================================================================#
+class SimRetrieveMessage( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( '+CMGR=%d' % self.index, self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        if response[-1] != "OK":
+            SimMediator.responseFromChannel( self, request, response )
+        else:
+            inbody = False
+            for line in response[:-1]:
+                #print "parsing line", line
+                if line.startswith( "+CMGR" ):
+                    #print "line is header line"
+                    header = const.PAT_SMS_PDU_HEADER_SINGLE.match( self._rightHandSide(line) )
+                    status = const.SMS_PDU_STATUS_OUT[int(header.groupdict()["status"])]
+                    if "read" in status:
+                      direction = "guess-deliver"
+                    else:
+                      direction = "guess-submit"
+                    length = int(header.groupdict()["pdulen"])
+                    inbody = True
+                elif inbody == True:
+                    inbody = False
+                    # Now we decode the actual PDU
+                    sms = ogsmd.gsm.sms.SMS.decode( line, direction )
+                    result = ( status, str(sms.addr), sms.ud, sms.properties )
+                else:
+                    logger.warning( "SimRetrieveMessage encountered strange answer to AT+CMGR: '%s'" % line )
+
+            self._ok( *result )
+
+#=========================================================================#
+class SimSetServiceCenterNumber( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        if not self.number.startswith( '+' ):
+            self.number = "+%s" % self.number
+        self._commchannel.enqueue( '+CSCA="%s",145' % self.number, self.responseFromChannel, self.errorFromChannel )
+
+#=========================================================================#
+class SimStoreMessage( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        sms = ogsmd.gsm.sms.SMSSubmit()
+        # Use PDUAddress
+        sms.addr = ogsmd.gsm.sms.PDUAddress.guess( self.number )
+        sms.ud = self.contents
+        sms.properties = self.properties
+        pdu = sms.pdu()
+        self._commchannel.enqueue( '+CMGW=%i\r%s' % ( len(pdu)/2-1, pdu), self.responseFromChannel, self.errorFromChannel )
+
+    def responseFromChannel( self, request, response ):
+        if response[-1] != "OK":
+            SimMediator.responseFromChannel( self, request, response )
+        else:
+            self._ok( int(self._rightHandSide(response[0])) )
+
+#=========================================================================#
+class SimSendStoredMessage( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+CMSS=%d" % self.index, self.responseFromChannel, self.errorFromChannel )
+
+    def responseFromChannel( self, request, response ):
+        if response[-1] != "OK":
+            SimMediator.responseFromChannel( self, request, response )
+        else:
+            timestamp = ""
+            result = safesplit( self._rightHandSide(response[0]), ',' )
+            mr = result[0]
+            if len(result) == 2:
+                ackpdu =  ogsmd.gsm.sms.SMS.decode( result[1].strip('"'), "sms-submit-report" )
+                timestamp = ackpdu.properties["timestamp"]
+            self._ok( int(mr), timestamp )
+
+#=========================================================================#
+class SimDeleteMessage( SimMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+CMGD=%d" % self.index, self.responseFromChannel, self.errorFromChannel )
+
+#
+# SMS Mediators
+#
+
+#=========================================================================#
+class SmsSendMessage( SmsMediator ):
+#=========================================================================#
+    def trigger( self ):
+        sms = ogsmd.gsm.sms.SMSSubmit()
+        # Use PDUAddress
+        sms.addr = ogsmd.gsm.sms.PDUAddress.guess( self.number )
+        sms.ud = self.contents
+        sms.properties = self.properties
+        pdu = sms.pdu()
+        self._commchannel.enqueue( '+CMGS=%i\r%s' % ( len(pdu)/2-1, pdu), self.responseFromChannel, self.errorFromChannel )
+
+    def responseFromChannel( self, request, response ):
+        if response[-1] != "OK":
+            SmsMediator.responseFromChannel( self, request, response )
+        else:
+            timestamp = ""
+            result = safesplit( self._rightHandSide(response[0]), ',' )
+            mr = result[0]
+            if len(result) == 2:
+                ackpdu =  ogsmd.gsm.sms.SMS.decode( result[1].strip('"'), "sms-submit-report" )
+                timestamp = ackpdu.properties["timestamp"]
+            self._ok( int(mr), timestamp )
+
+#=========================================================================#
+class SmsAckMessage( SmsMediator ):
+#=========================================================================#
+    def trigger( self ):
+        sms = ogsmd.gsm.sms.SMSDeliverReport(True)
+        sms.ud = self.contents
+        sms.properties = self.properties
+        pdu = sms.pdu()
+        commchannel = self._object.modem.communicationChannel( "UnsolicitedMediator" )
+        commchannel.enqueue( '+CNMA=1,%i\r%s' % ( len(pdu)/2, pdu), self.responseFromChannel, self.errorFromChannel )
+
+#=========================================================================#
+class SmsNackMessage( SmsMediator ):
+#=========================================================================#
+    def trigger( self ):
+        sms = ogsmd.gsm.sms.SMSDeliverReport(False)
+        sms.ud = self.contents
+        sms.properties = self.properties
+        pdu = sms.pdu()
+        commchannel = self._object.modem.communicationChannel( "UnsolicitedMediator" )
+        commchannel.enqueue( '+CNMA=2,%i\r%s' % ( len(pdu)/2, pdu), self.responseFromChannel, self.errorFromChannel )
+
+#
+# Network Mediators
+#
+
+#=========================================================================#
+class NetworkRegister( NetworkMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+COPS=0,0", self.responseFromChannel, self.errorFromChannel )
+
+#=========================================================================#
+class NetworkUnregister( NetworkMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+COPS=2,0", self.responseFromChannel, self.errorFromChannel )
+
+#=========================================================================#
+class NetworkGetStatus( NetworkMediator ):
+#=========================================================================#
+    def trigger( self ):
+        charset = currentModem()._charsets["DEFAULT"]
+        # query strength
+        request, response, error = yield( "+CSQ" )
+        result = {}
+        if error is not None:
+            self.errorFromChannel( request, error )
+        else:
+            if response[-1] != "OK" or len( response ) == 1:
+                pass
+            else:
+                result["strength"] = const.signalQualityToPercentage( int(safesplit( self._rightHandSide( response[0] ), ',' )[0]) ) # +CSQ: 22,99
+
+        # query registration status and lac/cid
+        request, response, error = yield( "+CREG?" )
+        if error is not None:
+            self.errorFromChannel( request, error )
+        elif response[-1] != "OK" or len( response ) == 1:
+            pass
+        else:
+            oldreg = safesplit( self._rightHandSide( response[-2] ), ',' )[0]
+            request, response, error = yield( "+CREG=2;+CREG?;+CREG=%s" % oldreg )
+
+            if error is not None:
+                self.errorFromChannel( request, error )
+            elif response[-1] != "OK" or len( response ) == 1:
+                pass
+            else:
+                result[ "registration"] = const.REGISTER_STATUS[int(safesplit( self._rightHandSide( response[-2] ), ',' )[1])]
+                values = safesplit( self._rightHandSide( response[-2] ), ',' )
+                if len( values ) == 4: # have lac and cid now
+                    result["lac"] = values[2].strip( '"' ).decode(charset)
+                    result["cid"] = values[3].strip( '"' ).decode(charset)
+
+        # query operator name and numerical code
+        request, response, error = yield( "+COPS=3,0;+COPS?;+COPS=3,2;+COPS?" )
+
+        if error is not None:
+            self.errorFromChannel( request, error )
+        else:
+            if response[-1] != "OK" or len( response ) != 3:
+                pass
+            else:
+                # first parse the alphanumerical response set
+                values = safesplit( self._rightHandSide( response[-3] ), ',' )
+                result["mode"] = const.REGISTER_MODE[int(values[0])]
+                if len( values ) > 2:
+                    result["provider"] = values[2].strip( '"' ).decode(charset)
+                    if len( values ) == 4:
+                        result["act"] = const.REGISTER_ACT[int( values[3] )]
+                    else: # AcT defaults to GSM
+                        result["act"] = const.REGISTER_ACT[ 0 ]
+                # then parse the numerical response set
+                values = safesplit( self._rightHandSide( response[-2] ), ',' )
+                if len( values ) > 2:
+                    mccmnc = values[2].strip( '"' ).decode(charset)
+                    result["code"] = mccmnc
+                    # Some providers' name may be unknown to the modem hence not show up in +COPS=3,0;+COPS?
+                    # In this case try to gather the name from our network database
+                    if not "provider" in result:
+                        network = const.NETWORKS.get( ( int( mccmnc[:3]), int( mccmnc[3:] ) ), {} )
+                        if "brand" in network:
+                            result["provider"] = network["brand"]
+                        elif "operator" in network:
+                            result["provider"] = network["operator"]
+                        else:
+                            result["provider"] = "Unknown"
+        # UGLY special check for some modems, which return a strength of 0, if you
+        # call +CSQ too early after a (re)registration. In that case, we just
+        # leave the strength out of the result
+        try:
+            if result["registration"] in "home roaming denied".split() and result["strength"] == 0:
+                del result["strength"]
+        except KeyError:
+            pass
+        self._ok( result )
+
+#=========================================================================#
+class NetworkGetSignalStrength( NetworkMediator ): # i
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( '+CSQ', self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        if response[-1] != "OK":
+            NetworkMediator.responseFromChannel( self, request, response )
+
+        result = const.signalQualityToPercentage( int(safesplit( self._rightHandSide( response[0] ), ',' )[0]) ) # +CSQ: 22,99
+        self._ok( result )
+
+#=========================================================================#
+class NetworkListProviders( NetworkMediator ): # a{sv}
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+COPS=?", self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        charset = currentModem()._charsets["DEFAULT"]
+        if response[0] == "OK":
+            self._ok( [] )
+        if response[-1] == "OK":
+            result = []
+            for operator in const.PAT_OPERATOR_LIST.finditer( response[0] ):
+                index = operator.groupdict()["code"].decode(charset)
+                status = const.PROVIDER_STATUS[int(operator.groupdict()["status"])]
+                name = operator.groupdict()["name"].decode(charset)
+                shortname = operator.groupdict()["shortname"].decode(charset)
+                act = operator.groupdict()["act"]
+                if act is None or act == "":
+                    act = "0" # AcT defaults to GSM
+                act = const.REGISTER_ACT[int(act)]
+                result.append( ( index, status, name, shortname, act ) )
+            self._ok( result )
+        else:
+            NetworkMediator.responseFromChannel( self, request, response )
+
+    # XXX: Where is this used?
+    def _providerTuple( self, provider ):
+        provider.replace( '"', "" )
+        values = safesplit( provider[1:-1], ',' )
+        return int( values[3] ), const.PROVIDER_STATUS[int(values[0])], values[1], values[2]
+
+#=========================================================================#
+class NetworkRegisterWithProvider( NetworkMediator ):
+#=========================================================================#
+    def trigger( self ):
+        charset = currentModem()._charsets["DEFAULT"]
+        opcode = self.operator_code.encode(charset)
+        self._commchannel.enqueue( '+COPS=1,2,"%s"' % opcode, self.responseFromChannel, self.errorFromChannel )
+
+#=========================================================================#
+class NetworkGetCountryCode( NetworkMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+COPS=3,2;+COPS?;+COPS=3,0", self.responseFromChannel, self.errorFromChannel )
+
+    def responseFromChannel( self, request, response ):
+        if response[-1] == "OK" and len( response ) > 1:
+            values = self._rightHandSide( response[0] ).split( ',' )
+            if len( values ) != 3:
+                self._error( DBusError.NetworkNotFound( "Not registered to any provider" ) )
+            else:
+                mcc = int( values[2].strip( '"' )[:3] )
+                code, name = const.mccToCountryCode( mcc )
+                self._ok( code, name )
+
+#=========================================================================#
+class NetworkGetCallForwarding( NetworkMediator ): # a{sv}
+#=========================================================================#
+    def trigger( self ):
+        try:
+            reason = const.CALL_FORWARDING_REASON[self.reason]
+        except KeyError:
+            self._error( DBusError.InvalidParameter( "valid reasons are %s" % const.CALL_FORWARDING_REASON.keys() ) )
+        else:
+            self._commchannel.enqueue( "+CCFC=%d,2" % reason, self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        if response[-1] == "OK":
+            result = {}
+            for line in response[:-1]:
+                match = const.PAT_CCFC.match( self._rightHandSide( line ) )
+                print match.groupdict
+                enabled = bool( int( match.groupdict()["enabled"] ) )
+                class_ = int( match.groupdict()["class"] )
+                number = match.groupdict()["number"]
+                ntype = int( match.groupdict()["ntype"] or 129 )
+                seconds = int( match.groupdict()["seconds"] or 0 )
+
+                if not enabled:
+                    result = {}
+                    break
+                else:
+                    result[ const.CALL_FORWARDING_CLASS[class_] ] = ( enabled, const.phonebookTupleToNumber( number, ntype ), seconds )
+            self._ok( result )
+        else:
+            NetworkMediator.responseFromChannel( self, request, response )
+
+#=========================================================================#
+class NetworkEnableCallForwarding( NetworkMediator ):
+#=========================================================================#
+    def trigger( self ):
+        try:
+            reason = const.CALL_FORWARDING_REASON[self.reason]
+        except KeyError:
+            self._error( DBusError.InvalidParameter( "valid reasons are %s" % const.CALL_FORWARDING_REASON.keys() ) )
+
+        try:
+            class_ = const.CALL_FORWARDING_CLASS[self.class_]
+        except KeyError:
+            self._error( DBusError.InvalidParameter( "valid classes are %s" % const.CALL_FORWARDING_CLASS.keys() ) )
+
+        number, ntype = currentModem().numberToPhonebookTuple( self.number )
+
+        if self.reason == "no reply" and self.timeout > 0:
+            self._commchannel.enqueue( """+CCFC=%d,3,"%s",%d,%d,,,%d""" % ( reason, number, ntype, class_, self.timeout ), self.responseFromChannel, self.errorFromChannel )
+        else:
+            self._commchannel.enqueue( """+CCFC=%d,3,"%s",%d,%d""" % ( reason, number, ntype, class_ ), self.responseFromChannel, self.errorFromChannel )
+
+#=========================================================================#
+class NetworkDisableCallForwarding( NetworkMediator ):
+#=========================================================================#
+    def trigger( self ):
+        try:
+            reason = const.CALL_FORWARDING_REASON[self.reason]
+        except KeyError:
+            self._error( DBusError.InvalidParameter( "valid reasons are %s" % const.CALL_FORWARDING_REASON.keys() ) )
+
+        try:
+            class_ = const.CALL_FORWARDING_CLASS[self.class_]
+        except KeyError:
+            self._error( DBusError.InvalidParameter( "valid classes are %s" % const.CALL_FORWARDING_CLASS.keys() ) )
+
+        self._commchannel.enqueue( "+CCFC=%d,4,,,%d" % ( reason, class_ ), self.responseFromChannel, self.errorFromChannel )
+
+#=========================================================================#
+class NetworkGetCallingIdentification( NetworkMediator ): # s
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel = self._object.modem.communicationChannel( "CallMediator" ) # exceptional, since this is a call-specific command
+        self._commchannel.enqueue( "+CLIR?", self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        if response[-1] == "OK" and len( response ) > 1:
+            status, adjustment = safesplit( self._rightHandSide( response[0] ), ',' )
+            self._ok( const.CALL_IDENTIFICATION_RESTRICTION.revlookup( int(status) ) )
+        # We can't rely on NetworkMediator.responseFromChannel since dbus_ok
+        # needs to be called with arguments
+        elif response[-1] == "OK":
+                self._ok( "unknown" )
+        else:
+            NetworkMediator.responseFromChannel( self, request, response )
+
+#=========================================================================#
+class NetworkSetCallingIdentification( NetworkMediator ): # s
+#=========================================================================#
+    def trigger( self ):
+        try:
+            restriction = const.CALL_IDENTIFICATION_RESTRICTION[self.status]
+        except KeyError:
+            self._error( DBusError.InvalidParameter( "valid restrictions are %s" % const.CALL_IDENTIFICATION_RESTRICTION.keys() ) )
+        self._commchannel = self._object.modem.communicationChannel( "CallMediator" ) # exceptional, since this is a call-specific command
+        self._commchannel.enqueue( "+CLIR=%d" % restriction, self.responseFromChannel, self.errorFromChannel )
+
+#=========================================================================#
+class NetworkSendUssdRequest( NetworkMediator ): # s
+#=========================================================================#
+    def trigger( self ):
+        charset = currentModem()._charsets["USSD"]
+        # FIXME request code validation
+        # when using UCS2 we need to encode the request, although it is just a number :/
+        request = self.request.encode(charset)
+        commchannel = self._object.modem.communicationChannel( "UnsolicitedMediator" ) # exceptional, since CUSD is semi-unsolicited
+        commchannel.enqueue( '+CUSD=1,"%s",15' % request, self.responseFromChannel, self.errorFromChannel, prefixes=["NONE"] )
+
+#
+# Call Mediators
+#
+
+#=========================================================================#
+class CallEmergency( CallMediator ):
+#=========================================================================#
+    def trigger( self ):
+        if self.number in const.EMERGENCY_NUMBERS:
+            # FIXME once we have a priority queue, insert these with maximum priority
+            self._commchannel.enqueue( 'H' ) # hang up (just in case)
+            self._commchannel.enqueue( '+CFUN=1;+COPS=0,0' )
+            self._commchannel.enqueue( 'D%s;' % self.number ) # dial emergency number
+        else:
+            self._error( DBusError.CallNotAnEmergencyNumber( "valid emergency numbers are %s" % const.EMERGENCY_NUMBERS ) )
+
+#=========================================================================#
+class CallTransfer( CallMediator ):
+#=========================================================================#
+    def trigger( self ):
+        number, ntype = currentModem().numberToPhonebookTuple( self.number )
+        self._commchannel.enqueue( '+CTFR="%s",%d' % ( number, ntype ), self.responseFromChannel, self.errorFromChannel )
+
+#=========================================================================#
+class CallListCalls( NetworkMediator ): # a(isa{sv})
+#=========================================================================#
+    """
+    CallListCalls is a NetworkMediator since its commands should not be issued on the call channel.
+    """
+    def trigger( self ):
+        self._commchannel.enqueue( "+CLCC", self.responseFromChannel, self.errorFromChannel )
+
+    def responseFromChannel( self, request, response ):
+        if response[-1] == "OK":
+            result = []
+            for line in response[:-1]:
+                if not line: # some modems might include empty lines here, one for every (not present) call...
+                    continue
+                gd = const.groupDictIfMatch( const.PAT_CLCC, line )
+                if gd is None:
+                    logger.warning( "+CLCC parsing error for line '%s'" % line )
+                    continue
+                index = int( gd["id"] )
+                stat = int( gd["stat"] )
+                direction = const.CALL_DIRECTION[ int( gd["dir"] ) ]
+                mode = const.CALL_MODE.revlookup( int( gd["mode"] ) )
+                number, ntype = gd["number"], gd["ntype"]
+
+                properties = { "direction": direction, "type": mode }
+                if number is not None:
+                    properties["peer"] = const.phonebookTupleToNumber( number, int(ntype) )
+                c = ( index, const.CALL_STATUS[ stat ], properties )
+                result.append( c )
+            self._ok( result )
+        else:
+            NetworkMediator.responseFromChannel( self, request, response )
+
+#=========================================================================#
+class CallSendDtmf( CallMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self.tonelist = [ tone.upper() for tone in self.tones if tone.upper() in const.CALL_VALID_DTMF ]
+        self.tonelist.reverse()
+        if not self.tonelist:
+            self._error( DBusError.InvalidParameter( "not enough valid tones" ) )
+        else:
+            self._commchannel.enqueue( "+VTS=%s" % self.tonelist.pop(), self.responseFromChannel, self.errorFromChannel )
+
+    def responseFromChannel( self, request, response ):
+        if response[-1] == "OK":
+            if self.tonelist:
+                self._commchannel.enqueue( "+VTS=%s" % self.tonelist.pop(), self.responseFromChannel, self.errorFromChannel )
+            else:
+                self._ok()
+        else:
+            CallMediator.responseFromChannel( self, request, response )
+
+#=========================================================================#
+class CallInitiate( CallMediator ):
+#=========================================================================#
+    def trigger( self ):
+        # check parameters
+        if self.calltype not in const.PHONE_CALL_TYPES:
+            self._error( DBusError.InvalidParameter( "invalid call type. Valid call types are: %s" % const.PHONE_CALL_TYPES ) )
+            return
+        for digit in self.number:
+            if digit not in const.PHONE_NUMBER_DIGITS:
+                self._error( DBusError.InvalidParameter( "invalid number digit. Valid number digits are: %s" % const.PHONE_NUMBER_DIGITS ) )
+                return
+        # do the work
+        if self.calltype == "voice":
+            dialstring = "%s;" % self.number
+        else:
+            dialstring = self.number
+
+        line = CallHandler.getInstance().initiate( dialstring, self._commchannel )
+        if line is None:
+            self._error( DBusError.CallNoCarrier( "unable to dial" ) )
+        else:
+            self._ok( line )
+
+#=========================================================================#
+class CallRelease( CallMediator ):
+#=========================================================================#
+    def trigger( self ):
+        if CallHandler.getInstance().release( self.index, self._commchannel ) is not None:
+            self._ok()
+        else:
+            self._error( DBusError.CallNotFound( "no such call to release" ) )
+
+#=========================================================================#
+class CallReleaseAll( CallMediator ):
+#=========================================================================#
+    def trigger( self ):
+        # need to use misc channel here, so that it can also work during outgoing call
+        # FIXME might rather want to consider using the state machine after all (see below)
+        CallHandler.getInstance().releaseAll( self._object.modem.channel( "MiscMediator" ) )
+        self._ok()
+
+#=========================================================================#
+class CallActivate( CallMediator ):
+#=========================================================================#
+    def trigger( self ):
+        if CallHandler.getInstance().activate( self.index, self._commchannel ) is not None:
+            self._ok()
+        else:
+            self._error( DBusError.CallNotFound( "no such call to activate" ) )
+
+#=========================================================================#
+class CallHoldActive( CallMediator ):
+#=========================================================================#
+    def trigger( self ):
+        if CallHandler.getInstance().hold( self._commchannel ) is not None:
+            self._ok()
+        else:
+            self._error( DBusError.CallNotFound( "no such call to hold" ) )
+
+#
+# PDP Mediators
+#
+
+#=========================================================================#
+class PdpListAvailableGprsClasses( PdpMediator ): # as
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+CGCLASS=?", self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        if response[-1] == "OK" and len( response ) > 1:
+            self._ok( re.findall( const.PAT_STRING, response[0] ) )
+        else:
+            PdpMediator.responseFromChannel( self, request, response )
+
+#=========================================================================#
+class PdpGetCurrentGprsClass( PdpMediator ): # s
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( "+CGCLASS?", self.responseFromChannel, self.errorFromChannel )
+
+    @logged
+    def responseFromChannel( self, request, response ):
+        if response[-1] == "OK" and len( response ) > 1:
+            self._ok( re.findall( const.PAT_STRING, response[0] )[0] )
+        else:
+            PdpMediator.responseFromChannel( self, request, response )
+
+#=========================================================================#
+class PdpSetCurrentGprsClass( PdpMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueue( '+CGCLASS="%s"' % self.class_, self.responseFromChannel, self.errorFromChannel )
+
+#=========================================================================#
+class PdpGetNetworkStatus( PdpMediator ):
+#=========================================================================#
+    def trigger( self ):
+        result = {}
+        # query registration status and lac/cid
+        request, response, error = yield( "+CGREG?" )
+        if error is not None:
+            self.errorFromChannel( request, error )
+        elif response[-1] != "OK" or len( response ) == 1:
+            pass
+        else:
+            oldreg = safesplit( self._rightHandSide( response[-2] ), ',' )[0]
+            request, response, error = yield( "+CGREG=2;+CGREG?;+CGREG=%s" % oldreg )
+
+            if error is not None:
+                self.errorFromChannel( request, error )
+            elif response[-1] != "OK" or len( response ) == 1:
+                pass
+            else:
+                charset = currentModem()._charsets["DEFAULT"]
+                result[ "registration"] = const.REGISTER_STATUS[int(safesplit( self._rightHandSide( response[-2] ), ',' )[1])]
+                values = safesplit( self._rightHandSide( response[-2] ), ',' )
+                if len( values ) >= 4: # have lac and cid now
+                    result["lac"] = values[2].strip( '"' ).decode(charset)
+                    result["cid"] = values[3].strip( '"' ).decode(charset)
+                if len( values ) == 5:
+                    result["act"] = const.REGISTER_ACT[ int(values[4]) ]
+                else: # AcT defaults to GSM
+                    result["act"] = const.REGISTER_ACT[ 0 ]
+        self._ok( result )
+
+#=========================================================================#
+class PdpActivateContext( PdpMediator ):
+#=========================================================================#
+    def trigger( self ):
+        pdpConnection = Pdp.getInstance( self._object )
+        if pdpConnection.isActive():
+            self._ok()
+        else:
+            pdpConnection.setParameters( self.apn, self.user, self.password )
+            pdpConnection.activate()
+            self._ok()
+
+#=========================================================================#
+class PdpDeactivateContext( PdpMediator ):
+#=========================================================================#
+    def trigger( self ):
+        # the right way... leading to a hanging pppd :(
+        #self._commchannel.enqueue( '+CGACT=0', self.responseFromChannel, self.errorFromChannel )
+        # the workaround
+        pdpConnection = Pdp.getInstance( self._object )
+        if pdpConnection.isActive():
+            pdpConnection.deactivate()
+        self._ok()
+
+#=========================================================================#
+class PdpGetContextStatus( PdpMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._ok( Pdp.getInstance( self._object ).status() )
+#
+# CB Mediators
+#
+
+#=========================================================================#
+class CbGetCellBroadcastSubscriptions( CbMediator ): # s
+#=========================================================================#
+    def trigger( self ):
+
+        request, response, err = yield( "+CSCB?" )
+        if err is not None:
+            self.errorFromChannel( request, err )
+        else:
+            if response[-1] != "OK":
+                self.responseFromChannel( request, response )
+            else:
+                # +CSCB: 0,"0-999","0-3,5"
+                gd = const.groupDictIfMatch( const.PAT_CSCB, response[0] )
+                assert gd is not None, "parsing error"
+                drop = gd["drop"] == '1'
+                channels = gd["channels"]
+                encodings = gd["encodings"]
+
+                if not drop:
+                    if channels == "":
+                        self._ok( "none" )
+                    elif channels == "0-999":
+                        self._ok( "all" )
+                    else:
+                        self._ok( channels )
+                else:
+                    if channels == "": # drop nothing = accept 0-999
+                        self._ok( "all" )
+                    self._error( DBusError.InternalException( "+CSCB: 1 not yet handled" ) )
+
+#=========================================================================#
+class CbSetCellBroadcastSubscriptions( CbMediator ):
+#=========================================================================#
+    def trigger( self ):
+        if self.channels == "all":
+            message = '1,"",""'
+        elif self.channels == "none":
+            message = '0,"",""'
+        else:
+            message = '0,"%s","0-3,5"' % self.channels
+        self._commchannel.enqueue( "+CSCB=%s" % message, self.responseFromChannel, self.errorFromChannel )
+
+#
+# Monitor Mediators
+#
+
+#=========================================================================#
+class MonitorGetServingCellInformation( MonitorMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._error( DBusError.UnsupportedCommand( "org.freesmartphone.GSM.Monitor.GetServingCellInformation" ) )
+
+#=========================================================================#
+class MonitorGetNeighbourCellInformation( MonitorMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._error( DBusError.UnsupportedCommand( "org.freesmartphone.GSM.Monitor.GetNeighbourCellInformation" ) )
+
+#
+# Debug Mediators
+#
+
+#=========================================================================#
+class DebugCommand( DebugMediator ):
+#=========================================================================#
+    def trigger( self ):
+        self._commchannel.enqueueRaw( "%s" % self.command, self.responseFromChannel, self.errorFromChannel, prefixes = [""] )
+
+    def responseFromChannel( self, request, response ):
+        self._ok( response )
+
+#=========================================================================#
+if __name__ == "__main__":
+#=========================================================================#
+    pass
diff --git a/framework/subsystems/ogsmd/modems/abstractcdma/modem.py b/framework/subsystems/ogsmd/modems/abstractcdma/modem.py
new file mode 100644
index 0000000..4bde580
--- /dev/null
+++ b/framework/subsystems/ogsmd/modems/abstractcdma/modem.py
@@ -0,0 +1,298 @@
+#!/usr/bin/env python
+"""
+The Open GSM Daemon - Python Implementation
+
+(C) 2008 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
+(C) 2008 Openmoko, Inc.
+
+GPLv2 or later
+
+Package: ogsmd.modems.abstractcdma
+Module: modem
+"""
+
+# FIXME: The modem should really be a sigleton
+
+__version__ = "0.9.9.4"
+MODULE_NAME = "ogsmd.modem.abstractcdma"
+
+import gobject
+import sys, types
+
+import logging
+logger = logging.getLogger( MODULE_NAME )
+
+FALLBACK_TIMEOUT = 30
+
+#=========================================================================#
+class AbstractCdmaModem( object ):
+#=========================================================================#
+    """This class abstracts a GSM Modem."""
+    instance = None
+
+    def __init__( self, dbus_object, bus, *args, **kwargs ):
+        """
+        Initialize
+        """
+        assert self.__class__.__name__ != "AbstractCdmaModem", "can't instanciate pure virtual class"
+        # FIXME make sure only one modem exists
+        AbstractCdmaModem.instance = self
+
+        self._channels = {}                     # container for channel instances
+        self._object = dbus_object              # dbus object
+        self._bus = bus                         # dbus bus
+        self._simPinState = "unknown"           # SIM PIN state
+        self._simReady = "unknown"              # SIM data access state
+        self._data = {}                         # misc modem-wide data, set/get from channels
+        self._phonebookIndices = {}             # min. index, max. index
+
+        self._data["sim-buffers-sms"] = True
+        self._data["sms-buffered-cb"] = "2,1,2,1,1"
+        self._data["sms-buffered-nocb"] = "2,1,0,0,0"
+        # FIXME: Might be bad as default, since not all modems necessarily support that
+        self._data["sms-direct-cb"] = "2,2,2,1,1" # what about a,3,c,d,e?
+        self._data["sms-direct-nocb"] = "2,2,0,0,0" # dito
+
+        self._data["pppd-configuration"] = [ \
+            '115200',
+            'nodetach',
+            'crtscts',
+            'defaultroute',
+            'debug',
+            'hide-password',
+            'holdoff', '3',
+            'ipcp-accept-local',
+            'ktune',
+            #'lcp-echo-failure', '10',
+            #'lcp-echo-interval', '20',
+            'ipcp-max-configure', '4',
+            'lock',
+            'noauth',
+            #'demand',
+            'noipdefault',
+            'novj',
+            'novjccomp',
+            #'persist',
+            'proxyarp',
+            'replacedefaultroute',
+            'usepeerdns',
+        ]
+
+        self._data["pppd-does-setup-and-teardown"] = True # default is using connect and disconnect scripts
+
+        self._charsets = { \
+            "DEFAULT":      "gsm_default",
+            "PHONEBOOK":    "gsm_ucs2",
+            "USSD":         "gsm_ucs2",
+            }
+
+        self._data["cancel-outgoing-call"] = "H" # default will kill all connections
+
+    def open( self, on_ok, on_error ):
+        """
+        Triggers opening the channels on this modem.
+
+        The actual opening will happen from inside mainloop.
+        """
+        self._counter = len( self._channels )
+        if ( self._counter ):
+            gobject.idle_add( self._initChannels, on_ok, on_error )
+
+    def close( self ): # SYNC
+        """
+        Closes the communication channels.
+        """
+        # FIXME: A really good way would be to stop accepting new commands,
+        # giving it time to drain the queues, and then closing all channels.
+        for channel in self._channels.values():
+            # FIXME: We're throwing away the result here :/
+            channel.close()
+
+    def reinit( self ):
+        """
+        Closes and reopens the communication channels, also triggering
+        resending the initialization commands on every channel.
+        """
+        self.close()
+        for channel in self._channels.values():
+            channel._sendCommands( "init" ) # just enqueues them for later
+        # FIXME no error handling yet
+        self.open( lambda: None, lambda foo: None )
+
+    def data( self, key, defaultValue=None ):
+        return self._data.get( key, defaultValue )
+
+    def setData( self, key, value ):
+        self._data[key] = value
+
+    def numberToPhonebookTuple( self, nstring ):
+        """
+        Returns a phonebook tuple.
+        """
+        if type( nstring ) != types.StringType():
+            # even though we set +CSCS="UCS2" (modem charset), the name is always encoded in text format, not PDU.
+            nstring = nstring.encode( "iso-8859-1" )
+
+        if nstring[0] == '+':
+            return nstring[1:], 145
+        else:
+            return nstring, 129
+
+    def channel( self, category ):
+        """
+        Returns the communication channel for certain command category.
+        """
+        sys.exit( -1 ) # pure virtual method called
+
+    def channels( self ):
+        """
+        Returns the names of the communication channels.
+        """
+        return self._channels.keys()
+
+    def inject( self, channel, string ):
+        """
+        Injects a string to a channel.
+        """
+        self._channels[channel].readyToRead( string )
+
+    def simPinState( self ):
+        """
+        Returns the SIM PIN state
+        """
+        return self._simPinState
+
+    def simReady( self ):
+        """
+        Returns the SIM availability state.
+        """
+        return self._simReady
+
+    def stateAntennaOn( self ):
+        """
+        Notify channels that the antenna is now powered on.
+        """
+        for channel in self._channels.itervalues():
+            channel.modemStateAntennaOn()
+
+    def setSimPinState( self, state ):
+        """
+        Set and notify channels about a new SIM PIN state.
+        """
+        self._simPinState = state
+        if state == "READY":
+            for channel in self._channels.itervalues():
+                channel.modemStateSimUnlocked()
+
+    def setSimReady( self, ready ):
+        """
+        Set and notify channels about a SIM data accessibility.
+        """
+        self._simReady = ready
+        if ready == True:
+            for channel in self._channels.itervalues():
+                channel.modemStateSimReady()
+
+    def setPhonebookIndices( self, category, first, last ):
+        """
+        Set phonebook valid indices interval for a given phonebook
+        """
+        self._phonebookIndices[category] = first, last
+
+    def phonebookIndices( self, category ):
+        try:
+            first, last = self._phonebookIndices[category]
+        except KeyError:
+            return None, None
+        else:
+            return first, last
+
+    def prepareForSuspend( self, ok_callback, error_callback ):
+        """
+        Prepares the modem for suspend.
+        """
+        # FIXME can we refactor this into a generic useful callback/object adapter class?
+        class MyOk(object):
+            def __init__( self, *args, **kwargs ):
+                assert args == (), "only keyword arguments allowed"
+                for key, value in kwargs.iteritems():
+                    if key.startswith( "CL_" ):
+                        setattr( self.__class__, key[3:], value )
+                    else:
+                        setattr( self, key, value )
+
+            def __call__( self, channel ):
+                logger.debug( "prepareForSuspend ACK from channel %s received" % channel )
+                self.__class__.counter -= 1
+                if self.__class__.counter == 0:
+                    self.ok()
+
+        class MyError(MyOk):
+            def __call__( self, channel ):
+                logger.debug( "prepareForSuspend NACK from channel %s received" % channel )
+                self.__class__.counter -= 1
+                if self.__class__.counter == 0:
+                    self.error()
+
+        ok = MyOk( CL_counter = len( self._channels ), ok = ok_callback )
+        error = MyError( error = error_callback )
+        for channel in self._channels.values():
+            channel.suspend( ok, error )
+
+    def recoverFromSuspend( self, ok_callback, error_callback ):
+        """
+        Recovers the modem from suspend.
+        """
+        # FIXME can we refactor this into a generic useful callback/object adapter class?
+        class MyOk(object):
+            def __init__( self, *args, **kwargs ):
+                assert args == (), "only keyword arguments allowed"
+                for key, value in kwargs.iteritems():
+                    if key.startswith( "CL_" ):
+                        setattr( self.__class__, key[3:], value )
+                    else:
+                        setattr( self, key, value )
+
+            def __call__( self, channel ):
+                logger.debug( "recoverFromSuspend ACK from channel %s received" % channel )
+                self.__class__.counter -= 1
+                if self.__class__.counter == 0:
+                    self.ok()
+
+        class MyError(MyOk):
+            def __call__( self, channel ):
+                logger.debug( "recoverFromSuspend NACK from channel %s received" % channel )
+                self.__class__.counter -= 1
+                if self.__class__.counter == 0:
+                    self.error()
+
+        ok = MyOk( CL_counter = len( self._channels ), ok = ok_callback )
+        error = MyError( error = error_callback )
+        for channel in self._channels.values():
+            channel.resume( ok, error )
+
+    #
+    # internal API
+    #
+    def _initChannels( self, on_ok, on_error, iteration=1 ):
+        if iteration == 7:
+            # we did try to open the modem 5 times -- giving up now
+            on_error()
+        # try to open all channels
+        for channel in self._channels:
+            if not self._channels[channel].isOpen():
+                logger.debug( "trying to open channel %s" % channel )
+                if not self._channels[channel].open():
+                    logger.error( "could not open channel %s, retrying in 2 seconds" % channel )
+                    gobject.timeout_add_seconds( 2, self._initChannels, on_ok, on_error, iteration+1 )
+                else:
+                    self._counter -= 1
+                    if not self._counter:
+                        on_ok()
+        return False # don't call me again
+
+    @classmethod
+    def communicationChannel( cls, category ):
+        return cls.instance.channel( category )
+
+#=========================================================================#
diff --git a/framework/subsystems/ogsmd/modems/abstractcdma/overlay.py b/framework/subsystems/ogsmd/modems/abstractcdma/overlay.py
new file mode 100644
index 0000000..f0a1cbe
--- /dev/null
+++ b/framework/subsystems/ogsmd/modems/abstractcdma/overlay.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+"""
+The Open GSM Daemon - Python Implementation
+
+(C) 2008 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
+(C) 2008 Openmoko, Inc.
+GPLv2 or later
+
+"""
+
+import os
+import stat
+import shutil
+
+#=========================================================================#
+class OverlayFile( object ):
+#=========================================================================#
+
+    backupPath = "/var/tmp/ogsmd/"
+    classInitDone = False
+
+    def __init__( self, name, overlay ):
+        self._classInit()
+        self.name = os.path.abspath( name )
+        if os.path.exists( self.name ):
+            self.backupname = "%s/%s" % ( self.__class__.backupPath, self.name.replace( '/', ',' ) )
+        else:
+            self.backupname = None
+        self.overlay = overlay
+
+    def store( self ):
+        """Store the overlay"""
+        if self.backupname is not None:
+            shutil.copy( self.name, self.backupname )
+        f = open( self.name, "w" )
+        f.write( self.overlay )
+        del f
+        # FIXME store chmod
+        os.chmod( self.name, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO )
+
+    def restore( self ):
+        """Restore original content"""
+        if self.backupname is not None:
+            shutil.copy( self.backupname, self.name )
+        # FIXME restore chmod
+
+    def __del__( self ):
+        pass
+
+    def _classInit( self ):
+        if not OverlayFile.classInitDone:
+            if os.path.exists( OverlayFile.backupPath ) and os.path.isdir( OverlayFile.backupPath ):
+                pass
+            else:
+                # FIXME lets do that without shell
+                os.system( "mkdir -p %s" % OverlayFile.backupPath )
+            OverlayFile.classInitDone = True
diff --git a/framework/subsystems/ogsmd/modems/abstractcdma/pdp.py b/framework/subsystems/ogsmd/modems/abstractcdma/pdp.py
new file mode 100644
index 0000000..bae75bd
--- /dev/null
+++ b/framework/subsystems/ogsmd/modems/abstractcdma/pdp.py
@@ -0,0 +1,232 @@
+#!/usr/bin/env python
+"""
+The Open GSM Daemon - Python Implementation
+
+(C) 2008 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
+(C) 2008 Openmoko, Inc.
+GPLv2 or later
+
+This module is based on pyneod/pypppd.py (C) 2008 M. Dietrich.
+
+Package: ogsmd.modems.abstractcmda
+Module: pdp
+
+"""
+
+__version__ = "0.3.0"
+MODULE_NAME = "ogsmd.modems.abstractcdma.pdp"
+
+from .mediator import AbstractMediator
+from .overlay import OverlayFile
+
+from framework.patterns.kobject import KObjectDispatcher
+from framework.patterns.processguard import ProcessGuard
+
+import gobject
+import os, subprocess, signal, copy
+
+import logging
+logger = logging.getLogger( MODULE_NAME )
+
+#=========================================================================#
+class Pdp( AbstractMediator ):
+#=========================================================================#
+    """
+    Encapsulates the state of (all) PDP connections on a modem
+    """
+
+    _instance = None
+
+    @classmethod
+    def getInstance( klass, dbus_object=None ):
+        if klass._instance is None and dbus_object is not None:
+            klass._instance = Pdp( dbus_object )
+        return klass._instance
+
+    def __init__( self, dbus_object, **kwargs ):
+        AbstractMediator.__init__( self, dbus_object, None, None, **kwargs )
+        self._callchannel = self._object.modem.communicationChannel( "PdpMediator" )
+        self._netchannel = self._object.modem.communicationChannel( "NetworkMediator" )
+
+        self.state = "release" # initial state
+        self.ppp = None
+        self.overlays = []
+
+        # FIXME: add match only while running pppd
+        KObjectDispatcher.addMatch( "addlink", "", self._onAddLinkEvent )
+
+    def _onInterfaceChange( action, path, **kwargs ):
+        logger.debug( "detected interface change", action, path )
+
+    #
+    # public
+    #
+    def setParameters( self, apn, user, password ):
+        self.pds = copy.copy( self.__class__.PPP_DAEMON_SETUP )
+        self.pds[self.__class__.PPP_CONNECT_CHAT_FILENAME] = self.__class__.PPP_DAEMON_SETUP[self.__class__.PPP_CONNECT_CHAT_FILENAME] % apn
+
+        apn, user, password = str(apn), str(user), str(password)
+
+        # check whether pppd needs to handle setup and teardown
+        if self._object.modem.data( "pppd-does-setup-and-teardown" ):
+            # merge with modem specific options
+            self.ppp_options = self.__class__.PPP_OPTIONS_GENERAL + self._object.modem.data( "pppd-configuration" )
+        else:
+            self.ppp_options = self._object.modem.data( "pppd-configuration" )
+
+        # merge with user and password settings
+        if user:
+            logger.info( "configuring ppp for user '%s' w/ password '%s'" % ( user, password ) )
+            self.ppp_options += [ "user", user ]
+            self.pds[self.__class__.PPP_PAP_SECRETS_FILENAME] = '%s * "%s" *\n' % ( user or '*', password )
+            self.pds[self.__class__.PPP_CHAP_SECRETS_FILENAME] =  '%s * "%s" *\n'% ( user or '*', password )
+
+        self.childwatch_source = None
+
+    def isActive( self ):
+        return self.state == "active"
+
+    def activate( self ):
+        self._activate()
+
+    def deactivate( self ):
+        self._deactivate()
+
+    def status( self ):
+        return self.state
+
+    #
+    # private
+    #
+    def _prepareFiles( self ):
+        for filename, overlay in self.pds.iteritems():
+            logger.debug( "preparing file %s" % filename )
+            f = OverlayFile( filename, overlay=overlay )
+            f.store()
+            self.overlays.append( f )
+
+    def _recoverFiles( self ):
+        for f in self.overlays:
+            logger.debug( "recovering file %s" % f.name )
+            f.restore()
+        self.overlays = []
+
+    def _activate( self ):
+        if self.ppp is not None and self.ppp.isRunning():
+            raise Exception( "already active" )
+
+        self.port = str( self._object.modem.dataPort() )
+        if not self.port:
+            raise Exception( "no device" )
+
+        logger.debug( "activate got port %s" % self.port )
+        ppp_commandline = [ self.__class__.PPP_BINARY, self.port ] + self.ppp_options
+        logger.info( "launching ppp as commandline %s" % ppp_commandline )
+
+        self._prepareFiles()
+        self.ppp = ProcessGuard( ppp_commandline )
+        self.ppp.execute( onExit=self._spawnedProcessDone, onError=self._onPppError, onOutput=self._onPppOutput )
+
+        logger.info( "pppd launched. See syslog (e.g. logread -f) for output." )
+
+        # FIXME that's somewhat premature. we might adopt the following states:
+        # "setup", "active", "shutdown", "release"
+
+        self._updateState( "outgoing" )
+
+    def _updateState( self, newstate ):
+        if newstate != self.state:
+            self.state = newstate
+            self._object.ContextStatus( 1, newstate, {} )
+
+    def _deactivate( self ):
+        logger.info( "shutting down pppd" )
+        self.ppp.shutdown()
+
+    def _spawnedProcessDone( self, pid, exitcode, exitsignal ):
+        logger.info( "pppd exited with code %d, signal %d" % ( exitcode, exitsignal ) )
+
+        # FIXME check whether this was a planned exit or not, if not, try to recover
+
+        self._updateState( "release" )
+        self._recoverFiles()
+
+        # FIXME at this point, the default route might be wrong, if someone killed pppd
+
+        # force releasing context and attachment to make sure that
+        # the next ppp setup will find the data port in command mode
+        self._netchannel.enqueue( "+CGACT=0;+CGATT=0", lambda a,b:None, lambda a,b:None )
+
+    def _onAddLinkEvent( self, action, path, **properties ):
+        """
+        Called by KObjectDispatcher
+        """
+        try:
+            device = properties["dev"]
+            flags = properties["flags"]
+        except KeyError:
+            pass # not enough information
+        else:
+            if device == "ppp0" and "UP" in flags:
+                self._updateState( "active" )
+
+    def _onPppError( self, text ):
+        print "ppp error:", repr(text)
+
+    def _onPppOutput( self, text ):
+        print "ppp output:", repr(text)
+
+    # class wide constants constants
+
+    PPP_CONNECT_CHAT_FILENAME = "/var/tmp/ogsmd/gprs-connect-chat"
+    PPP_DISCONNECT_CHAT_FILENAME = "/var/tmp/ogsmd/gprs-disconnect-chat"
+
+    PPP_PAP_SECRETS_FILENAME = "/etc/ppp/pap-secrets"
+    PPP_CHAP_SECRETS_FILENAME = "/etc/ppp/chap-secrets"
+
+    PPP_OPTIONS_GENERAL = [ "connect", PPP_CONNECT_CHAT_FILENAME, "disconnect", PPP_DISCONNECT_CHAT_FILENAME ]
+
+    PPP_BINARY = "/usr/sbin/pppd"
+
+    PPP_DAEMON_SETUP = {}
+
+    PPP_DAEMON_SETUP[ PPP_CONNECT_CHAT_FILENAME ] = r"""#!/bin/sh -e
+exec /usr/sbin/chat -v\
+    'ABORT' 'BUSY'\
+    'ABORT' 'DELAYED'\
+    'ABORT' 'ERROR'\
+    'ABORT' 'NO ANSWER'\
+    'ABORT' 'NO CARRIER'\
+    'ABORT' 'NO DIALTONE'\
+    'ABORT' 'RINGING'\
+    'ABORT' 'VOICE'\
+    'TIMEOUT' '5'\
+    '' '+++AT'\
+    'OK-\k\k\k\d+++ATH-OK' 'ATE0Q0V1'\
+    'OK' 'AT+CMEE=2'\
+    'OK' 'AT+CGDCONT=1,"IP","%s"'\
+    'TIMEOUT' '180'\
+    'OK' 'ATD*99#'\
+    'CONNECT' '\d\c'
+"""
+
+    PPP_DAEMON_SETUP[ PPP_DISCONNECT_CHAT_FILENAME ] = r"""#!/bin/sh -e
+echo disconnect script running...
+"""
+
+    PPP_DAEMON_SETUP["/etc/ppp/ip-up.d/08setupdns"] = """#!/bin/sh -e
+cp /var/run/ppp/resolv.conf /etc/resolv.conf
+"""
+
+    PPP_DAEMON_SETUP["/etc/ppp/ip-down.d/92removedns"] = """#!/bin/sh -e
+echo nameserver 127.0.0.1 > /etc/resolv.conf
+"""
+
+    PPP_DAEMON_SETUP[PPP_PAP_SECRETS_FILENAME] = '* * "%s" *\n' % ''
+
+    PPP_DAEMON_SETUP[PPP_CHAP_SECRETS_FILENAME] =  '* * "%s" *\n'% ''
+
+#=========================================================================#
+if __name__ == "__main__":
+#=========================================================================#
+    pass
diff --git a/framework/subsystems/ogsmd/modems/abstractcdma/unsolicited.py b/framework/subsystems/ogsmd/modems/abstractcdma/unsolicited.py
new file mode 100644
index 0000000..a362bec
--- /dev/null
+++ b/framework/subsystems/ogsmd/modems/abstractcdma/unsolicited.py
@@ -0,0 +1,298 @@
+#!/usr/bin/env python
+"""
+The Open GSM Daemon - Python Implementation
+
+(C) 2008 Daniel Willmann <daniel@totalueberwachung.de>
+(C) 2008 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
+(C) 2008 Openmoko, Inc.
+
+GPLv2 or later
+
+Package: ogsmd.modems.abstractcdma
+Module: unsolicited
+"""
+
+__version__ = "0.9.9.5"
+MODULE_NAME = "ogsmd.modems.abstractcdma.unsolicited"
+
+import calling, pdp
+
+from ogsmd.gsm.decor import logged
+from ogsmd.gsm import const, convert
+from ogsmd.helpers import safesplit
+from ogsmd.modems import currentModem
+import ogsmd.gsm.sms
+
+import logging
+logger = logging.getLogger( MODULE_NAME )
+
+import gobject
+
+KEYCODES = {}
+
+#=========================================================================#
+class AbstractUnsolicitedResponseDelegate( object ):
+#=========================================================================#
+
+    def __init__( self, dbus_object, mediator ):
+        self._object = dbus_object
+        self._mediator = mediator
+        self._callHandler = calling.CallHandler.getInstance( dbus_object )
+        self._callHandler.setHook( self._cbCallHandlerAction )
+
+        self.lac = None
+        self.cid = None
+
+        self._syncTimeout = None
+
+    def _sendStatus( self ):
+        self._object.Status( self.operator, self.register, self.strength )
+
+    #
+    # unsolicited callbacks (alphabetically sorted, please keep it that way)
+    #
+
+    # PDU mode: +CBM: 88\r\n001000DD001133DAED46ABD56AB5186CD668341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D100
+    # or in text mode:
+    # +CBM: 16,221,0,1,1\r\n347747555093\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\n"
+    def plusCBM( self, righthandside, pdu ):
+        """
+        Cell Broadcast Message
+        """
+        values = safesplit( righthandside, ',' )
+        if len( values ) == 1: # PDU MODE
+            cb = ogsmd.gsm.sms.CellBroadcast.decode(pdu)
+            sn = cb.sn
+            channel = cb.mid
+            dcs = cb.dcs
+            page = cb.page
+            data = cb.ud
+        elif len( values ) == 5: # TEXT MODE
+            sn, mid, dcs, page, pages = values
+            channel = int(mid)
+            data = pdu
+        else:
+            logger.warning( "unrecognized +CBM cell broadcast notification, please fix me..." )
+            return
+        self._object.IncomingCellBroadcast( channel, data )
+
+    # +CGEV: ME DEACT "IP","010.161.025.237",1
+    # +CGEV: ME DETACH
+    def plusCGEV( self, righthandside ):
+        """
+        Gprs Context Event
+        """
+        values = safesplit( righthandside, ',' )
+        if len( values ) == 1: # detach, but we're not having an IP context online
+            if values[0] == "ME DETACH":
+                # FIXME this will probably lead to a zombie
+                instance = pdp.Pdp.getInstance()
+                if instance is not None:
+                    instance.deactivate()
+                # FIXME force dropping the dataport, this will probably kill the zombie
+        elif len( values ) >= 3: # detach while we were attached
+            pass
+
+        # +CGREG: 2
+        # +CGREG: 1,"000F","5B4F
+    def plusCGREG( self, righthandside ):
+        """
+        Gprs Registration Status Update
+        """
+        charset = currentModem()._charsets["DEFAULT"]
+        values = safesplit( righthandside, ',' )
+        status = {}
+        status["registration"] = const.REGISTER_STATUS[int(values[0])]
+        if len( values ) >= 3:
+            status["lac"] = values[1].strip( '"' ).decode(charset)
+            status["cid"] = values[2].strip( '"' ).decode(charset)
+        if len( values ) == 4:
+            status["act"] = const.REGISTER_ACT[int(values[3])]
+        else: # AcT defaults to GSM
+            status["act"] = const.REGISTER_ACT[ 0 ]
+        self._object.NetworkStatus( status )
+
+    # +CREG: 1,"000F","032F"
+    # +CREG: 1,"000F","032F",2
+    def plusCREG( self, righthandside ):
+        """
+        Network Registration Status Update
+        """
+        charset = currentModem()._charsets["DEFAULT"]
+        values = safesplit( righthandside, ',' )
+        self.register = const.REGISTER_STATUS[int(values[0])]
+        if len( values ) >= 3:
+            self.lac = values[1].strip( '"' ).decode(charset)
+            self.cid = values[2].strip( '"' ).decode(charset)
+        if len( values ) == 4:
+            self.act = const.REGISTER_ACT[int(values[3])]
+        else: # AcT defaults to GSM
+            self.act = const.REGISTER_ACT[ 0 ]
+
+        self._mediator.NetworkGetStatus( self._object, self.statusOK, self.statusERR )
+
+    # +CLIP: "+496912345678",145,,,,0
+    def plusCLIP( self, righthandside ):
+        """
+        Connecting Line Identification Presence
+        """
+        number, ntype, rest = safesplit( righthandside, ',', 2 )
+        number = number.replace( '"', '' )
+        logger.warning( "plusCLIP not handled -- please fix me" )
+        #self._mediator.Call.clip( self._object, const.phonebookTupleToNumber( number, int(ntype ) ) )
+
+    # +CMT: "004D00690063006B006500790020007000720069",22
+    # 0791947107160000000C9194712716464600008001301131648003D9B70B
+    def plusCMT( self, righthandside, pdu ):
+        """
+        Message Transfer Indication
+        """
+        header = safesplit( righthandside, ',' )
+        length = int(header[1])
+        # Now we decode the actual PDU
+        sms = ogsmd.gsm.sms.SMS.decode( pdu, "sms-deliver" )
+        self._object.IncomingMessage( str(sms.addr), sms.ud, sms.properties )
+
+    # +CDS: <PDU size>\r\n<PDU>
+    def plusCDS( self, righthandside, pdu ):
+        """
+        Incoming delivery report
+        """
+        sms = ogsmd.gsm.sms.SMS.decode( pdu, "sms-status-report" )
+        self._object.IncomingMessageReceipt( str(sms.addr), sms.ud, sms.properties )
+
+        # Always acknoledge a status report right away
+        sms = ogsmd.gsm.sms.SMSDeliverReport(True)
+        pdu = sms.pdu()
+        commchannel = self._object.modem.communicationChannel( "UnsolicitedMediator" )
+        # XXX: Replace the lambda with an actual funtion in error case?
+        commchannel.enqueue( '+CNMA=1,%i\r%s' % ( len(pdu)/2, pdu), lambda x,y: None, lambda x,y: None )
+
+    # +CKEV: 19,1
+    # +CKEV: 19,0
+    def plusCKEV( self, righthandside ):
+        values = safesplit( righthandside, ',' )
+        keyname = KEYCODES.get( int( values[0] ), "unknown" )
+        pressed = bool( int( values[1] ) )
+        self._object.KeypadEvent( keyname, pressed )
+
+    # +CMTI: "SM",7
+    def plusCMTI( self, righthandside ):
+        """
+        Message Transfer Indication
+        """
+        storage, index = safesplit( righthandside, ',' )
+        if storage != '"SM"':
+            logger.warning( "unhandled +CMTI message notification" )
+        else:
+            self._object.IncomingStoredMessage( int(index) )
+
+    # +CRING: VOICE
+    # +CRING: REL ASYNC
+    def plusCRING( self, calltype ):
+        """
+        Incoming call
+        """
+        self._syncCallStatus( "RING" )
+        self._startTimeoutIfNeeded()
+
+    # +CMS ERROR: 322
+    def plusCMS_ERROR( self, righthandside ):
+        """
+        Incoming unsolicited error
+
+        Seen, when we are using SMS in SIM-buffered mode.
+        """
+        errornumber = int( righthandside )
+        if errornumber == 322:
+            self._object.MemoryFull()
+
+    # +CTZV: 35 (observed in Taipei, UTC+7)
+    # +CTZV: 105 (observed in UTC-4)
+    def plusCTZV( self, righthandside ):
+        """
+        Incoming Timezone Report
+        """
+        self._object.TimeZoneReport( const.ctzvToTimeZone( righthandside ) )
+
+    # +CUSD: 0," Aktuelles Guthaben: 10.00 EUR.",0'
+    def plusCUSD( self, righthandside ):
+        """
+        Incoming USSD result
+        """
+        charset = currentModem()._charsets["USSD"]
+        values = safesplit( righthandside, ',' )
+        if len( values ) == 1:
+            mode = const.NETWORK_USSD_MODE[int(values[0])]
+            self._object.IncomingUssd( mode, "" )
+        elif len( values ) == 3:
+            mode = const.NETWORK_USSD_MODE[int(values[0])]
+            message = values[1].strip( '" ' ).decode(charset)
+            self._object.IncomingUssd( mode, message )
+        else:
+            logger.warning( "Ignoring unknown format: '%s'" % righthandside )
+    #
+    # helpers
+    #
+
+    def statusOK( self, status ):
+        if self.lac is not None:
+            status["lac"] = self.lac
+        if self.cid is not None:
+            status["cid"] = self.cid
+        status["act"] = self.act
+        self._object.Status( status ) # send dbus signal
+
+    def statusERR( self, values ):
+        logger.warning( "statusERR... ignoring" )
+
+    def _startTimeoutIfNeeded( self ):
+        if self._syncTimeout is None:
+            self._syncTimeout = gobject.timeout_add_seconds( 1, self._cbSyncTimeout )
+
+    def _syncCallStatus( self, initiator ):
+       self._mediator.CallListCalls( self._object, self._syncCallStatus_ok, self._syncCallStatus_err )
+
+    def _syncCallStatus_ok( self, calls ):
+        seen = []
+        for callid, status, properties in calls:
+            seen.append( callid )
+            self._callHandler.statusChangeFromNetwork( callid, {"status": status} )
+        # synthesize remaining calls
+        if not 1 in seen:
+            self._callHandler.statusChangeFromNetwork( 1, {"status": "release"} )
+        if not 2 in seen:
+            self._callHandler.statusChangeFromNetwork( 2, {"status": "release"} )
+
+    def _syncCallStatus_err( self, request, error ):
+        logger.warning( "+CLCC didn't succeed -- ignoring" )
+
+    def _cbSyncTimeout( self, *args, **kwargs ):
+        """
+        Called by the glib mainloop while anything call-related happens.
+        """
+        logger.debug( "sync timeout while GSM is not idle" )
+        self._syncCallStatus( "SYNC TIMEOUT" )
+
+        if self._callHandler.isBusy():
+            logger.debug( "call handler is busy" )
+            return True # glib mainloop: please call me again
+        else:
+            logger.debug( "call handler is not busy" )
+            self._syncTimeout = None
+            return False # glib mainloop: don't call me again
+
+    def _cbCallHandlerAction( self, action, result ):
+        """
+        Called by the call handler once a user-initiated action has been performed.
+        """
+        self._syncCallStatus( "MEDIATOR ACTION" )
+        logger.debug( "call handler action %s w/ result %s" % ( action, result ) )
+        if result is not False:
+            if action == "initiate":
+                first, second = self._callHandler.status()
+                if first == "release":
+                    self._callHandler.statusChangeFromNetwork( 1, {"status": "outgoing"} )
+                else:
+                    self._callHandler.statusChangeFromNetwork( 2, {"status": "outgoing"} )
+            self._startTimeoutIfNeeded()
diff --git a/framework/subsystems/ogsmd/modems/qualcomm_msm_cdma/__init__.py b/framework/subsystems/ogsmd/modems/qualcomm_msm_cdma/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/framework/subsystems/ogsmd/modems/qualcomm_msm_cdma/channel.py b/framework/subsystems/ogsmd/modems/qualcomm_msm_cdma/channel.py
new file mode 100644
index 0000000..b6f484d
--- /dev/null
+++ b/framework/subsystems/ogsmd/modems/qualcomm_msm_cdma/channel.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+"""
+The Open GSM Daemon - Python Implementation
+
+(C) 2008 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
+(C) 2008 Openmoko, Inc.
+GPLv2 or later
+
+Package: ogsmd.modems.qualcomm_msm
+Module: channel
+"""
+
+__version__ = "0.4.0"
+MODULE_NAME = "ogsmd.modems.qualcomm_msm_cdma.channel"
+
+from ogsmd.modems.abstractcdma.channel import AbstractCdmaModemChannel
+from ogsmd.gsm import parser
+
+import itertools, select
+
+import logging
+logger = logging.getLogger( MODULE_NAME )
+
+#=========================================================================#
+class SingleLineCdmaChannel( AbstractCdmaModemChannel ):
+#=========================================================================#
+    def __init__( self, *args, **kwargs ):
+        if not "timeout" in kwargs:
+            kwargs["timeout"] = 60*60
+        AbstractCdmaModemChannel.__init__( self, *args, **kwargs )
+
+    def _populateCommands( self ):
+        """
+        Populate the command queues to be sent on modem state changes.
+        """
+        AbstractCdmaModemChannel._populateCommands( self ) # prepopulated
+
+        c = self._commands["init"]
+        c.remove( "Z" ) # do not reset, otherwise it will fall back to V1
+        # reenable unsolicited responses, we don't have a seperate channel
+        # so we need to process them here as well
+        c.append( "+CLIP=1" ) # calling line identification presentation enable
+        c.append( "+COLP=1" ) # connected line identification presentation enable
+        c.append( "+CCWA=1" ) # call waiting
+        c.append( "+CSSN=1,1" ) # supplementary service notifications: send unsol. code
+        c.append( "+CTZU=1" ) # timezone update
+        c.append( "+CTZR=1" ) # timezone reporting
+        c.append( "+CREG=2" ) # registration information (NOTE not all modems support =2)
+        c.append( "+CAOC=2" ) # advice of charge: send unsol. code
+        # GPRS unsolicited
+        c.append( "+CGEREP=2,1" )
+        c.append( "+CGREG=2" )
+
+    def installParser( self ):
+        """
+        Install a specific low level parser for this channel.
+        """
+        self.parser = parser.QualcommGsmViolationParser( self._handleResponseToRequest, self._handleUnsolicitedResponse )
+
+    def _hookLowLevelInit( self ):
+        """
+        Low level initialization of channel.
+
+        Qualcomm default mode is V0, which completely disturbs our parser.
+        We send a special init here until it responds in V1 mode.
+        """
+        for i in itertools.count():
+            logger.debug( "(modem init... try #%d)", i+1 )
+            select.select( [], [self.serial.fd], [], 0.5 )
+            self.serial.write( "ATE0Q0V1\r\n" )
+            r, w, x = select.select( [self.serial.fd], [], [], 0.5 )
+            if r:
+                try:
+                    buf = self.serial.inWaiting()
+                # FIXME remove catchall here
+                except:
+                    self.serial.close()
+                    path = self.pathfactory( self.name )
+                    if not path: # path is None or ""
+                        return False
+                    self.serial.port = str( path )
+                    self.serial.open()
+                    buf = self.serial.inWaiting()
+                ok = self.serial.read(buf).strip()
+                logger.debug( "read: %s", ok )
+                if "OK" in ok or "AT" in ok:
+                    break
+            logger.debug( "(modem not responding)" )
+            if i == 5:
+                logger.debug( "(reopening modem)" )
+                self.serial.close()
+                path = self.pathfactory( self.name )
+                if not path: # path is None or ""
+                    return False
+                self.serial.port = str( path )
+                self.serial.open()
+
+            if i == 10:
+                logger.warning( "(can't read from modem. giving up)" )
+                self.serial.close()
+                return False
+        logger.info( "%s: responding OK" % self )
+        self.serial.flushInput()
+
+        return True
diff --git a/framework/subsystems/ogsmd/modems/qualcomm_msm_cdma/mediator.py b/framework/subsystems/ogsmd/modems/qualcomm_msm_cdma/mediator.py
new file mode 100644
index 0000000..db30409
--- /dev/null
+++ b/framework/subsystems/ogsmd/modems/qualcomm_msm_cdma/mediator.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+"""
+The Open GSM Daemon - Python Implementation
+
+(C) 2008 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
+(C) 2008 Openmoko, Inc.
+GPLv2 or later
+
+Package: ogsmd.modems.qualcomm_msm_cdma
+Module: mediator
+"""
+
+from ogsmd.modems.abstractcdma.mediator import *
+
+__version__ = "0.1.0"
+MODULE_NAME = "ogsmd.modems.qualcomm_msm_cdma.mediator"
+
+#=========================================================================#
+class PdpActivateContext( PdpMediator ):
+#=========================================================================#
+    def trigger( self ):
+        pdpConnection = Pdp.getInstance( self._object )
+        if pdpConnection.isActive():
+            self._ok()
+        else:
+            pdpConnection.setParameters( self.apn, self.user, self.password )
+            self._commchannel.enqueue( '+CGDCONT=1,"IP","%s"' % self.apn, self.responseFromChannel, self.errorFromChannel )
+
+    def responseFromChannel( self, request, response ):
+        if response[-1] != "OK":
+            PdpMediator.responseFromChannel( self, request, response )
+        else:
+            self._commchannel.enqueue( "D*99***1#", self.responseFromChannel2, self.errorFromChannel )
+
+    def responseFromChannel2( self, request, response ):
+        if response[-1] == "NO CARRIER":
+            PdpMediator.responseFromChannel( self, request, response )
+        else:
+            pdpConnection = Pdp.getInstance( self._object )
+            pdpConnection.activate()
+            self._ok()
diff --git a/framework/subsystems/ogsmd/modems/qualcomm_msm_cdma/modem.py b/framework/subsystems/ogsmd/modems/qualcomm_msm_cdma/modem.py
new file mode 100644
index 0000000..34af682
--- /dev/null
+++ b/framework/subsystems/ogsmd/modems/qualcomm_msm_cdma/modem.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+"""
+The Open GSM Daemon - Python Implementation
+
+(C) 2008 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
+(C) 2008 Openmoko, Inc.
+GPLv2 or later
+
+Package: ogsmd.modems.qualcomm_msm_cdma
+Module: modem
+"""
+
+__version__ = "1.1.0"
+MODULE_NAME = "ogsmd.modems.qualcomm_msm_cdma.modem"
+
+import mediator
+
+from ..abstractcdma.modem import AbstractCdmaModem
+
+from .channel import SingleLineCdmaChannel
+from .unsolicited import UnsolicitedResponseDelegate
+
+from framework.config import config
+
+#=========================================================================#
+class QualcommMsmCdma( AbstractCdmaModem ):
+#=========================================================================#
+
+    def __init__( self, *args, **kwargs ):
+        AbstractModem.__init__( self, *args, **kwargs )
+
+        # The one and only serial line
+        self._channels["SINGLE"] = SingleLineCdmaChannel( self.portfactory, "ogsmd", modem=self )
+        # configure channels
+        self._channels["SINGLE"].setDelegate( UnsolicitedResponseDelegate( self._object, mediator ) )
+
+        # This modem handles setup and teardown of data connections on its own
+        self._data["pppd-does-setup-and-teardown"] = False
+
+        # This modem has a special ppp configuration
+        self._data["pppd-configuration"] = [ \
+            'nodetach',
+            'debug',
+            'defaultroute',
+            "local",
+            'noipdefault',
+            'novj',
+            "novjcomp",
+            #'persist',
+            'proxyarp',
+            'replacedefaultroute',
+            'usepeerdns',
+        ]
+
+    def channel( self, category ):
+        # we do not care about a category here, we only have one channel
+        return self._channels["SINGLE"]
+
+    def portfactory( self, name ):
+        return config.getValue( "ogsmd", "serial", default="/dev/smd0" )
+
+    def dataPort( self ):
+        # FIXME remove duplication and just use pathfactory
+        return config.getValue( "ogsmd", "data", default="/dev/smd7" )
diff --git a/framework/subsystems/ogsmd/modems/qualcomm_msm_cdma/unsolicited.py b/framework/subsystems/ogsmd/modems/qualcomm_msm_cdma/unsolicited.py
new file mode 100644
index 0000000..f2fad4b
--- /dev/null
+++ b/framework/subsystems/ogsmd/modems/qualcomm_msm_cdma/unsolicited.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+"""
+The Open GSM Daemon - Python Implementation
+
+(C) 2008-2009 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
+GPLv2 or later
+
+Package: ogsmd.modems.qualcomm_msm_cdma
+Module: unsolicited
+"""
+
+__version__ = "0.0.1.0"
+MODULE_NAME = "ogsmd.modems.qualcomm_msm_cdma.unsolicited"
+
+from ogsmd.modems.abstractcdma.unsolicited import AbstractUnsolicitedResponseDelegate
+from ogsmd.gsm import const
+from ogsmd.helpers import safesplit
+import ogsmd.gsm.sms
+
+import logging
+logger = logging.getLogger( MODULE_NAME )
+
+class UnsolicitedResponseDelegate( AbstractUnsolicitedResponseDelegate ):
+
+    def __init__( self, *args, **kwargs ):
+        AbstractUnsolicitedResponseDelegate.__init__( self, *args, **kwargs )
+
+    #
+    # GSM standards
+    #
+
+    #
+    # Proprietary URCs
+    #
+
+    # @HTCCSQ: 2
+    def atHTCCSQ( self, righthandside ):
+        """Indicates signal strength"""
+        value = int( righthandside )
+        self._object.SignalStrength( 20*value )
+
+    # +PB_READY
+    def plusPB_READY( self, righthandside ):
+        """Indicates phonebook readyness"""
+        self._object.ReadyStatus( True )
+
diff --git a/framework/subsystems/ogsmd/modems/singlelinecdma/__init__.py b/framework/subsystems/ogsmd/modems/singlelinecdma/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/framework/subsystems/ogsmd/modems/singlelinecdma/channel.py b/framework/subsystems/ogsmd/modems/singlelinecdma/channel.py
new file mode 100644
index 0000000..db4ec3e
--- /dev/null
+++ b/framework/subsystems/ogsmd/modems/singlelinecdma/channel.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+"""
+The Open GSM Daemon - Python Implementation
+
+(C) 2007-2008 M. Dietrich
+(C) 2008 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
+(C) 2008 Openmoko, Inc.
+GPLv2 or later
+
+Package: ogsmd.modems.singelinecdma
+Module: channel
+"""
+
+from ogsmd.modems.abstractcdma.channel import AbstractCdmaModemChannel
+
+#=========================================================================#
+class SingleLineCdmaChannel( AbstractCdmaModemChannel ):
+#=========================================================================#
+    def __init__( self, *args, **kwargs ):
+        if not "timeout" in kwargs:
+            kwargs["timeout"] = 60*60
+        AbstractCdmaModemChannel.__init__( self, *args, **kwargs )
+
+    def _populateCommands( self ):
+        """
+        Populate the command queues to be sent on modem state changes.
+        """
+
+        AbstractCdmaModemChannel._populateCommands( self ) # prepopulated
+
+        c = self._commands["init"]
+        # reenable unsolicited responses, we don't have a seperate channel
+        # so we need to process them here as well
+        c.append( '+CLIP=1' ) # calling line identification presentation enable
+        c.append( '+COLP=1' ) # connected line identification presentation enable
+        c.append( '+CCWA=1' ) # call waiting
+#       c.append( "+CSSN=1,1" ) # supplementary service notifications: send unsol. code
+        c.append( '+CTZU=1' ) # timezone update
+        c.append( '+CTZR=1' ) # timezone reporting
+        c.append( '+CREG=2' ) # registration information (NOTE not all modems support =2)
+#       c.append( "+CAOC=2" ) # advice of charge: send unsol. code
+        # GPRS unsolicited
+#       c.append( "+CGEREP=2,1" )
+#       c.append( "+CGREG=2" )
diff --git a/framework/subsystems/ogsmd/modems/singlelinecdma/mediator.py b/framework/subsystems/ogsmd/modems/singlelinecdma/mediator.py
new file mode 100644
index 0000000..0f0a2a5
--- /dev/null
+++ b/framework/subsystems/ogsmd/modems/singlelinecdma/mediator.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+"""
+The Open GSM Daemon - Python Implementation
+
+(C) 2008 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
+(C) 2008 Openmoko, Inc.
+GPLv2 or later
+
+Package: ogsmd.modems.singleline
+Module: mediator
+"""
+
+from ogsmd.modems.abstract.mediator import *
+
+# add overrides here
+
diff --git a/framework/subsystems/ogsmd/modems/singlelinecdma/modem.py b/framework/subsystems/ogsmd/modems/singlelinecdma/modem.py
new file mode 100644
index 0000000..ec7f658
--- /dev/null
+++ b/framework/subsystems/ogsmd/modems/singlelinecdma/modem.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+"""
+The Open GSM Daemon - Python Implementation
+
+(C) 2008 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
+(C) 2008 Openmoko, Inc.
+GPLv2 or later
+
+Package: ogsmd.modems.singlelinecdma
+Module: modem
+"""
+
+__version__ = "1.0.0"
+MODULE_NAME = "singlelinecmda"
+
+import mediator
+
+from ..abstract.modem import AbstractCdmaModem
+
+from .channel import SingleLineCdmaChannel
+from .unsolicited import UnsolicitedResponseDelegate
+
+from framework.config import config
+
+#=========================================================================#
+class SingleLine( AbstractCdmaModem ):
+#=========================================================================#
+
+    def __init__( self, *args, **kwargs ):
+        AbstractModem.__init__( self, *args, **kwargs )
+
+        # The one and only serial line
+        self._channels["SINGLE"] = SingleLineChannel( self.portfactory, "ogsmd", modem=self )
+        # configure channels
+        self._channels["SINGLE"].setDelegate( UnsolicitedResponseDelegate( self._object, mediator ) )
+
+    def channel( self, category ):
+            # we do not care about a category here, we only have one channel
+            return self._channels["SINGLE"]
+
+    def portfactory( self, name ):
+        return config.getValue( "ogsmd", "serial", default="/dev/ttySAC0" )
diff --git a/framework/subsystems/ogsmd/modems/singlelinecdma/unsolicited.py b/framework/subsystems/ogsmd/modems/singlelinecdma/unsolicited.py
new file mode 100644
index 0000000..e63c0ba
--- /dev/null
+++ b/framework/subsystems/ogsmd/modems/singlelinecdma/unsolicited.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+"""
+The Open GSM Daemon - Python Implementation
+
+(C) 2008 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
+(C) 2008 Openmoko, Inc.
+GPLv2 or later
+
+Package: ogsmd.modems.singleline
+Module: unsolicited
+"""
+
+from ..abstract.unsolicited import AbstractUnsolicitedResponseDelegate
+
+class UnsolicitedResponseDelegate( AbstractUnsolicitedResponseDelegate ):
+
+    def __init__( self, *args, **kwargs ):
+        AbstractUnsolicitedResponseDelegate.__init__( self, *args, **kwargs )
+

