| // 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-cromo-plugin/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 "gobi-cromo-plugin/device_watcher.h" |
| #include "gobi-cromo-plugin/gobi_cdma_modem.h" |
| #include "gobi-cromo-plugin/gobi_modem.h" |
| #include "gobi-cromo-plugin/gobi_modem_factory.h" |
| #include "gobi-cromo-plugin/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_(nullptr), |
| 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) { |
| 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) { |
| 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 nullptr; |
| } |
| |
| 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) |