diff --git a/.gitignore b/.gitignore
index de47842..8b6e24b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,6 +35,7 @@ src/tests/test-modem-helpers
 src/tests/test-charsets
 src/tests/test-qcdm-serial-port
 policy/org.freedesktop.modem-manager.policy
+include/
 
 libqcdm/tests/test-qcdm
 
@@ -48,3 +49,5 @@ m4/gtk-doc.m4
 m4/intltool.m4
 m4/libtool.m4
 m4/lt*.m4
+
+TAGS
diff --git a/plugins/mm-modem-huawei-gsm.c b/plugins/mm-modem-huawei-gsm.c
index e038069..5f4c2fb 100644
--- a/plugins/mm-modem-huawei-gsm.c
+++ b/plugins/mm-modem-huawei-gsm.c
@@ -25,21 +25,25 @@
 #include "mm-modem-huawei-gsm.h"
 #include "mm-modem-gsm-network.h"
 #include "mm-modem-gsm-card.h"
+#include "mm-modem-gsm-ussd.h"
 #include "mm-errors.h"
 #include "mm-callback-info.h"
 #include "mm-at-serial-port.h"
 #include "mm-serial-parsers.h"
 #include "mm-log.h"
+#include "mm-utils.h"
 
 static void modem_init (MMModem *modem_class);
 static void modem_gsm_network_init (MMModemGsmNetwork *gsm_network_class);
 static void modem_gsm_card_init (MMModemGsmCard *gsm_card_class);
+static void modem_gsm_ussd_init (MMModemGsmUssd *ussd_class);
 
 G_DEFINE_TYPE_EXTENDED (MMModemHuaweiGsm, mm_modem_huawei_gsm, MM_TYPE_GENERIC_GSM, 0,
                         G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init)
                         G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_NETWORK, modem_gsm_network_init)
-                        G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_CARD, modem_gsm_card_init))
-
+                        G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_CARD, modem_gsm_card_init)
+                        G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_USSD, modem_gsm_ussd_init)
+)
 
 #define MM_MODEM_HUAWEI_GSM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_MODEM_HUAWEI_GSM, MMModemHuaweiGsmPrivate))
 
@@ -874,6 +878,42 @@ out:
     return !!port;
 }
 
+/* Encode to packed GSM - this is what Huawei supports on all known models */
+static char*
+ussd_encode (MMModemGsmUssd *self, const char* command, guint *scheme)
+{
+    char *hex;
+    guint8 *gsm, *packed;
+    guint32 len = 0, packed_len = 0;
+
+    *scheme = MM_MODEM_GSM_USSD_SCHEME_7BIT;
+    gsm = mm_charset_utf8_to_unpacked_gsm (command, &len);
+    packed = gsm_pack (gsm, len, 0, &packed_len);
+    hex = utils_bin2hexstr (packed, packed_len);
+    g_free (packed);
+    g_free (gsm);
+
+    return hex;
+}
+
+/* Unparse packed gsm to utf8 */
+static char*
+ussd_decode (MMModemGsmUssd *self, const char* reply, guint scheme)
+{
+    char *bin, *utf8;
+    guint8 *unpacked;
+    gsize bin_len;
+    guint32 unpacked_len;
+
+    bin = utils_hexstr2bin (reply, &bin_len);
+    unpacked = gsm_unpack ((guint8*)bin, bin_len, 0, &unpacked_len);
+    utf8 = (char*)mm_charset_gsm_unpacked_to_utf8 (unpacked, unpacked_len);
+
+    g_free (bin);
+    g_free (unpacked);
+    return utf8;
+}
+
 /*****************************************************************************/
 
 static void
@@ -902,6 +942,13 @@ mm_modem_huawei_gsm_init (MMModemHuaweiGsm *self)
 }
 
 static void
+modem_gsm_ussd_init (MMModemGsmUssd *ussd_class)
+{
+    ussd_class->encode = ussd_encode;
+    ussd_class->decode = ussd_decode;
+}
+
+static void
 mm_modem_huawei_gsm_class_init (MMModemHuaweiGsmClass *klass)
 {
     GObjectClass *object_class = G_OBJECT_CLASS (klass);
diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c
index d814c57..0c6f9d8 100644
--- a/src/mm-generic-gsm.c
+++ b/src/mm-generic-gsm.c
@@ -4740,7 +4740,9 @@ mm_generic_gsm_ussd_cleanup (MMGenericGsm *self)
 }
 
 static char *
