core: don't allow concurrent flashes on the same device

Previously, a few operations (like disable) could trigger a modem
flash in parallel with another flash.  That's wrong, don't allow
that.  At the same time, add in finer-grained error checking on
serial port speed operations, and fix a GSM generic bug that would
send the POWER_UP string on disable.
This commit is contained in:
Dan Williams
2009-09-08 17:31:54 -07:00
parent 6cf01d2ab6
commit 14e5c52f78
12 changed files with 403 additions and 92 deletions

View File

@@ -15,4 +15,22 @@
</tp:docstring>
</tp:error>
<tp:error name="Connected">
<tp:docstring>
Operation could not be performed while the modem is connected.
</tp:docstring>
</tp:error>
<tp:error name="Disconnected">
<tp:docstring>
Operation could not be performed while the modem is disconnected.
</tp:docstring>
</tp:error>
<tp:error name="OperationInProgress">
<tp:docstring>
Operation could not be performed because it is already in progress.
</tp:docstring>
</tp:error>
</tp:errors>

View File

@@ -402,11 +402,17 @@ mbm_emrdy_done (MMSerialPort *port,
}
static void
enable_flash_done (MMSerialPort *port, gpointer user_data)
enable_flash_done (MMSerialPort *port, GError *error, gpointer user_data)
{
MMCallbackInfo *info = user_data;
MMModemMbmPrivate *priv = MM_MODEM_MBM_GET_PRIVATE (info->modem);
if (error) {
info->error = g_error_copy (error);
mm_callback_info_schedule (info);
return;
}
if (priv->have_emrdy) {
/* Modem is ready, no need to check EMRDY */
do_init (port, info);
@@ -427,8 +433,16 @@ disable_done (MMSerialPort *port,
}
static void
disable_flash_done (MMSerialPort *port, gpointer user_data)
disable_flash_done (MMSerialPort *port, GError *error, gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
if (error) {
info->error = g_error_copy (error);
mm_callback_info_schedule (info);
return;
}
mm_serial_port_queue_command (port, "+CMER=0", 5, disable_done, user_data);
}
@@ -449,18 +463,20 @@ enable (MMModem *modem,
g_assert (primary);
if (do_enable) {
if (mm_serial_port_open (primary, &info->error))
mm_serial_port_flash (primary, 100, enable_flash_done, info);
if (info->error)
if (!mm_serial_port_open (primary, &info->error)) {
g_assert (info->error);
mm_callback_info_schedule (info);
return;
}
mm_serial_port_flash (primary, 100, enable_flash_done, info);
} else {
mm_serial_port_queue_command (primary, "+CREG=0", 100, NULL, NULL);
mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem));
if (mm_port_get_connected (MM_PORT (primary)))
mm_serial_port_flash (primary, 1000, disable_flash_done, info);
else
disable_flash_done (primary, info);
disable_flash_done (primary, NULL, info);
}
}

View File

@@ -94,8 +94,16 @@ pre_init_done (MMSerialPort *port,
}
static void
enable_flash_done (MMSerialPort *port, gpointer user_data)
enable_flash_done (MMSerialPort *port, GError *error, gpointer user_data)
{
MMCallbackInfo *info = user_data;
if (error) {
info->error = g_error_copy (error);
mm_callback_info_schedule (info);
return;
}
mm_serial_port_queue_command (port, "E0 V1", 3, pre_init_done, user_data);
}
@@ -110,8 +118,16 @@ disable_done (MMSerialPort *port,
}
static void
disable_flash_done (MMSerialPort *port, gpointer user_data)
disable_flash_done (MMSerialPort *port, GError *error, gpointer user_data)
{
MMCallbackInfo *info = user_data;
if (error) {
info->error = g_error_copy (error);
mm_callback_info_schedule (info);
return;
}
mm_serial_port_queue_command (port, "+CFUN=0", 5, disable_done, user_data);
}
@@ -136,13 +152,15 @@ enable (MMModem *modem,
if (mm_port_get_connected (MM_PORT (primary)))
mm_serial_port_flash (primary, 1000, disable_flash_done, info);
else
disable_flash_done (primary, info);
disable_flash_done (primary, NULL, info);
} else {
if (mm_serial_port_open (primary, &info->error))
mm_serial_port_flash (primary, 100, enable_flash_done, info);
if (info->error)
if (!mm_serial_port_open (primary, &info->error)) {
g_assert (info->error);
mm_callback_info_schedule (info);
return;
}
mm_serial_port_flash (primary, 100, enable_flash_done, info);
}
}

