blob: b5a5bd85c9a8d35cfbb3f9d99209f06c2e516e2d [file] [log] [blame]
// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gobi_modem_handler.h"
#include <glib.h>
extern "C" {
#include <libudev.h>
#include <stdio.h>
#include <syslog.h>
// Defines from syslog.h that conflict with base/logging.h. Ugh.
#undef LOG_INFO
#undef LOG_WARNING
}
#include <base/logging.h>
#include <cromo/cromo_server.h>
#include <cromo/plugin.h>
#include "device_watcher.h"
#include "gobi_modem.h"
#include "gobi_cdma_modem.h"
#include "gobi_modem_factory.h"
#include "gobi_sdk_wrapper.h"
#ifndef VCSID
#define VCSID "<not set>"
#endif
using std::vector;
static ModemHandler* mm;
static const int kDevicePollIntervalSecs = 1;
static const char* kQCDeviceName = "QCQMI";
static const char* kUSBDeviceListFile = "/var/run/cromo/usb-devices";
static void udev_callback(void* data, const char *action, const char *device) {
GobiModemHandler* handler = static_cast<GobiModemHandler*>(data);
handler->HandleUdevMessage(action, device);
}
static void timeout_callback(void* data) {
GobiModemHandler* handler = static_cast<GobiModemHandler*>(data);
handler->HandlePollEvent();
}
GobiModemHandler::GobiModemHandler(CromoServer& server)
: ModemHandler(server, "Gobi"),
clear_device_list_on_destroy_(true),
device_watcher_(NULL),
scan_generation_(0) {
}
GobiModemHandler::~GobiModemHandler() {
if (clear_device_list_on_destroy_) {
ClearDeviceListFile();
}
delete device_watcher_;
}
bool GobiModemHandler::Initialize() {
// Can't use LOG here: we want this to be always logged, but we don't want it
// to be an error. Fortunately syslog declares both openlog() and closelog()
// 'optional', so...
syslog(LOG_NOTICE, "gobi-cromo-plugin vcsid %s", VCSID);
sdk_.reset(new gobi::Sdk(GobiModem::SinkSdkError));
sdk_->Init();
GobiModem::set_handler(this);
MonitorDevices();
RegisterSelf();
return true;
}
// Watch for addition and removal of Gobi devices.
// When a udev event arrives, we begin polling
// the SDK until the change reported by the
// event is visible via EnumerateDevices. At that
// point, we stop polling.
void GobiModemHandler::MonitorDevices() {
device_watcher_ = new DeviceWatcher(kQCDeviceName);
device_watcher_->set_callback(udev_callback, this);
device_watcher_->StartMonitoring();
GetDeviceList();
}
void GobiModemHandler::HandleUdevMessage(const char *action,
const char *device) {
// udev deals in long device names (like "/dev/qcqmi0") but the qualcomm sdk
// deals in just basenames (like "qcqmi0"). The control paths we store in the
// control-path-to-device map are basenames, so only use the device's basename
// here.
if (strstr(device, "/dev/") == device)
device += strlen("/dev/");
// If this method is called due to an udev event after the poller is started
// but before the polling callback is invoked, the GetDeviceList() call below
// may potentially "absorb" any changes in the device list, which means the
// GetDeviceList() call in the polling callback will return false and keep the
// polling continuously running. Thus, we stop any scheduled polling here to
// prevent that from happening.
device_watcher_->StopPolling();
bool saw_changes = GetDeviceList();
if (0 == strcmp("change", action)) {
// No need to start poller as there is no device added or removed.
// Otherwise, if we start the poller, it won't be stopped as
// GetDeviceList() will return false.
return;
}
if (0 == strcmp("add" , action) && DevicePresentByControlPath(device)) {
LOG(INFO) << "Device already present";
return; // Do not start poller
}
if (0 == strcmp("remove", action)) {
// No need to start poller; we have acted on event
RemoveDeviceByControlPath(device);
return;
}
if (!saw_changes) {
// The udev change isn't yet visible to QCWWAN. Poll until it is.
device_watcher_->StartPolling(kDevicePollIntervalSecs,
timeout_callback,
this);
} else {
LOG(ERROR) << "Saw unexpected change " << action << " " << device;
}
}
void GobiModemHandler::RemoveDeviceByControlPath(const char *path) {
ControlPathToModem::iterator p = control_path_to_modem_.find(path);
if (p != control_path_to_modem_.end()) {
LOG(INFO) << "Removing device " << path;
RemoveDeviceByIterator(p);
} else {
LOG(INFO) << "Could not find " << path << " to remove";
}
}
static gboolean delete_modem(gpointer p) {
GobiModem *m = static_cast<GobiModem*>(p);
delete m;
return FALSE;
}
static gboolean clear_idle_callbacks_and_delete(gpointer p) {
GobiModem *m = static_cast<GobiModem*>(p);
m->ClearIdleCallbacks();
g_idle_add(delete_modem, m);
return FALSE;
}
GobiModemHandler::ControlPathToModem::iterator
GobiModemHandler::RemoveDeviceByIterator(ControlPathToModem::iterator p) {
if (p == control_path_to_modem_.end()) {
LOG(ERROR) << "Bad iterator";
return p;
}
ControlPathToModem::iterator next = p;
++next;
GobiModem *m = p->second;
if (m == NULL) {
LOG(ERROR) << "no modem";
return next;
}
server().DeviceRemoved(m->path());
control_path_to_modem_.erase(p);
g_idle_add(clear_idle_callbacks_and_delete, m);
return next;
}
void GobiModemHandler::Remove(GobiModem* modem) {
for (ControlPathToModem::iterator p = key_to_modem_.begin();
p != key_to_modem_.end(); ++p) {
GobiModem* m = p->second;
if (m == modem) {
RemoveDeviceByIterator(p);
}
}
}
bool GobiModemHandler::DevicePresentByControlPath(const char *path) {
return control_path_to_modem_.find(path) != control_path_to_modem_.end();
}
void GobiModemHandler::HandlePollEvent() {
if (GetDeviceList())
device_watcher_->StopPolling();
}
// Get the list of visible devices, keeping track of what devices have
// been added and removed since the last time we looked. Returns true
// if any devices have been added or removed, false otherwise.
bool GobiModemHandler::GetDeviceList() {
const int MAX_MODEMS = 16;
ULONG rc;
gobi::DeviceElement devices[MAX_MODEMS];
BYTE num_devices = MAX_MODEMS;
rc = sdk_->QCWWANEnumerateDevices(&num_devices,
reinterpret_cast<BYTE*>(devices));
if (rc != 0) {
LOG(ERROR) << "QCWWANEnumerateDevices returned " << rc;
return false;
}
++scan_generation_;
bool something_changed = false;
LOG(INFO) << "QCWWANEnumerateDevices found " << static_cast<int>(num_devices)
<< " device(s)";
for (size_t i = 0; i < num_devices; ++i) {
ControlPathToModem::iterator p =
control_path_to_modem_.find(devices[i].deviceNode);
if (p != control_path_to_modem_.end()) {
CHECK((*p).second);
(*p).second->set_last_seen(scan_generation_);
} else {
something_changed = true;
GobiModem* m = GobiModemFactory::CreateModem(server().conn(),
MakePath(),
devices[i],
sdk_.get());
if (m == NULL) {
LOG(ERROR) << "Could not create modem object for "
<< devices[i].deviceNode;
continue;
}
m->Init();
m->set_last_seen(scan_generation_);
control_path_to_modem_[std::string(devices[i].deviceNode)] = m;
server().DeviceAdded(m->path());
LOG(INFO) << "Found new modem: " << m->path()
<< " (" << devices[i].deviceNode << ")";
}
}
for (ControlPathToModem::iterator p = control_path_to_modem_.begin();
p != control_path_to_modem_.end(); ) {
GobiModem* m = p->second;
if (m->last_seen() != scan_generation_) {
something_changed = true;
LOG(INFO) << "Device " << m->path() << " has disappeared";
p = RemoveDeviceByIterator(p);
} else {
++p;
}
}
WriteDeviceListFile(control_path_to_modem_);
return something_changed;
}
void GobiModemHandler::ClearDeviceListFile() {
ControlPathToModem no_modems;
WriteDeviceListFile(no_modems);
}
void GobiModemHandler::WriteDeviceListFile(const ControlPathToModem &modems) {
FILE *dev_list_file = fopen(kUSBDeviceListFile, "w");
if (dev_list_file) {
for (ControlPathToModem::const_iterator p = modems.begin();
p != modems.end(); p++) {
GobiModem *m = p->second;
fprintf(dev_list_file, "%s\n", m->GetUSBAddress().c_str());
}
fclose(dev_list_file);
}
}
// Enumerate the existing devices, and add them to the list of devices
// that are managed by the ChromeOS modem manager
vector<DBus::Path> GobiModemHandler::EnumerateDevices(DBus::Error& error) {
vector <DBus::Path> to_return;
for (ControlPathToModem::iterator p = control_path_to_modem_.begin();
p != control_path_to_modem_.end(); ++p) {
to_return.push_back(p->second->path());
}
return to_return;
}
GobiModem* GobiModemHandler::LookupByDbusPath(const std::string& dbuspath)
{
for (ControlPathToModem::iterator p = control_path_to_modem_.begin();
p != control_path_to_modem_.end(); ++p) {
GobiModem* m = p->second;
if (m->path() == dbuspath)
return m;
}
return NULL;
}
void GobiModemHandler::ExitLeavingModemsForCleanup() {
clear_device_list_on_destroy_ = false;
LOG(ERROR) << "Exiting without clearing device list.";
// Send a SIGTERM so cromo gets a chance to clean up before exiting.
kill(getpid(), SIGTERM);
}
static void onload(CromoServer* server) {
mm = new GobiModemHandler(*server);
if (!mm->Initialize())
LOG(ERROR) << "Failed to initialize GobiModemHandler";
}
static void onunload() {
delete mm;
}
CROMO_DEFINE_PLUGIN(gobi, onload, onunload)