-decode_ussd_response (const char *reply, MMModemCharset cur_charset)
+decode_ussd_response (MMGenericGsm *self,
+                      const char *reply,
+                      MMModemCharset cur_charset)
 {
     char **items, **iter, *p;
     char *str = NULL;
@@ -4771,8 +4773,42 @@ decode_ussd_response (const char *reply, MMModemCharset cur_charset)
     if (p)
         *p = '\0';
 
-    /* FIXME: actually use the given encoding scheme */
-    return mm_modem_charset_hex_to_utf8 (str, cur_charset);
+    return mm_modem_gsm_ussd_decode (MM_MODEM_GSM_USSD (self), str,
+                                     cur_charset);
+}
+
+static char*
+ussd_encode (MMModemGsmUssd *modem, const char* command, guint *scheme)
+{
+    MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+    GByteArray *ussd_command = g_byte_array_new();
+    gboolean success;
+    char *hex = NULL;
+
+    /* encode to cur_charset */
+    success = mm_modem_charset_byte_array_append (ussd_command, command, FALSE,
+                                                  priv->cur_charset);
+    g_warn_if_fail (success == TRUE);
+    if (!success)
+        goto out;
+
+    *scheme = MM_MODEM_GSM_USSD_SCHEME_7BIT;
+    /* convert to hex representation */
+    hex = utils_bin2hexstr (ussd_command->data, ussd_command->len);
+
+ out:
+    g_byte_array_free (ussd_command, TRUE);
+    return hex;
+}
+
+static char*
+ussd_decode (MMModemGsmUssd *modem, const char* reply, guint scheme)
+{
+    MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+    char *converted;
+
+    converted = mm_modem_charset_hex_to_utf8 (reply, priv->cur_charset);
+    return converted;
 }
 
 static void