View File

@@ -84,16 +84,32 @@ init_done (MMSerialPort *port,
}
static void
enable_flash_done (MMSerialPort *port, gpointer user_data)
enable_flash_done (MMSerialPort *port, GError *error, gpointer user_data)
{
MMCallbackInfo *info = user_data;
if (error) {
info->error = g_error_copy (error);
mm_callback_info_schedule (info);
return;
}
mm_serial_port_queue_command (port, "Z E0 V1 X4 &C1 +CMEE=1", 3, init_done, user_data);
}
static void
disable_flash_done (MMSerialPort *port, gpointer user_data)
disable_flash_done (MMSerialPort *port, GError *error, gpointer user_data)
{
MMCallbackInfo *info = user_data;
if (error) {
info->error = g_error_copy (error);
mm_callback_info_schedule (info);
return;
}
mm_serial_port_close (port);
mm_callback_info_schedule ((MMCallbackInfo *) user_data);
mm_callback_info_schedule (info);
}
static void
@@ -118,13 +134,15 @@ enable (MMModem *modem,
if (mm_port_get_connected (MM_PORT (primary)))
mm_serial_port_flash (primary, 1000, disable_flash_done, info);
else
disable_flash_done (primary, info);
disable_flash_done (primary, NULL, info);
} else {
if (mm_serial_port_open (primary, &info->error))
mm_serial_port_flash (primary, 100, enable_flash_done, info);
if (info->error)
if (!mm_serial_port_open (primary, &info->error)) {
g_assert (info->error);
mm_callback_info_schedule (info);
return;
}
mm_serial_port_flash (primary, 100, enable_flash_done, info);
}
}

View File

@@ -95,8 +95,16 @@ pre_init_done (MMSerialPort *port,
}
static void
enable_flash_done (MMSerialPort *port, gpointer user_data)
enable_flash_done (MMSerialPort *port, GError *error, gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
if (error) {
info->error = g_error_copy (error);
mm_callback_info_schedule (info);
return;
}
mm_serial_port_queue_command (port, "E0 V1", 3, pre_init_done, user_data);
}
@@ -111,8 +119,16 @@ disable_done (MMSerialPort *port,
}
static void
disable_flash_done (MMSerialPort *port, gpointer user_data)
disable_flash_done (MMSerialPort *port, GError *error, gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
if (error) {
info->error = g_error_copy (error);
mm_callback_info_schedule (info);
return;
}
mm_serial_port_queue_command (port, "+CFUN=0", 5, disable_done, user_data);
}
@@ -137,13 +153,15 @@ enable (MMModem *modem,
if (mm_port_get_connected (MM_PORT (primary)))
mm_serial_port_flash (primary, 1000, disable_flash_done, info);
else
disable_flash_done (primary, info);
disable_flash_done (primary, NULL, info);
} else {
if (mm_serial_port_open (primary, &info->error))
mm_serial_port_flash (primary, 100, enable_flash_done, info);
if (info->error)
if (!mm_serial_port_open (primary, &info->error)) {
g_assert (info->error);
mm_callback_info_schedule (info);
return;
}
mm_serial_port_flash (primary, 100, enable_flash_done, info);
}
}

View File

@@ -69,6 +69,8 @@ mm_modem_error_get_type (void)
ENUM_ENTRY (MM_MODEM_ERROR_GENERAL, "General"),
ENUM_ENTRY (MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED, "OperationNotSupported"),
ENUM_ENTRY (MM_MODEM_ERROR_CONNECTED, "Connected"),
ENUM_ENTRY (MM_MODEM_ERROR_DISCONNECTED, "Disconnected"),
ENUM_ENTRY (MM_MODEM_ERROR_OPERATION_IN_PROGRESS, "OperationInProgress"),
{ 0, 0, 0 }
};

