service experiments

Change-Id: I4affed8a4b6e2bc61c67f1b6cd408e6c662fa7e3
diff --git a/bin/sysmgr/config/services.config b/bin/sysmgr/config/services.config
index 423f8a0..eb55ae3 100644
--- a/bin/sysmgr/config/services.config
+++ b/bin/sysmgr/config/services.config
@@ -32,6 +32,7 @@
     "fuchsia.scpi.SystemController": "scpi",
     "fuchsia.simplecamera.SimpleCamera" : "simple_camera_server_cpp",
     "fuchsia.stash.Store": "stash",
+    "fuchsia.telephony.ModemManagement": "modem-mgr",
     "fuchsia.timezone.Timezone": "timezone",
     "fuchsia.timezone.TimeService": "network_time_service",
     "fuchsia.tracelink.Registry": "trace_manager",
diff --git a/bin/telephony/modem-mgr/BUILD.gn b/bin/telephony/modem-mgr/BUILD.gn
index ddecff0..4425390 100644
--- a/bin/telephony/modem-mgr/BUILD.gn
+++ b/bin/telephony/modem-mgr/BUILD.gn
@@ -11,7 +11,7 @@
 
   deps = [
 #    "//garnet/bin/telephony/qmi-protocol",
-#    "//garnet/public/fidl/fuchsia.telephony.qmi:fuchsia.telephony.qmi-rustc",
+    "//garnet/public/fidl/fuchsia.telephony.qmi:fuchsia.telephony.qmi-rustc",
     "//garnet/public/lib/fidl/rust/fidl",
     "//garnet/public/rust/crates/fdio",
     "//garnet/public/rust/crates/fuchsia-app",
diff --git a/bin/telephony/modem-mgr/src/main.rs b/bin/telephony/modem-mgr/src/main.rs
index c213471..21b8b6b 100644
--- a/bin/telephony/modem-mgr/src/main.rs
+++ b/bin/telephony/modem-mgr/src/main.rs
@@ -10,6 +10,8 @@
 //
 //
 
+use fuchsia_app::{server::ServicesServer, client::Launcher};
+use fidl_fuchsia_telephony_qmi::{QmiModemMarker, QmiModemProxy};
 use fdio::{fdio_sys, ioctl_raw, make_ioctl};
 use std::os::unix::io::AsRawFd;
 use std::os::raw;
@@ -59,14 +61,25 @@
     Ok(fasync::Channel::from_channel(chan)?)
 }
 
