// Copyright 2023, The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! NFC Service implementation with NFC AIDL HAL (`INfc.aidl`) //! //! `INfc.aidl` only has blocking calls, but calls are called by multiple thread //! so implementation should prepare calls from multiple thread. use crate::nci; use android_hardware_nfc::aidl::android::hardware::nfc::{ INfc::INfcAsyncServer, INfcClientCallback::INfcClientCallback, NfcCloseType::NfcCloseType, NfcConfig::NfcConfig, NfcEvent::NfcEvent, NfcStatus::NfcStatus, }; use async_trait::async_trait; use binder::{DeathRecipient, IBinder, Interface, Strong}; use log::{debug, error, info}; use nix::sys::termios; use std::path::Path; use std::sync::Arc; use tokio::fs::{File, OpenOptions}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::sync::Mutex; use tokio::task::JoinSet; const BUF_SIZE: usize = 1024; const NCI_HEADER_SIZE: usize = 3; const DBG: bool = true; struct NfcClient { callback: Strong, death_recipient: DeathRecipient, } impl NfcClient { fn send_data_callback(&self, data: &[u8]) -> binder::Result<()> { match self.callback.sendData(data) { Err(err) => { info!("Failed to send data: {err}"); Err(err) } _ => Ok(()), } } fn send_event_callback(&self, event: NfcEvent, status: NfcStatus) -> binder::Result<()> { match self.callback.sendEvent(event, status) { Err(err) => { info!("Failed to send event: {err}"); Err(err) } _ => Ok(()), } } } #[derive(Default)] enum NfcSession { #[default] Closed, Opened(NfcClient), } #[derive(Default)] struct NfcHalConfig { dbg_logging: bool, } struct NfcServiceStatus { writer: File, session: NfcSession, } impl NfcServiceStatus { fn unwrap_mut_opened(&mut self) -> &mut NfcClient { match self.session { NfcSession::Opened(ref mut client) => client, _ => unreachable!(), } } fn ensure_opened(&self) -> binder::Result<&NfcClient> { match self.session { NfcSession::Opened(ref client) => Ok(client), _ => { error!("NFC isn't opened"); Err(binder::Status::new_service_specific_error(NfcStatus::FAILED.0, None)) } } } fn ensure_opened_mut(&mut self) -> binder::Result<(&mut File, &mut NfcClient)> { match self.session { NfcSession::Opened(ref mut client) => Ok((&mut self.writer, client)), _ => { error!("NFC isn't opened"); Err(binder::Status::new_service_specific_error(NfcStatus::FAILED.0, None)) } } } async fn close(&mut self) -> binder::Result<()> { match self.session { NfcSession::Opened(ref mut client) => { client.callback.as_binder().unlink_to_death(&mut client.death_recipient)?; client.send_event_callback(NfcEvent::CLOSE_CPLT, NfcStatus::OK)?; self.session = NfcSession::Closed; Ok(()) } _ => Err(binder::Status::new_service_specific_error(NfcStatus::FAILED.0, None)), } } } pub struct NfcService { _tasks: JoinSet<()>, status: Arc>, config: Arc>, } fn set_console_fd_raw(file: &File) { let mut attrs = termios::tcgetattr(file).expect("Failed to setup virtio-console to raw mode"); termios::cfmakeraw(&mut attrs); termios::tcsetattr(file, termios::SetArg::TCSANOW, &attrs) .expect("Failed to set virtio-console to raw mode"); let raw_attrs = termios::tcgetattr(file).expect("Failed to validate virtio-console mode"); if attrs != raw_attrs { panic!("Failed to set virtio-console to raw mode. Only partially applied"); } } impl NfcService { pub async fn new(dev_path: &Path) -> NfcService { // Important notes: // - Must clone FD for writing and reading. Otherwise can't read data from host side. // - Must not be closed while HAL is running. Otherwise packet loss may happen. let writer = OpenOptions::new() .read(true) .write(true) .open(dev_path) .await .expect("Failed to open virtio-console device"); // Make FD raw mode for sending raw bytes via console driver (virtio-console), set_console_fd_raw(&writer); // Must clone FD -- otherwise read may not get incoming data from host side. let mut reader = writer.try_clone().await.expect("Failed to prepare virtio-console device for reading"); let mut tasks = JoinSet::new(); let status = Arc::new(Mutex::new(NfcServiceStatus { writer, session: Default::default() })); let status_clone = status.clone(); // Keep this task running forever to prevent packet loss. read_exact() may partially read // packets and dropped if cancelled. tasks.spawn(async move { let mut buf = [0_u8; BUF_SIZE]; loop { reader .read_exact(&mut buf[0..NCI_HEADER_SIZE]) .await .expect("Failed to read from virtio-console device"); let total_packet_length = (buf[2] as usize) + NCI_HEADER_SIZE; reader .read_exact(&mut buf[NCI_HEADER_SIZE..total_packet_length]) .await .expect("Failed to read from virtio-console device"); info!("read sz={}", total_packet_length); if let Err(e) = log_packet(&buf[0..total_packet_length]) { debug!( "+ Unidentified packet ({e:?}): bytes={:?}", &buf[0..total_packet_length] ); } let status = status_clone.lock().await; if let NfcSession::Opened(ref client) = status.session { if let Err(e) = client.send_data_callback(&buf[0..total_packet_length]) { info!("Failed to send data callback. Maybe closed?: err={e:?}"); // If client is disconnected, DeathRecipient will handle clean up. } } else { info!("Nfc service is closed. Dropping incoming packets"); } } }); NfcService { _tasks: tasks, status, config: Default::default() } } } fn log_packet(packet: &[u8]) -> Result<(), anyhow::Error> { if !DBG { return Ok(()); } let header = nci::PacketHeader::parse(&packet[0..3])?; match header.get_mt() { nci::MessageType::Data => { let packet = nci::DataPacket::parse(packet)?; debug!("+ Packet: {packet:?}"); } _ => { let packet = nci::ControlPacket::parse(packet)?; debug!("+ Packet: {packet:?}"); } } Ok(()) } impl Interface for NfcService {} #[async_trait] impl INfcAsyncServer for NfcService { async fn open(&self, callback: &Strong) -> binder::Result<()> { info!("open"); let mut status = self.status.lock().await; if let NfcSession::Opened(ref _client) = status.session { info!("already opened. closing first."); status.close().await?; } let status_death_recipient = self.status.clone(); let mut death_recipient = DeathRecipient::new(move || { let mut status = status_death_recipient.blocking_lock(); if let NfcSession::Opened(ref _client) = status.session { info!("Nfc service has died"); // Just set status to closed, because no need to unlink DeathRecipient nor send event callback. status.session = NfcSession::Closed; } }); callback.as_binder().link_to_death(&mut death_recipient)?; status.session = NfcSession::Opened(NfcClient { callback: callback.clone(), death_recipient }); let session = status.unwrap_mut_opened(); session.send_event_callback(NfcEvent::OPEN_CPLT, NfcStatus::OK) } async fn close(&self, _close_type: NfcCloseType) -> binder::Result<()> { info!("close"); let mut status = self.status.lock().await; match status.session { NfcSession::Opened(_) => status.close().await, _ => { error!("NFC isn't opened"); Err(binder::Status::new_service_specific_error(NfcStatus::FAILED.0, None)) } } } async fn coreInitialized(&self) -> binder::Result<()> { info!("coreInitialized"); let status = self.status.lock().await; let client = status.ensure_opened()?; client.send_event_callback(NfcEvent::POST_INIT_CPLT, NfcStatus::OK) } async fn factoryReset(&self) -> binder::Result<()> { info!("factoryReset"); let _status = self.status.lock().await; // No-op Ok(()) } async fn getConfig(&self) -> binder::Result { info!("getConfig"); let _status = self.status.lock().await; // TODO: read config from libnfc-hal-cf.conf (/apex//etc) Ok(NfcConfig { nfaPollBailOutMode: true, maxIsoDepTransceiveLength: 0xFEFF, defaultOffHostRoute: 0x81_u8 as i8, defaultOffHostRouteFelica: 0x81_u8 as i8, defaultSystemCodeRoute: 0x00, defaultSystemCodePowerState: 0x3B, defaultRoute: 0x00, offHostRouteUicc: vec![0x81], offHostRouteEse: vec![0x81], defaultIsoDepRoute: 0x81_u8 as i8, ..Default::default() }) } async fn powerCycle(&self) -> binder::Result<()> { info!("powerCycle"); let status = self.status.lock().await; let client = status.ensure_opened()?; client.send_event_callback(NfcEvent::OPEN_CPLT, NfcStatus::OK) } async fn preDiscover(&self) -> binder::Result<()> { info!("preDiscover"); let status = self.status.lock().await; let client = status.ensure_opened()?; client.send_event_callback(NfcEvent::PRE_DISCOVER_CPLT, NfcStatus::OK) } async fn write(&self, data: &[u8]) -> binder::Result { info!("write, sz={}", data.len()); let mut status = self.status.lock().await; let (writer, _) = status.ensure_opened_mut()?; writer.write_all(data).await.expect("Failed to write virtio-console device"); if let Err(e) = log_packet(data) { debug!("+ Unidentified packet ({e:?}): bytes={data:?}"); } Ok(data.len().try_into().unwrap()) } async fn setEnableVerboseLogging(&self, enable: bool) -> binder::Result<()> { info!("setEnableVerboseLogging"); let mut config = self.config.lock().await; config.dbg_logging = enable; Ok(()) } async fn isVerboseLoggingEnabled(&self) -> binder::Result { info!("isVerboseLoggingEnabled"); let config = self.config.lock().await; Ok(config.dbg_logging) } }