View File

@@ -35,7 +35,9 @@ GType mm_serial_error_get_type (void);
enum {
MM_MODEM_ERROR_GENERAL = 0,
MM_MODEM_ERROR_OPERATION_NOT_SUPPORTED = 1,
MM_MODEM_ERROR_CONNECTED = 2
MM_MODEM_ERROR_CONNECTED = 2,
MM_MODEM_ERROR_DISCONNECTED = 3,
MM_MODEM_ERROR_OPERATION_IN_PROGRESS = 4
};
#define MM_MODEM_ERROR (mm_modem_error_quark ())

View File

@@ -218,8 +218,17 @@ init_done (MMSerialPort *port,
}
static void
flash_done (MMSerialPort *port, gpointer user_data)
flash_done (MMSerialPort *port, GError *error, gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
if (error) {
/* Flash failed for some reason */
info->error = g_error_copy (error);
mm_callback_info_schedule (info);
return;
}
mm_serial_port_queue_command (port, "Z E0 V1 X4 &C1", 3, init_done, user_data);
}
@@ -240,11 +249,13 @@ enable (MMModem *modem,
return;
}
if (mm_serial_port_open (priv->primary, &info->error))
mm_serial_port_flash (priv->primary, 100, flash_done, info);
if (info->error)
if (!mm_serial_port_open (priv->primary, &info->error)) {
g_assert (info->error);
mm_callback_info_schedule (info);
return;
}
mm_serial_port_flash (priv->primary, 100, flash_done, info);
}
static void
@@ -283,12 +294,19 @@ connect (MMModem *modem,
}
static void
disconnect_flash_done (MMSerialPort *port, gpointer user_data)
disconnect_flash_done (MMSerialPort *port,
GError *error,
gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
MMGenericCdmaPrivate *priv;
MMGenericCdmaPrivate *priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem);
if (error) {
info->error = g_error_copy (error);
mm_callback_info_schedule (info);
return;
}
priv = MM_GENERIC_CDMA_GET_PRIVATE (info->modem);
mm_port_set_connected (priv->data, FALSE);
mm_callback_info_schedule (info);
}

View File

@@ -369,11 +369,17 @@ init_done (MMSerialPort *port,
}
static void
enable_flash_done (MMSerialPort *port, gpointer user_data)
enable_flash_done (MMSerialPort *port, GError *error, gpointer user_data)
{
MMCallbackInfo *info = user_data;
char *cmd = NULL;
if (error) {
info->error = g_error_copy (error);
mm_callback_info_schedule (info);
return;
}
g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_INIT_CMD, &cmd, NULL);
mm_serial_port_queue_command (port, cmd, 3, init_done, user_data);
g_free (cmd);
@@ -390,12 +396,20 @@ disable_done (MMSerialPort *port,
}
static void
disable_flash_done (MMSerialPort *port, gpointer user_data)
disable_flash_done (MMSerialPort *port,
GError *error,
gpointer user_data)
{
MMCallbackInfo *info = user_data;
char *cmd = NULL;
g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_POWER_UP_CMD, &cmd, NULL);
if (error) {
info->error = g_error_copy (error);
mm_callback_info_schedule (info);
return;
}
g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_POWER_DOWN_CMD, &cmd, NULL);
if (cmd && strlen (cmd))
mm_serial_port_queue_command (port, cmd, 5, disable_done, user_data);
else
@@ -423,13 +437,15 @@ enable (MMModem *modem,
if (mm_port_get_connected (MM_PORT (priv->primary)))
mm_serial_port_flash (priv->primary, 1000, disable_flash_done, info);
else
disable_flash_done (priv->primary, info);
disable_flash_done (priv->primary, NULL, info);
} else {
if (mm_serial_port_open (priv->primary, &info->error))
mm_serial_port_flash (priv->primary, 100, enable_flash_done, info);
if (info->error)
if (!mm_serial_port_open (priv->primary, &info->error)) {
g_assert (info->error);
mm_callback_info_schedule (info);
return;
}
mm_serial_port_flash (priv->primary, 100, enable_flash_done, info);
}
}
@@ -1037,11 +1053,19 @@ connect (MMModem *modem,
}
static void
disconnect_flash_done (MMSerialPort *port, gpointer user_data)
disconnect_flash_done (MMSerialPort *port,
GError *error,
gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
if (error) {
info->error = g_error_copy (error);
mm_callback_info_schedule (info);
return;
}
mm_port_set_connected (priv->data, FALSE);
mm_callback_info_schedule (info);
}