+pub async fn start_qmi_modem(chan: fasync::Channel) -> Result<QmiModemProxy, Error> {
+    let launcher = Launcher::new()
+        .context("Failed to open launcher service")?;
+    let qmi = launcher
+        .launch(String::from("qmi-modem"), None)
+        .context("Failed to launch qmi-modem service")?;
+    let app = qmi.connect_to_service(QmiModemMarker)?;
+    let success = await!(app.connect_transport(chan.into()))?;
+    Ok(app)
+}
+
 pub struct Manager {
-    channel: Option<fasync::Channel>,
+    proxies: Vec<QmiModemProxy>
 }
 
 impl Manager {
     pub fn new() -> Self {
         Manager {
-            channel: None
+            proxies: vec![],
         }
     }
 
@@ -75,32 +88,34 @@
         let path: &Path = Path::new(QMI_TRANSPORT);
         let dir = File::open(QMI_TRANSPORT).unwrap();
         let mut watcher = Watcher::new(&dir).unwrap();
-//        let mut channel = None;
         while let Some(msg) = await!(watcher.try_next())? {
             match msg.event {
                 WatchEvent::EXISTING | WatchEvent::ADD_FILE => {
                     let qmi_path = path.join(msg.filename);
                     fx_log_info!("Found QMI device at {:?}", qmi_path);
                     let channel = connect_qmi_transport(qmi_path)?;
-                    fx_log_info!("Connected a channel to the device");
-                    channel.write(&[0x01,
-                                  0x0F, 0x00,  // length
-                                  0x00,         // control flag
-                                  0x00,         // service type
-                                  0x00,         // client id
-                                // SDU below
-                                  0x00, // control flag
-                                  0x00, // tx id
-                                  0x20, 0x00, // message id
-                                  0x04, 0x00, // Length
-                                  0x01, // type
-                                  0x01, 0x00, // length
-                                  0x48, // value
-                    ], &mut Vec::new()); // TODO Remove
-                    let mut buffer = zx::MessageBuf::new();
-                    await!(channel.repeat_server(|_chan, buf| {
-                        println!("{:X?}", buf.bytes());
-                    }));
+                    let svc = await!(start_qmi_modem(channel))?;
+                    self.proxies.push(svc);
+
+                    //fx_log_info!("Connected a channel to the device");
+                    //channel.write(&[0x01,
+                    //              0x0F, 0x00,  // length
+                    //              0x00,         // control flag
+                    //              0x00,         // service type
+                    //              0x00,         // client id
+                    //            // SDU below
+                    //              0x00, // control flag
+                    //              0x00, // tx id
+                    //              0x20, 0x00, // message id
+                    //              0x04, 0x00, // Length
+                    //              0x01, // type
+                    //              0x01, 0x00, // length
+                    //              0x48, // value
+                    //], &mut Vec::new()); // TODO Remove
+                    //let mut buffer = zx::MessageBuf::new();
+                    //await!(channel.repeat_server(|_chan, buf| {
+                    //    println!("{:X?}", buf.bytes());
+                    //}));
                     //}
                     //self.channel = Some(channel);
                 }
diff --git a/bin/telephony/qmi-modem/src/main.rs b/bin/telephony/qmi-modem/src/main.rs
index b49e842..94fe7e5 100644
--- a/bin/telephony/qmi-modem/src/main.rs
+++ b/bin/telephony/qmi-modem/src/main.rs
@@ -3,73 +3,183 @@
 // found in the LICENSE file.
 
 //#![deny(warnings)]
-#![feature(async_await, await_macro, futures_api, pin, arbitrary_self_types)]
+#![feature(
+    async_await,
+    await_macro,
+    futures_api,
+    pin,
+    arbitrary_self_types
+)]
 #![feature(try_from)]
 
-use fuchsia_syslog::{self as syslog, macros::*};
-use failure::{ResultExt, Error};
-use fuchsia_app::server::ServicesServer;
-use fidl_fuchsia_telephony_qmi::{QmiModemMarker, QmiModemRequest, QmiModemRequestStream};
+use failure::{Error, ResultExt};
 use fidl::endpoints2::RequestStream;
-use futures::{future, TryStreamExt, TryFutureExt};
-use fuchsia_async as fasync;
+use fidl::endpoints2::ServerEnd;
 use fidl::endpoints2::ServiceMarker;
-use std::pin::PinMut;
+use fidl_fuchsia_telephony_qmi::{DeviceManagementRequest, DeviceManagementRequestStream};
+use fidl_fuchsia_telephony_qmi::{QmiClientMarker, QmiClientRequest, QmiClientRequestStream};
+use fidl_fuchsia_telephony_qmi::{QmiModemMarker, QmiModemRequest, QmiModemRequestStream};
+use fuchsia_app::server::ServicesServer;
+use fuchsia_async as fasync;
+use fuchsia_syslog::{self as syslog, macros::*};
+use fuchsia_zircon as zx;
+use futures::{future, TryFutureExt, TryStreamExt};
 use std::convert::TryInto;
+use std::pin::PinMut;
 
-// mod service;
+use parking_lot::{Mutex, RwLock};
+use std::sync::Arc;
 
-//#[derive(QmiMsg)]
-//SetInstanceId {
-//    msg_id: 0x0020,
-//    req: tlv!(0x01, u8, instance),
-//    resp: tlv!(0x01, u16, id),
-//}
-//
-//let msg = SetInstanceId::msg(3u8);
-//let id = SetInstanceId::parse([0x01, 9u16])
-//
-//use qmi_protocol::QmiClient;
-use qmi_protocol::DeviceManagement::*;
-use qmi_protocol::{ToQmiMsg, QmiMsg};
+type QmiModemPtr = Arc<Mutex<QmiModem>>;
+type QmiClientPtr = Arc<RwLock<QmiClient>>;
 
-pub struct QmiClient {
+// Many Qualcomm modems have an limit of 5 outstanding requests by default
+pub const MAX_CONCURRENT: usize = 4;
 
-}
-
+pub struct QmiClient;
 impl QmiClient {
     pub fn new() -> Self {
-        QmiClient { }
-    }
-
-    pub async fn send_msg<T: ToQmiMsg>(&mut self, event: T) -> Result<QmiMsg, Error> {
-        //let res = DeviceManagement::EventReportResponse::new();
-        Ok(QmiMsg::new())
+        QmiClient {
+        }
     }
 }
 
-
-struct QmiModem;
+pub struct QmiModem {
+    transport_channel: Option<fasync::Channel>,
+}
 
 impl QmiModem {
-    fn spawn(chan: fasync::Channel) {
-        // allocate client ID here and whatnot
-        fasync::spawn(
-            QmiModemRequestStream::from_channel(chan)
-            .try_for_each(Self::handle_request)
-            .unwrap_or_else(|e| fx_log_err!("Error running {:?}", e)))
+    pub fn new() -> Self {
+        QmiModem {
+            transport_channel: None,
+        }
     }
 
-    async fn handle_request(request: QmiModemRequest) -> Result<(), fidl::Error> {
+    pub fn connected(&self) -> bool {
+        self.transport_channel.is_some()
+    }
+
+    pub fn set_transport(&mut self, chan: zx::Channel) -> bool {
+        if self.transport_channel.is_none() {
+            return false;
+        }
+        match fasync::Channel::from_channel(chan) {
+            Ok(chan) => {
+                self.transport_channel = Some(chan);
+                true
+            }
+            Err(_) => {
+                fx_log_err!("Failed to convert a zircon channel to a fasync one");
+                false
+            }
+        }
+    }
+
+    pub async fn create_client(&self) -> Result<QmiClientPtr, Error> {
+        // do all the client work
         let client = QmiClient::new();
+        //let resp = await!(client.send_raw_msg(&[0x01,
+        //                      0x0F, 0x00,  // length
+        //                      0x00,         // control flag
+        //                      0x00,         // service type
+        //                      0x00,         // client id
+        //                      // SDU below
+        //                      0x00, // control flag
+        //                      0x00, // tx id
+        //                      0x20, 0x00, // message id
+        //                      0x04, 0x00, // Length
+        //                      0x01, // type
+        //                      0x01, 0x00, // length
+        //                      0x48 // value
+        //]));
+        Ok(Arc::new(RwLock::new(client)))
+    }
+}
+
+// TODO create_service!(DataManagementService,
+
+use fidl_fuchsia_telephony_qmi::DeviceManagementMarker;
+struct DataManagementService;
+impl DataManagementService {
+    pub fn spawn(server_end: ServerEnd<DeviceManagementMarker>, client: QmiClientPtr) {
+        if let Ok(request_stream) = server_end.into_stream() {
+            fasync::spawn(
+                request_stream
+                    .try_for_each(move |req| Self::handle_request(client.clone(), req))
+                    .unwrap_or_else(|e| fx_log_err!("Error running {:?}", e)),
+            );
+        }
+    }
+
+    async fn handle_request(
+        client: QmiClientPtr, request: DeviceManagementRequest,
+    ) -> Result<(), fidl::Error> {
         match request {
-            QmiModemRequest::RequestDataManagementService { service, responder } => {
-                let client_id: u32 = 0; // TODO set this via modem
-                fx_log_info!("request for Data Management Service from client: {}", client_id);
-                // TODO start the service
-                                                         //)())); //qmi_msg!(0x0020)));
+            DeviceManagementRequest::SetEventReport {
+                power_state,
+                battery_lvl_lower_limit,
+                battery_lvl_upper_limit,
+                pin_state,
+                activation_state,
+                operator_mode_state,
+                uim_state,
+                responder,
+            } => {
                 Ok(())
-                //responder.send(&mut resp)
+            }
+        }
+    }
+}
+
+struct QmiClientService;
+impl QmiClientService {
+    pub fn spawn(server_end: ServerEnd<QmiClientMarker>, client: QmiClientPtr) {
+        if let Ok(request_stream) = server_end.into_stream() {
+            fasync::spawn(
+                request_stream
+                    .try_for_each(move |req| Self::handle_request(client.clone(), req))
+                    .unwrap_or_else(|e| fx_log_err!("Error running {:?}", e)),
+            );
+        }
+    }
+
+    async fn handle_request(
+        client: QmiClientPtr, request: QmiClientRequest,
+    ) -> Result<(), fidl::Error> {
+        match request {
+            QmiClientRequest::RequestDataManagementService { service, responder } => {
+                DataManagementService::spawn(service, client.clone());
+                responder.send(true)
+            }
+        }
+    }
+}
+
+struct QmiModemService;
+impl QmiModemService {
+    pub fn spawn(modem: QmiModemPtr, chan: fasync::Channel) {
+        let server = QmiModemRequestStream::from_channel(chan)
+            .try_for_each_concurrent(MAX_CONCURRENT, move |req| {
+                Self::handle_request(modem.clone(), req)
+            }).unwrap_or_else(|e| fx_log_err!("Error running {:?}", e));
+        fasync::spawn(server);
+    }
+
+    async fn handle_request(
+        modem: QmiModemPtr, request: QmiModemRequest,
+    ) -> Result<(), fidl::Error> {
+        match request {
+            QmiModemRequest::ConnectTransport { channel, responder } => {
+                responder.send(modem.lock().set_transport(channel))
+            }
+            QmiModemRequest::ConnectClient { channel, responder } => {
+                if !modem.lock().connected() {
+                    return responder.send(false);
+                }
+                let m = modem.lock();
+                let client = await!(m.create_client()).unwrap();
+                QmiClientService::spawn(channel, client);
+                responder.send(true)
             }
         }
     }
@@ -81,21 +191,14 @@
 
     let mut executor = fasync::Executor::new().context("Error creating executor")?;
 
+    let modem = Arc::new(Mutex::new(QmiModem::new()));
+
     let server = ServicesServer::new()
         .add_service((QmiModemMarker::NAME, move |chan: fasync::Channel| {
             fx_log_info!("client connecting to QMI modem");
-            QmiModem::spawn(chan)
-        })).start()?;
+            QmiModemService::spawn(modem.clone(), chan)
+        })).start()
+        .context("Error starting QMI modem service")?;
 
-    let client = QmiClient::new();
-
-    let test_fut = async {
-        let qmi_resp: EventReportResponse = await!(client.send_msg(EventReport::new()))?.try_into()?;
-                                                   //.unwrap().try_into().unwrap());//Some(true), Some(3))));
-//        let response = await!(DeviceManagement::set_event_report(&mut client, Some(false), Some(0))); //&client, 0, 0, 0));
-        Ok::<(), Error>(())
-    };
-
-    let x = executor.run_singlethreaded(test_fut);
-    Ok(())
+    executor.run_singlethreaded(server)
 }
diff --git a/packages/prod/telephony b/packages/prod/telephony
index 46ffd60..58c784f 100644
--- a/packages/prod/telephony
+++ b/packages/prod/telephony
@@ -1,6 +1,7 @@
 {
     "packages": [
       "//garnet/bin/telephony/modem-mgr",
+      "//garnet/bin/telephony/qmi-modem",
       "//garnet/drivers/telephony/qmi-transport"
  ]
 }
diff --git a/public/fidl/fuchsia.telephony.qmi/qmi.fidl b/public/fidl/fuchsia.telephony.qmi/qmi.fidl
index 87f600b..9854612 100644
--- a/public/fidl/fuchsia.telephony.qmi/qmi.fidl
+++ b/public/fidl/fuchsia.telephony.qmi/qmi.fidl
@@ -22,18 +22,36 @@
   ErrorCode error;
 };
 