@@ -4797,7 +4833,7 @@ cusd_received (MMAtSerialPort *port,
     status = g_ascii_digit_value (*reply);
     switch (status) {
     case 0: /* no further action required */
-        converted = decode_ussd_response (reply, priv->cur_charset);
+        converted = decode_ussd_response (self, reply, priv->cur_charset);
         if (priv->pending_ussd_info) {
             /* Response to the user's request */
             mm_callback_info_set_result (priv->pending_ussd_info, converted, g_free);
@@ -4810,7 +4846,7 @@ cusd_received (MMAtSerialPort *port,
         break;
     case 1: /* further action required */
         ussd_state = MM_MODEM_GSM_USSD_STATE_USER_RESPONSE;
-        converted = decode_ussd_response (reply, priv->cur_charset);
+        converted = decode_ussd_response (self, reply, priv->cur_charset);
         if (priv->pending_ussd_info) {
             mm_callback_info_set_result (priv->pending_ussd_info, converted, g_free);
         } else {
@@ -4889,10 +4925,9 @@ ussd_send (MMModemGsmUssd *modem,
     MMCallbackInfo *info;
     char *atc_command;
     char *hex;
-    GByteArray *ussd_command = g_byte_array_new();
+    guint scheme = 0;
     MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
     MMAtSerialPort *port;
-    gboolean success;
 
     g_warn_if_fail (priv->pending_ussd_info == NULL);
 
@@ -4907,14 +4942,16 @@ ussd_send (MMModemGsmUssd *modem,
     /* Cache the callback info since the response is an unsolicited one */
     priv->pending_ussd_info = info;
 
-    /* encode to cur_charset */
-    success = mm_modem_charset_byte_array_append (ussd_command, command, FALSE, priv->cur_charset);
-    g_warn_if_fail (success == TRUE);
-
-    /* convert to hex representation */
-    hex = utils_bin2hexstr (ussd_command->data, ussd_command->len);
-    g_byte_array_free (ussd_command, TRUE);
-    atc_command = g_strdup_printf ("+CUSD=1,\"%s\",15", hex);
+    hex = mm_modem_gsm_ussd_encode (MM_MODEM_GSM_USSD (modem), command, &scheme);
+    if (!hex) {
+        info->error =  g_error_new (MM_MODEM_ERROR,
+                                    MM_MODEM_ERROR_GENERAL,
+                                    "Failed to encode USSD command '%s'",
+                                    command);
+        mm_callback_info_schedule (info);
+        return;
+    }
+    atc_command = g_strdup_printf ("+CUSD=1,\"%s\",%d", hex, scheme);
     g_free (hex);
 
     mm_at_serial_port_queue_command (port, atc_command, 10, ussd_send_done, info);
@@ -5661,6 +5698,8 @@ modem_gsm_ussd_init (MMModemGsmUssd *class)
     class->initiate = ussd_initiate;
     class->respond = ussd_respond;
     class->cancel = ussd_cancel;
+    class->encode = ussd_encode;
+    class->decode = ussd_decode;
 }
 
 static void
diff --git a/src/mm-modem-gsm-ussd.c b/src/mm-modem-gsm-ussd.c
index f90a845..614999c 100644
--- a/src/mm-modem-gsm-ussd.c
+++ b/src/mm-modem-gsm-ussd.c
@@ -137,6 +137,32 @@ mm_modem_gsm_ussd_cancel (MMModemGsmUssd *self,
 
 }
 
+char*
+mm_modem_gsm_ussd_encode (MMModemGsmUssd *self,
+                          const char* command,
+                          guint *schema)
+{
+    if (MM_MODEM_GSM_USSD_GET_INTERFACE (self)->encode)
+        return MM_MODEM_GSM_USSD_GET_INTERFACE (self)->encode(self,
+                                                              command,
+                                                              schema);
+    else
+        return NULL;
+}
+
+char*
+mm_modem_gsm_ussd_decode (MMModemGsmUssd *self,
+                          const char* reply,
+                          guint schema)
+{
+    if (MM_MODEM_GSM_USSD_GET_INTERFACE (self)->decode)
+        return MM_MODEM_GSM_USSD_GET_INTERFACE (self)->decode(self,
+                                                              reply,
+                                                              schema);
+    else
+        return NULL;
+}
+
 /*****************************************************************************/
 
 typedef struct {
diff --git a/src/mm-modem-gsm-ussd.h b/src/mm-modem-gsm-ussd.h
index c8f652b..04d2be8 100644
--- a/src/mm-modem-gsm-ussd.h
+++ b/src/mm-modem-gsm-ussd.h
@@ -54,6 +54,14 @@ struct _MMModemGsmUssd {
     void (*cancel) (MMModemGsmUssd *modem,
                     MMModemFn callback,
                     gpointer user_data);
+
+    gchar* (*encode) (MMModemGsmUssd *modem,
+                      const char* command,
+                      guint *scheme);
+
+    gchar* (*decode) (MMModemGsmUssd *modem,
+                      const char* command,
+                      guint scheme);
 };
 
 GType mm_modem_gsm_ussd_get_type (void);
@@ -72,4 +80,16 @@ void mm_modem_gsm_ussd_cancel (MMModemGsmUssd *self,
                                MMModemFn callback,
                                gpointer user_data);
 
+/* CBS data coding scheme - 3GPP TS 23.038 */
+#define MM_MODEM_GSM_USSD_SCHEME_7BIT 0b00001111;
+#define MM_MODEM_GSM_USSD_SCHEME_UCS2 0b01001000;
+
+char *mm_modem_gsm_ussd_encode (MMModemGsmUssd *self,
+                                const char* command,
+                                guint *scheme);
+
+char *mm_modem_gsm_ussd_decode (MMModemGsmUssd *self,
+                                const char* reply,
+                                guint scheme);
+
 #endif /* MM_MODEM_GSM_USSD_H */
diff --git a/test/ussd.py b/test/ussd.py
index 2040f12..b3528f9 100755
--- a/test/ussd.py
+++ b/test/ussd.py
@@ -20,16 +20,16 @@ import sys, dbus, re
 MM_DBUS_SERVICE='org.freedesktop.ModemManager'
 MM_DBUS_INTERFACE_USSD='org.freedesktop.ModemManager.Modem.Gsm.Ussd'
 
-bus = dbus.SystemBus()
-proxy = bus.get_object(MM_DBUS_SERVICE, sys.argv[1])
-modem = dbus.Interface(proxy, dbus_interface=MM_DBUS_INTERFACE_USSD)
-
 if len(sys.argv) != 3:
-    print "Usage: %s dbus_object ussd"
+    print "Usage: %s dbus_object [<ussd>|cancel]" % sys.argv[0]
     sys.exit(1)
 else:
     arg = sys.argv[2]
 
+bus = dbus.SystemBus()
+proxy = bus.get_object(MM_DBUS_SERVICE, sys.argv[1])
+modem = dbus.Interface(proxy, dbus_interface=MM_DBUS_INTERFACE_USSD)
+
 # For testing purposes treat all "common" USSD sequences as initiate and the
 # rest (except for cancel) as response. See GSM 02.90.
 initiate_re = re.compile('[*#]{1,3}1[0-9][0-9].*#')