View File

@@ -205,6 +205,9 @@ dispose (GObject *object)
{
MMPluginBaseSupportsTaskPrivate *priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (object);
if (MM_IS_SERIAL_PORT (priv->port))
mm_serial_port_flash_cancel (MM_SERIAL_PORT (priv->port));
g_object_unref (priv->port);
g_object_unref (priv->physdev);
g_free (priv->driver);
@@ -459,12 +462,8 @@ parse_response (MMSerialPort *port,
}
static void
flash_done (MMSerialPort *port, gpointer user_data)
flash_done (MMSerialPort *port, GError *error, gpointer user_data)
{
MMPluginBaseSupportsTask *task = MM_PLUGIN_BASE_SUPPORTS_TASK (user_data);
MMPluginBaseSupportsTaskPrivate *task_priv = MM_PLUGIN_BASE_SUPPORTS_TASK_GET_PRIVATE (task);
task_priv->probe_id = 0;
mm_serial_port_queue_command (port, "+GCAP", 3, parse_response, user_data);
}
@@ -506,7 +505,7 @@ mm_plugin_base_probe_port (MMPluginBase *self,
g_debug ("(%s): probe requested by plugin '%s'", name, priv->name);
task_priv->probe_port = serial;
task_priv->probe_id = mm_serial_port_flash (serial, 100, flash_done, task);
mm_serial_port_flash (serial, 100, flash_done, task);
return TRUE;
}

View File

@@ -77,8 +77,93 @@ typedef struct {
guint queue_schedule;
guint watch_id;
guint timeout_id;
guint flash_id;
} MMSerialPortPrivate;
#if 0
static const char *
baud_to_string (int baud)
{
const char *speed = NULL;
switch (baud) {
case B0:
speed = "0";
break;
case B50:
speed = "50";
break;
case B75:
speed = "75";
break;
case B110:
speed = "110";
break;
case B150:
speed = "150";
break;
case B300:
speed = "300";
break;
case B600:
speed = "600";
break;
case B1200:
speed = "1200";
break;
case B2400:
speed = "2400";
break;
case B4800:
speed = "4800";
break;
case B9600:
speed = "9600";
break;
case B19200:
speed = "19200";
break;
case B38400:
speed = "38400";
break;
case B57600:
speed = "57600";
break;
case B115200:
speed = "115200";
break;
case B460800:
speed = "460800";
break;
default:
break;
}
return speed;
}
void
mm_serial_port_print_config (MMSerialPort *port, const char *detail)
{
MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (port);
struct termio stbuf;
int err;
err = ioctl (priv->fd, TCGETA, &stbuf);
if (err) {
g_warning ("*** %s (%s): (%s) TCGETA error %d",
__func__, detail, mm_port_get_device (MM_PORT (port)), errno);
return;
}
g_message ("*** %s (%s): (%s) baud rate: %d (%s)",
__func__, detail, mm_port_get_device (MM_PORT (port)),
stbuf.c_cflag & CBAUD,
baud_to_string (stbuf.c_cflag & CBAUD));
}
#endif
typedef struct {
GRegex *regex;
MMSerialUnsolicitedMsgFn callback;
@@ -241,7 +326,7 @@ parse_stopbits (guint i)
}
static gboolean
config_fd (MMSerialPort *self)
config_fd (MMSerialPort *self, GError **error)
{
MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
struct termio stbuf;
@@ -255,7 +340,13 @@ config_fd (MMSerialPort *self)
parity = parse_parity (priv->parity);
stopbits = parse_stopbits (priv->stopbits);
ioctl (priv->fd, TCGETA, &stbuf);
memset (&stbuf, 0, sizeof (struct termio));
if (ioctl (priv->fd, TCGETA, &stbuf) != 0) {
g_warning ("%s (%s): TCGETA error: %d",
__func__,
mm_port_get_device (MM_PORT (self)),
errno);
}
stbuf.c_iflag &= ~(IGNCR | ICRNL | IUCLC | INPCK | IXON | IXANY | IGNPAR );
stbuf.c_oflag &= ~(OPOST | OLCUC | OCRNL | ONLCR | ONLRET);
@@ -269,8 +360,11 @@ config_fd (MMSerialPort *self)
stbuf.c_cflag |= (speed | bits | CREAD | 0 | parity | stopbits);
if (ioctl (priv->fd, TCSETA, &stbuf) < 0) {
g_warning ("(%s) cannot control device (errno %d)",
mm_port_get_device (MM_PORT (self)), errno);
g_set_error (error,
MM_MODEM_ERROR,
MM_MODEM_ERROR_GENERAL,
"%s: failed to set serial port attributes; errno %d",
__func__, errno);
return FALSE;
}
@@ -401,8 +495,10 @@ mm_serial_port_got_response (MMSerialPort *self, GError *error)
MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (self);
MMQueueData *info;
if (priv->timeout_id)
if (priv->timeout_id) {
g_source_remove (priv->timeout_id);
priv->timeout_id = 0;
}
info = (MMQueueData *) g_queue_pop_head (priv->queue);
if (info) {
@@ -689,9 +785,7 @@ mm_serial_port_open (MMSerialPort *self, GError **error)
return FALSE;
}
if (!config_fd (self)) {
g_set_error (error, MM_SERIAL_ERROR, MM_SERIAL_OPEN_FAILED,
"Could not open serial device %s: %s", device, strerror (errno));
if (!config_fd (self, error)) {
close (priv->fd);
priv->fd = -1;
return FALSE;
@@ -725,6 +819,11 @@ mm_serial_port_close (MMSerialPort *self)
priv->channel = NULL;
}
if (priv->flash_id > 0) {
g_source_remove (priv->flash_id);
priv->flash_id = 0;
}
ioctl (priv->fd, TCSETA, &priv->old_t);
close (priv->fd);
priv->fd = -1;
@@ -789,81 +888,158 @@ typedef struct {
gpointer user_data;
} FlashInfo;
static speed_t
get_speed (MMSerialPort *self)
static gboolean
get_speed (MMSerialPort *self, speed_t *speed, GError **error)
{
struct termios options;
memset (&options, 0, sizeof (struct termios));
tcgetattr (MM_SERIAL_PORT_GET_PRIVATE (self)->fd, &options);
return cfgetospeed (&options);
if (tcgetattr (MM_SERIAL_PORT_GET_PRIVATE (self)->fd, &options) != 0) {
g_set_error (error,
MM_MODEM_ERROR,
MM_MODEM_ERROR_GENERAL,
"%s: tcgetattr() error %d",
__func__, errno);
return FALSE;
}
static void
set_speed (MMSerialPort *self, speed_t speed)
*speed = cfgetospeed (&options);
return TRUE;
}
static gboolean
set_speed (MMSerialPort *self, speed_t speed, GError **error)
{
struct termios options;
int fd;
int fd, count = 4;
gboolean success = FALSE;
fd = MM_SERIAL_PORT_GET_PRIVATE (self)->fd;
memset (&options, 0, sizeof (struct termios));
tcgetattr (fd, &options);
if (tcgetattr (fd, &options) != 0) {
g_set_error (error,
MM_MODEM_ERROR,
MM_MODEM_ERROR_GENERAL,
"%s: tcgetattr() error %d",
__func__, errno);
return FALSE;
}
cfsetispeed (&options, speed);
cfsetospeed (&options, speed);
options.c_cflag |= (CLOCAL | CREAD);
tcsetattr (fd, TCSANOW, &options);
while (count-- > 0) {
if (tcsetattr (fd, TCSANOW, &options) == 0) {
success = TRUE;
break; /* Operation successful */
}
static void
flash_done (gpointer data)
{
FlashInfo *info = (FlashInfo *) data;
/* Try a few times if EAGAIN */
if (errno == EAGAIN)
g_usleep (100000);
else {
/* If not EAGAIN, hard error */
g_set_error (error,
MM_MODEM_ERROR,
MM_MODEM_ERROR_GENERAL,
"%s: tcsetattr() error %d",
__func__, errno);
return FALSE;
}
}
info->callback (info->port, info->user_data);
if (!success) {
g_set_error (error,
MM_MODEM_ERROR,
MM_MODEM_ERROR_GENERAL,
"%s: tcsetattr() retry timeout",
__func__);
return FALSE;
}
g_slice_free (FlashInfo, info);
return TRUE;
}
static gboolean
flash_do (gpointer data)
{
FlashInfo *info = (FlashInfo *) data;
MMSerialPortPrivate *priv = MM_SERIAL_PORT_GET_PRIVATE (info->port);
GError *error = NULL;
set_speed (info->port, info->current_speed);
priv->flash_id = 0;
if (!set_speed (info->port, info->current_speed, &error))
g_assert (error);
info->callback (info->port, error, info->user_data);
g_clear_error (&error);
g_slice_free (FlashInfo, info);
return FALSE;
}
guint
gboolean
mm_serial_port_flash (MMSerialPort *self,
guint32 flash_time,
MMSerialFlashFn callback,
gpointer user_data)
{
FlashInfo *info;
guint id;
MMSerialPortPrivate *priv;
speed_t cur_speed = 0;
GError *error = NULL;
g_return_val_if_fail (MM_IS_SERIAL_PORT (self), 0);
g_return_val_if_fail (callback != NULL, 0);
g_return_val_if_fail (MM_IS_SERIAL_PORT (self), FALSE);
g_return_val_if_fail (callback != NULL, FALSE);
priv = MM_SERIAL_PORT_GET_PRIVATE (self);
if (priv->flash_id > 0) {
error = g_error_new_literal (MM_MODEM_ERROR,
MM_MODEM_ERROR_OPERATION_IN_PROGRESS,
"Modem is already being flashed.");
callback (self, error, user_data);
g_error_free (error);
return FALSE;
}
if (!get_speed (self, &cur_speed, &error)) {
callback (self, error, user_data);
g_error_free (error);
return FALSE;
}
info = g_slice_new0 (FlashInfo);
info->port = self;
info->current_speed = get_speed (self);
info->current_speed = cur_speed;
info->callback = callback;
info->user_data = user_data;
set_speed (self, B0);
if (!set_speed (self, B0, &error)) {
callback (self, error, user_data);
g_error_free (error);
return FALSE;
}
id = g_timeout_add_full (G_PRIORITY_DEFAULT,
flash_time,
flash_do,
info,
flash_done);
priv->flash_id = g_timeout_add (flash_time, flash_do, info);
return TRUE;
}
return id;
void
mm_serial_port_flash_cancel (MMSerialPort *self)
{
MMSerialPortPrivate *priv;
g_return_if_fail (MM_IS_SERIAL_PORT (self));
priv = MM_SERIAL_PORT_GET_PRIVATE (self);
if (priv->flash_id > 0) {
g_source_remove (priv->flash_id);
priv->flash_id = 0;
}
}
/*****************************************************************************/

View File

@@ -53,6 +53,7 @@ typedef void (*MMSerialResponseFn) (MMSerialPort *port,
gpointer user_data);
typedef void (*MMSerialFlashFn) (MMSerialPort *port,
GError *error,
gpointer user_data);
struct _MMSerialPort {
@@ -94,10 +95,11 @@ void mm_serial_port_queue_command_cached (MMSerialPort *self,
MMSerialResponseFn callback,
gpointer user_data);
guint mm_serial_port_flash (MMSerialPort *self,
gboolean mm_serial_port_flash (MMSerialPort *self,
guint32 flash_time,
MMSerialFlashFn callback,
gpointer user_data);
void mm_serial_port_flash_cancel (MMSerialPort *self);
#endif /* MM_SERIAL_PORT_H */