-/// Interface to request QMI Standard services from.
-interface QmiModem {
-  // No interface to CTL service is exposed
-
-//  /// Fulfill a request for the Wireless Data |service| interface
-//  1001: RequestWirelessDataService(request<WirelessData> service);
-
+interface QmiClient {
   /// Fulfill a request for the Device Management |service| interface
-  1002: RequestDataManagementService(request<DeviceManagement> service) -> (QmiResult res);
+  //1002: RequestDataManagementService(request<DeviceManagement> service) -> (QmiResult res);
+  1002: RequestDataManagementService(request<DeviceManagement> service) -> (bool res);
 
-//  /// Fulfill a request for the Loctaion |service| interface
-//  1003: RequestLocationService(request<Location> location);
-
-  // A bunch more services. Messaging, User Identity, Phonebook, etc...
 };
+
+/// Interface to request QMI Standard services from. Manages modem client ID
+/// allocation automatically after a transport mechanism has been connected.
+interface QmiModem {
+  /// Connect to the underlying transport |channel|. Start watching for QMI
+  /// messages to demultiplex/translate to their respective FIDL interface for
+  /// each connected client. Called per-modem, not per client connection.
+  100: ConnectTransport(handle<channel> channel) -> (bool success);
+
+  // No methods to the global CTL service is exposed, in the future we may want to expose a subset
+
+  /// Connect to the modem, allocating the client ID and exposing a set of QMI services over
+  /// the new |QmiClient| interface
+  200: ConnectClient(request<QmiClient> channel) -> (bool success);
+};
+
+
+//
+//  /// Fulfill a request for the Wireless Data |service| interface
+//  // 1001: RequestWirelessDataService(request<WirelessData> service);
+//
+//
+//  /// Fulfill a request for the Loctaion |service| interface
+//  // 1003: RequestLocationService(request<Location> location);
+//
+//  // A bunch more services. Messaging, User Identity, Phonebook, etc...
+//};