/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* qmi-firmware-update -- Command line tool to update firmware in QMI devices
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Copyright (C) 2017 Zodiac Inflight Innovations
* Copyright (C) 2017 Aleksander Morgado
*/
#include
#include
#include
#include
#include
#include "qfu-device-selection.h"
#include "qfu-udev-helpers.h"
G_DEFINE_TYPE (QfuDeviceSelection, qfu_device_selection, G_TYPE_OBJECT)
struct _QfuDeviceSelectionPrivate {
/* inputs */
gchar *preferred_devices[QFU_UDEV_HELPER_DEVICE_TYPE_LAST];
guint16 preferred_vid;
guint16 preferred_pid;
guint preferred_busnum;
guint preferred_devnum;
/* sysfs path */
gchar *sysfs_path;
/* generic udev monitor */
QfuUdevHelperGenericMonitor *monitor;
};
/******************************************************************************/
static GFile *
device_selection_get_single (QfuDeviceSelection *self,
QfuUdevHelperDeviceType device_type)
{
GFile *first_selection = NULL;
GFile *preferred_selection = NULL;
GList *list, *l;
gchar *path;
g_debug ("[qfu,device-selection] single %s device requested in sysfs path '%s'",
qfu_udev_helper_device_type_to_string (device_type),
self->priv->sysfs_path);
list = qfu_udev_helper_list_devices (device_type, self->priv->sysfs_path);
for (l = list; l; l = g_list_next (l)) {
path = g_file_get_path (G_FILE (l->data));
g_debug ("[qfu,device-selection] device found: %s", path);
if (!first_selection)
first_selection = g_object_ref (l->data);
if (!preferred_selection && !g_strcmp0 (path, self->priv->preferred_devices[device_type]))
preferred_selection = g_object_ref (l->data);
g_free (path);
}
g_list_free_full (list, (GDestroyNotify) g_object_unref);
if (preferred_selection) {
path = g_file_get_path (preferred_selection);
g_debug ("[qfu,device-selection] using preferred device: %s", path);
g_free (path);
if (first_selection)
g_object_unref (first_selection);
return preferred_selection;
}
if (first_selection) {
path = g_file_get_path (first_selection);
g_debug ("[qfu,device-selection] using automatically selected device: %s", path);
g_free (path);
return first_selection;
}
g_debug ("[qfu,device-selection] couldn't find any device to use");
return NULL;
}
GFile *
qfu_device_selection_get_single_cdc_wdm (QfuDeviceSelection *self)
{
return device_selection_get_single (self, QFU_UDEV_HELPER_DEVICE_TYPE_CDC_WDM);
}
GFile *
qfu_device_selection_get_single_tty (QfuDeviceSelection *self)
{
return device_selection_get_single (self, QFU_UDEV_HELPER_DEVICE_TYPE_TTY);
}
/******************************************************************************/
static GList *
device_selection_get_multiple (QfuDeviceSelection *self,
QfuUdevHelperDeviceType device_type)
{
GFile *preferred_selection = NULL;
GList *list, *l;
gchar *path;
g_debug ("[qfu,device-selection] multiple %s devices requested in sysfs path '%s'",
qfu_udev_helper_device_type_to_string (device_type),
self->priv->sysfs_path);
list = qfu_udev_helper_list_devices (device_type, self->priv->sysfs_path);
for (l = list; l; l = g_list_next (l)) {
path = g_file_get_path (G_FILE (l->data));
g_debug ("[qfu,device-selection] device found: %s", path);
if (!preferred_selection && !g_strcmp0 (path, self->priv->preferred_devices[device_type]))
preferred_selection = g_object_ref (l->data);
g_free (path);
}
/* If we have a preferred device selected, we will only include that one in the output list */
if (preferred_selection) {
path = g_file_get_path (preferred_selection);
g_debug ("[qfu,device-selection] using only preferred device: %s", path);
g_free (path);
g_list_free_full (list, (GDestroyNotify) g_object_unref);
return g_list_append (NULL, preferred_selection);
}
if (list)
return list;
g_debug ("[qfu,device-selection] couldn't find any device to use");
return NULL;
}
GList *
qfu_device_selection_get_multiple_ttys (QfuDeviceSelection *self)
{
return device_selection_get_multiple (self, QFU_UDEV_HELPER_DEVICE_TYPE_TTY);
}
/******************************************************************************/
GFile *
qfu_device_selection_wait_for_cdc_wdm_finish (QfuDeviceSelection *self,
GAsyncResult *res,
GError **error)
{
return G_FILE (g_task_propagate_pointer (G_TASK (res), error));
}
GFile *
qfu_device_selection_wait_for_tty_finish (QfuDeviceSelection *self,
GAsyncResult *res,
GError **error)
{
return G_FILE (g_task_propagate_pointer (G_TASK (res), error));
}
static void
wait_for_device_ready (gpointer unused,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
GFile *file;
file = qfu_udev_helper_wait_for_device_finish (res, &error);
if (!file)
g_task_return_error (task, error);
else
g_task_return_pointer (task, file, (GDestroyNotify) g_object_unref);
g_object_unref (task);
}
void
qfu_device_selection_wait_for_cdc_wdm (QfuDeviceSelection *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, cancellable, callback, user_data);
qfu_udev_helper_wait_for_device (QFU_UDEV_HELPER_DEVICE_TYPE_CDC_WDM,
self->priv->sysfs_path,
cancellable,
(GAsyncReadyCallback) wait_for_device_ready,
task);
}
void
qfu_device_selection_wait_for_tty (QfuDeviceSelection *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, cancellable, callback, user_data);
qfu_udev_helper_wait_for_device (QFU_UDEV_HELPER_DEVICE_TYPE_TTY,
self->priv->sysfs_path,
cancellable,
(GAsyncReadyCallback) wait_for_device_ready,
task);
}
/******************************************************************************/
QfuDeviceSelection *
qfu_device_selection_new (const gchar *preferred_cdc_wdm,
const gchar *preferred_tty,
guint16 preferred_vid,
guint16 preferred_pid,
guint preferred_busnum,
guint preferred_devnum,
GError **error)
{
QfuDeviceSelection *self;
guint n_selections;
/* Note: pid and busnum may be zero */
n_selections = (!!preferred_cdc_wdm +
!!preferred_tty +
!!preferred_vid +
!!preferred_devnum);
if (!n_selections) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"No device selected");
return NULL;
}
if (n_selections > 1) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Only one device selection option may be provided");
return NULL;
}
/* Selection valid, create object */
self = g_object_new (QFU_TYPE_DEVICE_SELECTION, NULL);
/* Store inputs */
self->priv->preferred_devices[QFU_UDEV_HELPER_DEVICE_TYPE_CDC_WDM] = g_strdup (preferred_cdc_wdm);
self->priv->preferred_devices[QFU_UDEV_HELPER_DEVICE_TYPE_TTY] = g_strdup (preferred_tty);
self->priv->preferred_vid = preferred_vid;
self->priv->preferred_pid = preferred_pid;
self->priv->preferred_busnum = preferred_busnum;
self->priv->preferred_devnum = preferred_devnum;
/* Initialize sysfs path from inputs */
if (preferred_vid || preferred_devnum)
self->priv->sysfs_path = qfu_udev_helper_find_by_device_info (preferred_vid, preferred_pid, preferred_busnum, preferred_devnum, error);
else if (preferred_cdc_wdm || preferred_tty)
self->priv->sysfs_path = qfu_udev_helper_find_by_file_path (preferred_cdc_wdm ? preferred_cdc_wdm : preferred_tty, error);
else
g_assert_not_reached ();
if (!self->priv->sysfs_path) {
g_object_unref (self);
return NULL;
}
/* Initialize right away the generic udev monitor for this sysfs path */
self->priv->monitor = qfu_udev_helper_generic_monitor_new (self->priv->sysfs_path);
return self;
}
static void
qfu_device_selection_init (QfuDeviceSelection *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, QFU_TYPE_DEVICE_SELECTION, QfuDeviceSelectionPrivate);
}
static void
finalize (GObject *object)
{
QfuDeviceSelection *self = QFU_DEVICE_SELECTION (object);
guint i;
if (self->priv->monitor)
qfu_udev_helper_generic_monitor_free (self->priv->monitor);
for (i = 0; i < QFU_UDEV_HELPER_DEVICE_TYPE_LAST; i++)
g_free (self->priv->preferred_devices[i]);
g_free (self->priv->sysfs_path);
G_OBJECT_CLASS (qfu_device_selection_parent_class)->finalize (object);
}
static void
qfu_device_selection_class_init (QfuDeviceSelectionClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (object_class, sizeof (QfuDeviceSelectionPrivate));
object_class->finalize = finalize;
}