| // Copyright 2021 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 <algorithm> |
| #include <array> |
| #include <utility> |
| #include <base/bind.h> |
| #include <base/logging.h> |
| #include <glib.h> |
| #include <gio/gio.h> |
| #include <gmodule.h> |
| #include <gio/gunixsocketaddress.h> |
| #include <libmbim-glib/libmbim-glib.h> |
| #include "hermes/apdu.h" |
| #include "hermes/euicc_manager_interface.h" |
| #include "hermes/hermes_common.h" |
| #include "hermes/modem_mbim.h" |
| #include "hermes/sgp_22.h" |
| #include "hermes/type_traits.h" |
| |
| namespace { |
| const int kExecutorIndex = 0; |
| const guint kMbimTimeoutSeconds = 30; |
| constexpr int kMbimMessageSuccess = 144; |
| constexpr auto kSlotInfoDelay = base::Seconds(5); |
| // Application identifier for the eUICC's SIM EID |
| const std::array<uint8_t, 12> kMbimEidReqApdu = { |
| 0x81, 0xE2, 0x91, 0x00, 0x06, 0xBF, 0x3E, 0x03, 0x5C, 0x01, 0x5A, 0x00, |
| }; |
| |
| // ModemManager uses channel_group=1. Make Hermes use 2 just to be cautious. |
| constexpr int kChannelGroupId = 2; |
| std::vector<uint8_t> AidIsdr() { |
| return {hermes::kAidIsdr.begin(), hermes::kAidIsdr.end()}; |
| } |
| |
| bool GetReadyState(MbimDevice* device, |
| bool is_notification, |
| MbimMessage* notification, |
| MbimSubscriberReadyState* ready_state) { |
| g_autoptr(GError) error = NULL; |
| |
| if (mbim_device_check_ms_mbimex_version(device, 3, 0)) { |
| MbimSubscriberReadyStatusFlag flags = |
| MBIM_SUBSCRIBER_READY_STATUS_FLAG_NONE; |
| auto parser_v3 = |
| is_notification |
| ? &mbim_message_ms_basic_connect_v3_subscriber_ready_status_notification_parse |
| : &mbim_message_ms_basic_connect_v3_subscriber_ready_status_response_parse; |
| if (!parser_v3(notification, ready_state, &flags, NULL, NULL, |
| NULL, /* ready_info */ |
| NULL, /* telephone_numbers_count */ |
| NULL, /* telephone_numbers */ |
| &error)) { |
| LOG(ERROR) << __func__ << ": Failed due to error: " << error->message; |
| return false; |
| } |
| } else { |
| auto parser = is_notification |
| ? &mbim_message_subscriber_ready_status_notification_parse |
| : &mbim_message_subscriber_ready_status_response_parse; |
| if (!parser(notification, ready_state, |
| /* subscriber_id */ NULL, |
| /* sim_iccid */ NULL, |
| /* ready_info */ NULL, |
| /* telephone_numbers_count */ NULL, |
| /* telephone_numbers */ NULL, &error)) { |
| LOG(ERROR) << __func__ << ": Failed due to error: " << error->message; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| namespace hermes { |
| |
| /* static */ |
| std::unique_ptr<ModemMbim> ModemMbim::Create( |
| Logger* logger, |
| Executor* executor, |
| std::unique_ptr<ModemManagerProxy> modem_manager_proxy) { |
| VLOG(2) << __func__; |
| return std::unique_ptr<ModemMbim>( |
| new ModemMbim(logger, executor, std::move(modem_manager_proxy))); |
| } |
| |
| ModemMbim::ModemMbim(Logger* logger, |
| Executor* executor, |
| std::unique_ptr<ModemManagerProxy> modem_manager_proxy) |
| : Modem<MbimCmd>(logger, executor, std::move(modem_manager_proxy)), |
| channel_(kInvalidChannel), |
| is_ready_state_valid(false), |
| ready_state_(MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED), |
| slot_info_(), |
| weak_factory_(this) {} |
| |
| ModemMbim::~ModemMbim() { |
| VLOG(2) << "~ModemMbim Destructor++"; |
| Shutdown(); |
| } |
| |
| void ModemMbim::Initialize(EuiccManagerInterface* euicc_manager, |
| ResultCallback cb) { |
| LOG(INFO) << __func__; |
| CHECK(current_state_ == State::kMbimUninitialized); |
| retry_initialization_callback_.Reset(); |
| euicc_manager_ = euicc_manager; |
| InitializationStep(State::kMbimUninitialized, std::move(cb)); |
| } |
| |
| void ModemMbim::Shutdown() { |
| LOG(INFO) << __func__; |
| CloseDevice(); |
| channel_ = kInvalidChannel; |
| ready_state_ = MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED; |
| current_state_.Transition(State::kMbimUninitialized); |
| slot_info_.Clear(); |
| eid_read_failed_ = false; |
| modem_manager_proxy_->ScheduleUninhibit(base::Seconds(0)); |
| } |
| |
| void ModemMbim::TransmitFromQueue() { |
| VLOG(2) << __func__; |
| if (tx_queue_.empty() || retry_initialization_callback_) { |
| return; |
| } |
| if (!device_) { |
| LOG(ERROR) << "No MBIM device. Cannot transmit MBIM message"; |
| ProcessMbimResult(kModemMessageProcessingError); |
| return; |
| } |
| |
| auto mbim_cmd = tx_queue_[0].msg_.get(); |
| switch (mbim_cmd->mbim_type()) { |
| case MbimCmd::MbimType::kMbimOpenChannel: |
| TransmitOpenChannel(); |
| break; |
| case MbimCmd::MbimType::kMbimCloseChannel: |
| TransmitCloseChannel(); |
| break; |
| case MbimCmd::MbimType::kMbimSendApdu: |
| TransmitMbimSendApdu(&tx_queue_[0]); |
| break; |
| case MbimCmd::MbimType::kMbimSubscriberStatusReady: |
| TransmitSubscriberStatusReady(); |
| break; |
| case MbimCmd::MbimType::kMbimDeviceCaps: |
| TransmitDeviceCaps(); |
| break; |
| case MbimCmd::MbimType::kMbimSendEidApdu: |
| TransmitSendEidApdu(); |
| break; |
| case MbimCmd::MbimType::kMbimSlotInfoStatus: |
| TransmitSlotInfoStatus(); |
| break; |
| case MbimCmd::MbimType::kMbimDeviceSlotMapping: |
| TransmitDeviceSlotMapping(); |
| break; |
| case MbimCmd::MbimType::kMbimSetDeviceSlotMapping: |
| TransmitSetDeviceSlotMapping(); |
| break; |
| case MbimCmd::MbimType::kMbimSysCaps: |
| TransmitSysCapsQuery(); |
| break; |
| default: |
| VLOG(2) << "no instruction"; |
| break; |
| } |
| } |
| |
| std::unique_ptr<MbimCmd> ModemMbim::GetTagForSendApdu() { |
| return std::make_unique<MbimCmd>(MbimCmd::MbimType::kMbimSendApdu); |
| } |
| |
| void ModemMbim::ProcessMbimResult(int err) { |
| if (tx_queue_.empty()) { |
| VLOG(2) << __func__ << ": Queue is empty"; |
| return; |
| } |
| // pop before running the callback since the callback might change the state |
| // of the queue. |
| auto cb_ = std::move(tx_queue_[0].cb_); |
| tx_queue_.pop_front(); |
| if (!cb_.is_null()) { |
| std::move(cb_).Run(err); |
| } |
| } |
| |
| /* static */ |
| void ModemMbim::MbimCreateNewDeviceCb(GObject* source, |
| GAsyncResult* res, |
| ModemMbim* modem_mbim) { |
| /* Open the device */ |
| VLOG(2) << __func__; |
| CHECK(modem_mbim) << "modem_mbim does not exist"; |
| g_autoptr(GError) error = NULL; |
| glib_bridge::ScopedGObject<MbimDevice> mbimdevice( |
| mbim_device_new_finish(res, &error)); |
| modem_mbim->device_ = std::move(mbimdevice); |
| if (!modem_mbim->device_.get() || error != NULL) { |
| LOG(INFO) << "The modem may be booting ..."; |
| modem_mbim->RetryInitialization(std::move(modem_mbim->init_done_cb_)); |
| return; |
| } |
| mbim_device_open_full(modem_mbim->device_.get(), MBIM_DEVICE_OPEN_FLAGS_PROXY, |
| kMbimTimeoutSeconds, /* cancellable */ NULL, |
| (GAsyncReadyCallback)MbimDeviceOpenReadyCb, modem_mbim); |
| } |
| |
| /* static */ |
| void ModemMbim::MbimDeviceOpenReadyCb(MbimDevice* device, |
| GAsyncResult* res, |
| ModemMbim* modem_mbim) { |
| VLOG(2) << __func__; |
| g_autoptr(GError) error = NULL; |
| base::OnceCallback<void(base::OnceCallback<void(int)>)> get_caps; |
| if (!mbim_device_open_finish(device, res, &error)) { |
| LOG(ERROR) << "Failed due to error: " << error->message; |
| modem_mbim->RetryInitialization(std::move(modem_mbim->init_done_cb_)); |
| return; |
| } |
| modem_mbim->indication_id_ = g_signal_connect( |
| modem_mbim->device_.get(), MBIM_DEVICE_SIGNAL_INDICATE_STATUS, |
| G_CALLBACK(ClientIndicationCb), modem_mbim); |
| if (modem_mbim->current_state_ == State::kMbimStarted) { |
| VLOG(2) << "Opened device. Reusing previous IMEI"; |
| std::move(modem_mbim->init_done_cb_).Run(kModemSuccess); |
| return; |
| } |
| LOG(INFO) << "Mbim device is ready, acquire imei and eid"; |
| modem_mbim->InitializationStep(State::kReadImei, |
| std::move(modem_mbim->init_done_cb_)); |
| } |
| |
| void ModemMbim::InitializationStep(ModemMbim::State::Value next_state, |
| ResultCallback cb) { |
| // cb is executed only after all initialization steps complete or if there is |
| // an error. cb is passed in from Initialize and passed around until the last |
| // step. At the last step, the cb is executed. |
| current_state_.Transition(next_state); |
| switch (next_state) { |
| case State::Value::kMbimUninitialized: |
| // Store cb in init_done_cb_ so that MbimCreateNewDeviceCb can use it |
| // while calling InitializationStep(kGetSubscriberReadyState) |
| init_done_cb_ = std::move(cb); |
| modem_manager_proxy_->WaitForModem(base::BindOnce( |
| &ModemMbim::InitializationStep, weak_factory_.GetWeakPtr(), |
| State::kMbimInitializeStarted, std::move(cb))); |
| break; |
| case State::kMbimInitializeStarted: { |
| if (modem_manager_proxy_->GetPrimaryPort().empty()) { |
| LOG(ERROR) << __func__ << ": Could not get primary port from MM"; |
| std::move(init_done_cb_).Run(kModemManagerError); |
| break; |
| } |
| std::string dev_path = "/dev/" + modem_manager_proxy_->GetPrimaryPort(); |
| const gchar* const path = dev_path.c_str(); |
| LOG(INFO) << __func__ << ": Opening path:" << path; |
| file_ = g_file_new_for_path(path); |
| mbim_device_new(file_, /* cancellable */ NULL, |
| (GAsyncReadyCallback)MbimCreateNewDeviceCb, |
| this); // MbimCreateNewDeviceCb will call |
| // InitializationStep(kReadImei) |
| break; |
| } |
| case State::kReadImei: |
| SendMessage(MbimCmd::MbimType::kMbimDeviceCaps, |
| std::make_unique<TxInfo>(), std::move(cb), |
| &ModemMbim::InitializationStep, |
| State::kGetSubscriberReadyState); |
| break; |
| case State::kGetSubscriberReadyState: |
| SendMessage(MbimCmd::MbimType::kMbimSubscriberStatusReady, |
| std::unique_ptr<TxInfo>(), std::move(cb), |
| &ModemMbim::InitializationStep, State::kCheckSingleSim); |
| break; |
| case State::kCheckSingleSim: { |
| if (!mbim_device_check_ms_mbimex_version(device_.get(), 2, 0)) { |
| // we have a single sim device. Skip to reading EID if a SIM was seen. |
| if (ready_state_ == MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED) { |
| VLOG(2) << "Sim not inserted"; |
| // skip retries, close device, listen for a new modem, and run cb |
| retry_count_ = kMaxRetries + 1; |
| RetryInitialization(std::move(cb)); |
| break; |
| } |
| slot_info_.InitSlotInfo(1 /* slot_count */, 1 /* map_count */); |
| slot_info_.cached_active_slot_ = 0; |
| slot_info_.slot_state_[0] = |
| MBIM_UICC_SLOT_STATE_ACTIVE_ESIM; // Assume we have an eSIM until |
| // we can confirm the EID. This |
| // is an mbimv1 limitation. |
| InitializationStep(State::kCloseChannel, std::move(cb)); |
| break; |
| } |
| InitializationStep(State::kSysQuery, std::move(cb)); |
| break; |
| } |
| case State::kSysQuery: |
| SendMessage(MbimCmd::MbimType::kMbimSysCaps, std::unique_ptr<TxInfo>(), |
| std::move(cb), &ModemMbim::InitializationStep, |
| State::kDeviceSlotMapping); |
| break; |
| case State::kDeviceSlotMapping: |
| // To figure out active slot |
| SendMessage(MbimCmd::MbimType::kMbimDeviceSlotMapping, |
| std::make_unique<SwitchSlotTxInfo>(0), std::move(cb), |
| &ModemMbim::InitializationStep, State::kSlotInfo); |
| break; |
| case State::kSlotInfo: |
| ReadSlotsInfo( |
| 0 /* slot_num */, 0 /* retry_count */, |
| std::move( |
| cb)); // ReadSlotsInfo calls InitializationStep(CloseChannel) |
| // after slot info for all slots has been read. This is an |
| // exception to the rule that all state transitions must be |
| // captured in InitializationStep. |
| break; |
| case State::kCloseChannel: |
| if (!slot_info_.IsEsimActive()) { |
| InitializationStep(State::kMbimStarted, std::move(cb)); |
| break; |
| } |
| SendMessage(MbimCmd::MbimType::kMbimCloseChannel, |
| std::unique_ptr<TxInfo>(), std::move(cb), |
| &ModemMbim::InitializationStep, State::kOpenChannel); |
| break; |
| case State::kOpenChannel: |
| SendMessage(MbimCmd::MbimType::kMbimOpenChannel, |
| std::make_unique<OpenChannelTxInfo>(AidIsdr()), std::move(cb), |
| &ModemMbim::InitializationStep, State::kReadEid); |
| break; |
| case State::kReadEid: |
| SendMessage(MbimCmd::MbimType::kMbimSendEidApdu, |
| std::unique_ptr<TxInfo>(), std::move(cb), |
| &ModemMbim::InitializationStep, State::kEidReadComplete); |
| break; |
| case State::kEidReadComplete: |
| SendMessage(MbimCmd::MbimType::kMbimCloseChannel, |
| std::unique_ptr<TxInfo>(), std::move(cb), |
| &ModemMbim::InitializationStep, State::kMbimStarted); |
| break; |
| case State::kMbimStarted: |
| CloseDevice(); |
| if (eid_read_failed_) { |
| LOG(ERROR) << "EID read failed"; |
| eid_read_failed_ = false; |
| std::move(cb).Run(kModemMessageProcessingError); |
| return; |
| } |
| VLOG(2) << "eSIM initialized for MBIM modem"; |
| std::move(cb).Run(kModemSuccess); |
| OnEuiccUpdated(); |
| break; |
| } |
| } |
| |
| void ModemMbim::OnEuiccUpdated() { |
| VLOG(2) << __func__ << ": " << slot_info_; |
| for (int i = 0; i < slot_info_.slot_count_; i++) { |
| if (slot_info_.IsEuicc(i)) { |
| auto euicc_slot_info = |
| (slot_info_.cached_active_slot_ == i) |
| ? EuiccSlotInfo(kExecutorIndex, slot_info_.eid_[i]) |
| : EuiccSlotInfo(slot_info_.eid_[i]); |
| euicc_manager_->OnEuiccUpdated(i + 1, EuiccSlotInfo(slot_info_.eid_[i])); |
| } |
| } |
| euicc_manager_->OnLogicalSlotUpdated(slot_info_.cached_active_slot_ + 1, |
| slot_info_.cached_active_slot_ + 1); |
| } |
| |
| void ModemMbim::ReadSlotsInfo(uint32_t slot_num, |
| uint32_t retry_count, |
| ResultCallback cb) { |
| VLOG(2) << __func__ << ": slot_num:" << slot_num; |
| if (slot_num == slot_info_.slot_count_) { |
| if (slot_info_.IsEuiccPresentOnAnySlot() || retry_count >= kMaxRetries) { |
| InitializationStep(State::kCloseChannel, std::move(cb)); |
| return; |
| } |
| // post a delayed task after 5secs |
| executor_->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&ModemMbim::ReadSlotsInfo, weak_factory_.GetWeakPtr(), 0, |
| ++retry_count, std::move(cb)), |
| kSlotInfoDelay); |
| } |
| SendMessage(MbimCmd::MbimType::kMbimSlotInfoStatus, |
| std::make_unique<SwitchSlotTxInfo>(slot_num), std::move(cb), |
| &ModemMbim::ReadSlotsInfo, slot_num + 1, 0 /* retry_count */); |
| } |
| |
| void ModemMbim::TransmitSysCapsQuery() { |
| g_autoptr(MbimMessage) message = NULL; |
| VLOG(2) << __func__; |
| message = mbim_message_ms_basic_connect_extensions_sys_caps_query_new(NULL); |
| if (!message) { |
| LOG(ERROR) << "Mbim message creation failed"; |
| ProcessMbimResult(kModemMessageProcessingError); |
| return; |
| } |
| mbim_device_command(device_.get(), message, kMbimTimeoutSeconds, |
| /* cancellable */ NULL, |
| (GAsyncReadyCallback)QuerySysCapsReady, this); |
| } |
| |
| void ModemMbim::TransmitSubscriberStatusReady() { |
| g_autoptr(MbimMessage) message = NULL; |
| VLOG(2) << __func__; |
| message = mbim_message_subscriber_ready_status_query_new(NULL); |
| if (!message) { |
| LOG(ERROR) << "Mbim message creation failed"; |
| ProcessMbimResult(kModemMessageProcessingError); |
| return; |
| } |
| mbim_device_command(device_.get(), message, kMbimTimeoutSeconds, |
| /* cancellable */ NULL, |
| (GAsyncReadyCallback)SubscriberReadyStatusRspCb, this); |
| } |
| |
| void ModemMbim::TransmitDeviceCaps() { |
| g_autoptr(MbimMessage) message = NULL; |
| VLOG(2) << __func__; |
| message = mbim_message_device_caps_query_new(/* error */ NULL); |
| if (!message) { |
| LOG(ERROR) << __func__ << " :Mbim message creation failed"; |
| ProcessMbimResult(kModemMessageProcessingError); |
| return; |
| } |
| mbim_device_command(device_.get(), message, kMbimTimeoutSeconds, |
| /*cancellable*/ NULL, |
| (GAsyncReadyCallback)DeviceCapsQueryReady, this); |
| } |
| |
| void ModemMbim::TransmitCloseChannel() { |
| g_autoptr(MbimMessage) message = NULL; |
| g_autoptr(GError) error = NULL; |
| VLOG(2) << __func__; |
| message = mbim_message_ms_uicc_low_level_access_close_channel_set_new( |
| /* channel */ 0, kChannelGroupId, &error); |
| if (!message) { |
| LOG(ERROR) << "Mbim message creation failed:" << error->message; |
| ProcessMbimResult(kModemMessageProcessingError); |
| return; |
| } |
| mbim_device_command(device_.get(), message, kMbimTimeoutSeconds, |
| /* cancellable */ NULL, |
| (GAsyncReadyCallback)UiccLowLevelAccessCloseChannelSetCb, |
| this); |
| } |
| |
| void ModemMbim::TransmitOpenChannel() { |
| VLOG(2) << __func__; |
| auto open_channel_tx_info = |
| dynamic_cast<OpenChannelTxInfo*>(tx_queue_[0].info_.get()); |
| guint8 appId[16]; |
| guint32 appIdSize = open_channel_tx_info->aid_.size(); |
| g_autoptr(GError) error = NULL; |
| g_autoptr(MbimMessage) message = NULL; |
| std::copy(open_channel_tx_info->aid_.begin(), |
| open_channel_tx_info->aid_.end(), appId); |
| message = mbim_message_ms_uicc_low_level_access_open_channel_set_new( |
| appIdSize, appId, /* selectP2arg */ 4, kChannelGroupId, &error); |
| if (!message) { |
| LOG(ERROR) << __func__ << ": Mbim Message Creation Failed"; |
| ProcessMbimResult(kModemMessageProcessingError); |
| return; |
| } |
| mbim_device_command(device_.get(), message, kMbimTimeoutSeconds, |
| /*cancellable*/ NULL, |
| (GAsyncReadyCallback)UiccLowLevelAccessOpenChannelSetCb, |
| this); |
| } |
| |
| void ModemMbim::TransmitSendEidApdu() { |
| VLOG(2) << __func__; |
| uint8_t eid_apduCmd[kMaxApduLen]; |
| guint32 kMbimEidReqApduSize = kMbimEidReqApdu.size(); |
| g_autoptr(MbimMessage) message = NULL; |
| MbimUiccSecureMessaging secure_messaging = MBIM_UICC_SECURE_MESSAGING_NONE; |
| MbimUiccClassByteType class_byte_type = MBIM_UICC_CLASS_BYTE_TYPE_EXTENDED; |
| std::copy(kMbimEidReqApdu.begin(), kMbimEidReqApdu.end(), eid_apduCmd); |
| message = (mbim_message_ms_uicc_low_level_access_apdu_set_new( |
| channel_, secure_messaging, class_byte_type, kMbimEidReqApduSize, |
| eid_apduCmd, NULL)); |
| mbim_device_command(device_.get(), message, kMbimTimeoutSeconds, |
| /* cancellable */ NULL, |
| (GAsyncReadyCallback)UiccLowLevelAccessApduEidParse, |
| this); |
| } |
| |
| void ModemMbim::TransmitSlotInfoStatus() { |
| g_autoptr(MbimMessage) message = NULL; |
| g_autoptr(GError) error = NULL; |
| auto switch_slot_tx_info = |
| dynamic_cast<SwitchSlotTxInfo*>(tx_queue_[0].info_.get()); |
| VLOG(2) << __func__ << ": slot:" << switch_slot_tx_info->physical_slot_; |
| message = |
| (mbim_message_ms_basic_connect_extensions_slot_info_status_query_new( |
| switch_slot_tx_info->physical_slot_, &error)); |
| if (!message) { |
| LOG(ERROR) << "Mbim message creation failed"; |
| return; |
| } |
| mbim_device_command(device_.get(), message, kMbimTimeoutSeconds, |
| /* cancellable */ NULL, |
| (GAsyncReadyCallback)DeviceSlotStatusInfoRspCb, this); |
| } |
| |
| void ModemMbim::TransmitDeviceSlotMapping() { |
| g_autoptr(MbimMessage) message = NULL; |
| VLOG(2) << __func__; |
| message = |
| mbim_message_ms_basic_connect_extensions_device_slot_mappings_query_new( |
| NULL); |
| if (!message) { |
| LOG(ERROR) << "Mbim message creation failed"; |
| return; |
| } |
| mbim_device_command(device_.get(), message, kMbimTimeoutSeconds, |
| /* cancellable */ NULL, |
| (GAsyncReadyCallback)DeviceSlotStatusMappingRspCb, this); |
| } |
| |
| void ModemMbim::TransmitSetDeviceSlotMapping() { |
| g_autoptr(MbimMessage) message = NULL; |
| g_autoptr(MbimSlotArray) slot_mappings = |
| NULL; // Array of MbimSlotArray. Every executor gets a MbimSlotArray. |
| // MbimSlotArray always holds a single element. |
| slot_mappings = g_new0(MbimSlot*, slot_info_.map_count_ + 1); |
| slot_mappings[kExecutorIndex] = g_new0(MbimSlot, 1); |
| VLOG(2) << __func__; |
| auto switch_slot_tx_info = |
| dynamic_cast<SwitchSlotTxInfo*>(tx_queue_[0].info_.get()); |
| VLOG(2) << __func__ << ": Hermes trying to operate on slot:" |
| << switch_slot_tx_info->physical_slot_; |
| slot_mappings[kExecutorIndex]->slot = switch_slot_tx_info->physical_slot_; |
| message = |
| mbim_message_ms_basic_connect_extensions_device_slot_mappings_set_new( |
| slot_info_.map_count_, (const MbimSlot**)slot_mappings, NULL); |
| if (!message) { |
| LOG(ERROR) << "Mbim message creation failed"; |
| return; |
| } |
| LOG(INFO) << __func__ << ": before mbim_device_command"; |
| mbim_device_command(device_.get(), message, kMbimTimeoutSeconds, |
| /* cancellable */ NULL, |
| (GAsyncReadyCallback)SetDeviceSlotMappingsRspCb, this); |
| } |
| |
| void ModemMbim::TransmitMbimSendApdu(TxElement* tx_element) { |
| g_autoptr(MbimMessage) message = NULL; |
| MbimUiccSecureMessaging secure_messaging = MBIM_UICC_SECURE_MESSAGING_NONE; |
| MbimUiccClassByteType class_byte_type = MBIM_UICC_CLASS_BYTE_TYPE_EXTENDED; |
| uint8_t* fragment; |
| size_t apdu_len = 0; |
| uint8_t apduCmd[kMaxApduLen] = {0}; |
| VLOG(2) << __func__; |
| ApduTxInfo* apdu = static_cast<ApduTxInfo*>(tx_element->info_.get()); |
| size_t fragment_size = apdu->apdu_.GetNextFragment(&fragment); |
| VLOG(2) << "Fragment size:" << fragment_size; |
| apdu_len = fragment_size; |
| std::copy(fragment, fragment + fragment_size, apduCmd); |
| apduCmd[apdu_len++] = |
| 0x00; // append extra byte for 4G Mbim Modem required for apdu handling |
| LOG(INFO) << "Sending APDU fragment (" << apdu_len << " bytes): over channel " |
| << channel_; |
| VLOG(2) << "APDU:" << base::HexEncode(apduCmd, apdu_len); |
| message = (mbim_message_ms_uicc_low_level_access_apdu_set_new( |
| channel_, secure_messaging, class_byte_type, apdu_len, apduCmd, NULL)); |
| mbim_device_command(device_.get(), message, kMbimTimeoutSeconds, |
| /* cancellable */ NULL, |
| (GAsyncReadyCallback)UiccLowLevelAccessApduResponseParse, |
| this); |
| return; |
| } |
| |
| void ModemMbim::ReacquireChannel(EuiccEventStep step, |
| std::vector<uint8_t> aid, |
| ResultCallback cb) { |
| LOG(INFO) << __func__ << ":" << to_underlying(step); |
| switch (step) { |
| case EuiccEventStep::CLOSE_CHANNEL: |
| SendMessage(MbimCmd::MbimType::kMbimCloseChannel, |
| std::unique_ptr<TxInfo>(), std::move(cb), |
| &ModemMbim::ReacquireChannel, EuiccEventStep::OPEN_CHANNEL, |
| std::move(aid)); |
| break; |
| case EuiccEventStep::OPEN_CHANNEL: |
| SendMessage(MbimCmd::MbimType::kMbimOpenChannel, |
| std::make_unique<OpenChannelTxInfo>(std::move(aid)), |
| std::move(cb), &ModemMbim::ReacquireChannel, |
| EuiccEventStep::EUICC_EVENT_STEP_LAST, std::move(aid)); |
| break; |
| case EuiccEventStep::EUICC_EVENT_STEP_LAST: |
| std::move(cb).Run(kModemSuccess); |
| break; |
| default: |
| VLOG(2) << "No Suitable operation"; |
| } |
| } |
| |
| /* static */ |
| void ModemMbim::SubscriberReadyStatusRspCb(MbimDevice* device, |
| GAsyncResult* res, |
| ModemMbim* modem_mbim) { |
| g_autoptr(MbimMessage) response = NULL; |
| VLOG(2) << __func__; |
| g_autoptr(GError) error = NULL; |
| response = mbim_device_command_finish(device, res, &error); |
| if (!GetReadyState(device, false /* is_notification */, response, |
| &modem_mbim->ready_state_)) { |
| LOG(ERROR) << "Could not parse ready state"; |
| modem_mbim->ProcessMbimResult(kModemMessageProcessingError); |
| return; |
| } |
| LOG(INFO) << "Current Sim status:" << modem_mbim->ready_state_; |
| modem_mbim->ProcessMbimResult(kModemSuccess); |
| } |
| |
| /* static */ |
| void ModemMbim::DeviceCapsQueryReady(MbimDevice* device, |
| GAsyncResult* res, |
| ModemMbim* modem_mbim) { |
| g_autoptr(MbimMessage) response = NULL; |
| g_autoptr(GError) error = NULL; |
| g_autofree gchar* caps_device_id = NULL; |
| VLOG(2) << __func__; |
| response = mbim_device_command_finish(device, res, &error); |
| if (!response || |
| !mbim_message_response_get_result( |
| response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) || |
| !mbim_message_device_caps_response_parse(response, NULL, /* device_type */ |
| NULL, /* cellular class */ |
| NULL, /* voice_class */ |
| NULL, /* sim_class */ |
| NULL, /* data_class */ |
| NULL, /* sms_caps */ |
| NULL, /* ctrl_caps */ |
| NULL, /* max_sessions */ |
| NULL, /* custom_data_class */ |
| &caps_device_id, |
| NULL, /* firmware_info */ |
| NULL, /* hardware_info */ |
| &error)) { |
| modem_mbim->ProcessMbimResult(kModemMessageProcessingError); |
| return; |
| } |
| modem_mbim->imei_ = caps_device_id; |
| VLOG(2) << "IMEI received from modem:" << modem_mbim->imei_; |
| modem_mbim->ProcessMbimResult(kModemSuccess); |
| } |
| |
| /* static */ |
| void ModemMbim::UiccLowLevelAccessCloseChannelSetCb(MbimDevice* device, |
| GAsyncResult* res, |
| ModemMbim* modem_mbim) { |
| g_autoptr(GError) error = NULL; |
| g_autoptr(MbimMessage) response = NULL; |
| guint32 status; |
| LOG(INFO) << __func__; |
| response = mbim_device_command_finish(device, res, &error); |
| if (response && |
| mbim_message_response_get_result(response, MBIM_MESSAGE_TYPE_COMMAND_DONE, |
| &error) && |
| mbim_message_ms_uicc_low_level_access_close_channel_response_parse( |
| response, &status, &error)) { |
| modem_mbim->channel_ = kInvalidChannel; |
| modem_mbim->ProcessMbimResult(kModemSuccess); |
| return; |
| } |
| if (g_error_matches(error, MBIM_STATUS_ERROR, |
| MBIM_STATUS_ERROR_OPERATION_NOT_ALLOWED)) { |
| LOG(INFO) << "Operation not allowed from modem:" << error->message; |
| } else { |
| LOG(INFO) << "Channel could not be closed:" << error->message; |
| } |
| modem_mbim->ProcessMbimResult(kModemSuccess); |
| } |
| |
| /* static */ |
| void ModemMbim::UiccLowLevelAccessOpenChannelSetCb(MbimDevice* device, |
| GAsyncResult* res, |
| ModemMbim* modem_mbim) { |
| g_autoptr(GError) error = NULL; |
| g_autoptr(MbimMessage) response = NULL; |
| guint32 status; |
| guint32 chl; |
| guint32 rsp_size; |
| const guint8* rsp = NULL; |
| LOG(INFO) << __func__; |
| response = mbim_device_command_finish(device, res, &error); |
| if (response && |
| mbim_message_response_get_result(response, MBIM_MESSAGE_TYPE_COMMAND_DONE, |
| &error) && |
| mbim_message_ms_uicc_low_level_access_open_channel_response_parse( |
| response, &status, &chl, &rsp_size, &rsp, &error)) { |
| if (status != kMbimMessageSuccess) { |
| LOG(INFO) << "Could not open channel:" << error->message |
| << ". Inserted sim may not be an eSIM."; |
| modem_mbim->ProcessMbimResult(kModemMessageProcessingError); |
| return; |
| } |
| VLOG(2) << "Successfully opened channel:" << chl; |
| modem_mbim->channel_ = chl; |
| modem_mbim->open_channel_raw_response_.clear(); |
| for (int i = 0; i < rsp_size; i++) |
| modem_mbim->open_channel_raw_response_.push_back(rsp[i]); |
| modem_mbim->open_channel_raw_response_.push_back(status & 0xFF); |
| modem_mbim->open_channel_raw_response_.push_back((status >> 8) & 0xFF); |
| VLOG(2) << __func__ << " Open Channel Response: " |
| << base::HexEncode(modem_mbim->open_channel_raw_response_.data(), |
| modem_mbim->open_channel_raw_response_.size()); |
| modem_mbim->ProcessMbimResult(kModemSuccess); |
| return; |
| } |
| if (g_error_matches(error, MBIM_STATUS_ERROR, |
| MBIM_STATUS_ERROR_OPERATION_NOT_ALLOWED)) { |
| LOG(INFO) << "Modem FW may not support eSIM:" << error->message; |
| } else { |
| LOG(INFO) << "Could not open channel:" << error->message |
| << ". Inserted sim may not be an eSIM."; |
| } |
| // not being able to open a channel is irrecoverable on L850. On dual sim |
| // modems too, we should not attempt to open a channel unless we know we have |
| // an eSIM. |
| modem_mbim->retry_count_ = kMaxRetries + 1; |
| modem_mbim->ProcessMbimResult(kModemMessageProcessingError); |
| } |
| |
| /* static */ |
| bool ModemMbim::ParseEidApduResponse(const MbimMessage* response, |
| std::string* eid, |
| ModemMbim* modem_mbim) { |
| g_autoptr(GError) error = NULL; |
| guint32 status; |
| guint32 response_size = 0; |
| const guint8* out_response = NULL; |
| std::vector<uint8_t> kGetEidDgiTag = {0xBF, 0x3E, 0x12, 0x5A, 0x10}; |
| if (!response) |
| return false; |
| if (!mbim_message_response_get_result( |
| response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) { |
| LOG(ERROR) << "Could not parse EID: " << error->message; |
| if (modem_mbim) { |
| // We've likely encountered b/230851574. Set flag that closes the channel, |
| // and restores the slot. |
| modem_mbim->retry_count_ = kMaxRetries + 1; |
| modem_mbim->eid_read_failed_ = true; |
| } |
| return false; |
| } |
| if (!mbim_message_ms_uicc_low_level_access_apdu_response_parse( |
| response, &status, &response_size, &out_response, &error)) { |
| LOG(ERROR) << "Could not parse EID: " << error->message; |
| return false; |
| } |
| if (response_size < 2 || out_response[0] != kGetEidDgiTag[0] || |
| out_response[1] != kGetEidDgiTag[1]) { |
| return false; |
| } |
| VLOG(2) << "Decoding EID from APDU response (" << response_size << " bytes)" |
| << base::HexEncode(&out_response[kGetEidDgiTag.size()], |
| response_size - kGetEidDgiTag.size()); |
| for (int j = kGetEidDgiTag.size(); j < response_size; j++) { |
| *eid += bcd_chars[(out_response[j] >> 4) & 0xF]; |
| *eid += bcd_chars[out_response[j] & 0xF]; |
| } |
| return true; |
| } |
| |
| /* static */ |
| void ModemMbim::UiccLowLevelAccessApduEidParse(MbimDevice* device, |
| GAsyncResult* res, |
| ModemMbim* modem_mbim) { |
| g_autoptr(GError) error = NULL; |
| std::string eid; |
| g_autoptr(MbimMessage) response = NULL; |
| response = mbim_device_command_finish(device, res, &error); |
| if (ParseEidApduResponse(response, &eid, modem_mbim)) { |
| VLOG(2) << "EID for physical slot:" |
| << modem_mbim->slot_info_.cached_active_slot_ << " is " << eid; |
| modem_mbim->slot_info_.SetSlotStateActiveSlot(MBIM_UICC_SLOT_STATE_UNKNOWN); |
| if (!eid.empty()) { |
| modem_mbim->slot_info_.SetEidActiveSlot(std::move(eid)); |
| modem_mbim->slot_info_.SetSlotStateActiveSlot( |
| MBIM_UICC_SLOT_STATE_ACTIVE_ESIM); |
| } |
| modem_mbim->ProcessMbimResult(kModemSuccess); |
| return; |
| } |
| LOG(ERROR) << "Could not read EID"; |
| // An EID read failure should usually abort any eSIM operation. In the special |
| // case where we encounter b/230851574, eid_read_failed_ is set. In such |
| // cases, do not abort the eSIM operation immediately. Instead, declare |
| // success, and expect any subsequent code to read the eid_read_failed_ flag |
| // and close any channels and reverse any slot switches. |
| if (modem_mbim->eid_read_failed_) { |
| modem_mbim->ProcessMbimResult(kModemSuccess); |
| return; |
| } |
| modem_mbim->ProcessMbimResult(kModemMessageProcessingError); |
| } |
| |
| /* static */ |
| void ModemMbim::UiccLowLevelAccessApduResponseParse(MbimDevice* device, |
| GAsyncResult* res, |
| ModemMbim* modem_mbim) { |
| g_autoptr(GError) error = NULL; |
| g_autoptr(MbimMessage) response = NULL; |
| guint32 status; |
| guint32 response_size = 0; |
| const guint8* out_response; |
| CHECK(modem_mbim->tx_queue_.size()); |
| // Ensure that the queued element is for a kSendApdu command |
| TxInfo* base_info = modem_mbim->tx_queue_[0].info_.get(); |
| CHECK(base_info); |
| static ResponseApdu payload; |
| ApduTxInfo* info = static_cast<ApduTxInfo*>(base_info); |
| response = mbim_device_command_finish(device, res, &error); |
| if (response && |
| mbim_message_response_get_result(response, MBIM_MESSAGE_TYPE_COMMAND_DONE, |
| &error) && |
| mbim_message_ms_uicc_low_level_access_apdu_response_parse( |
| response, &status, &response_size, &out_response, &error)) { |
| LOG(INFO) << "Adding to payload from APDU response (" << response_size |
| << " bytes)"; |
| VLOG(2) << "Payload: " << base::HexEncode(out_response, response_size) |
| << ", status: " << status; |
| |
| payload.AddData(out_response, response_size); |
| payload.AddStatusBytes((status >> 8) & 0xFF, status & 0xFF); |
| if (payload.MorePayloadIncoming()) { |
| // Make the next transmit operation be a request for more APDU data |
| info->apdu_ = payload.CreateGetMoreCommand(/* is_extended_apdu */ false, |
| info->apdu_.cls_); |
| LOG(INFO) << "Requesting more APDUs..."; |
| modem_mbim->TransmitFromQueue(); |
| return; |
| } |
| if (info->apdu_.HasMoreFragments()) { |
| // Send next fragment of APDU |
| LOG(INFO) << "Sending next APDU fragment..."; |
| modem_mbim->TransmitFromQueue(); |
| return; |
| } |
| modem_mbim->responses_.push_back(std::move(payload)); |
| std::move(modem_mbim->tx_queue_[0].cb_).Run(lpa::card::EuiccCard::kNoError); |
| modem_mbim->tx_queue_.pop_front(); |
| modem_mbim->TransmitFromQueue(); |
| } else { |
| LOG(ERROR) << __func__ << ": Failed to parse APDU response"; |
| std::move(modem_mbim->tx_queue_[0].cb_) |
| .Run(lpa::card::EuiccCard::kSendApduError); |
| modem_mbim->tx_queue_.pop_front(); |
| modem_mbim->TransmitFromQueue(); |
| return; |
| } |
| } |
| |
| void ModemMbim::DeviceSlotStatusInfoRspCb(MbimDevice* device, |
| GAsyncResult* res, |
| ModemMbim* modem_mbim) { |
| g_autoptr(MbimMessage) response = NULL; |
| g_autoptr(GError) error = NULL; |
| guint32 slot_index; |
| MbimUiccSlotState slot_status; |
| response = mbim_device_command_finish(device, res, &error); |
| if (response && |
| mbim_message_response_get_result(response, MBIM_MESSAGE_TYPE_COMMAND_DONE, |
| &error) && |
| mbim_message_ms_basic_connect_extensions_slot_info_status_response_parse( |
| response, &slot_index, &slot_status, &error)) { |
| modem_mbim->slot_info_.slot_state_[slot_index] = slot_status; |
| VLOG(2) << "Response received with slot_index:" << slot_index |
| << " & status:" << slot_status; |
| modem_mbim->ProcessMbimResult(kModemSuccess); |
| return; |
| } |
| LOG(ERROR) << __func__ << ":" << error->message; |
| modem_mbim->ProcessMbimResult(kModemMessageProcessingError); |
| } |
| |
| void ModemMbim::QuerySysCapsReady(MbimDevice* device, |
| GAsyncResult* res, |
| ModemMbim* modem_mbim) { |
| g_autoptr(MbimMessage) response = NULL; |
| g_autoptr(GError) error = NULL; |
| guint32 number_executors; |
| guint32 number_slots; |
| guint32 concurrency; |
| guint64 modem_id; |
| LOG(INFO) << __func__; |
| response = mbim_device_command_finish(device, res, &error); |
| if (!response || |
| !mbim_message_response_get_result( |
| response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) || |
| !mbim_message_ms_basic_connect_extensions_sys_caps_response_parse( |
| response, &number_executors, &number_slots, &concurrency, &modem_id, |
| &error)) { |
| modem_mbim->ProcessMbimResult(kModemMessageProcessingError); |
| return; |
| } |
| VLOG(2) << "executor index:" << number_executors |
| << " Number of slots:" << number_slots; |
| modem_mbim->slot_info_.InitSlotInfo(number_slots, number_executors); |
| modem_mbim->ProcessMbimResult(kModemSuccess); |
| } |
| |
| void ModemMbim::DeviceSlotStatusMappingRspCb(MbimDevice* device, |
| GAsyncResult* res, |
| ModemMbim* modem_mbim) { |
| g_autoptr(MbimMessage) response = NULL; |
| g_autoptr(GError) error = NULL; |
| guint32 map_count = 0; |
| g_autoptr(MbimSlotArray) slot_mappings = NULL; |
| response = mbim_device_command_finish(device, res, &error); |
| if (response && |
| mbim_message_response_get_result(response, MBIM_MESSAGE_TYPE_COMMAND_DONE, |
| &error) && |
| mbim_message_ms_basic_connect_extensions_device_slot_mappings_response_parse( |
| response, &map_count, &slot_mappings, &error)) { |
| CHECK_EQ(map_count, 1) << "Unexpected multi radio modem"; |
| modem_mbim->slot_info_.map_count_ = map_count; |
| modem_mbim->slot_info_.cached_active_slot_ = |
| slot_mappings[kExecutorIndex]->slot; |
| modem_mbim->slot_info_.get_slot_mapping_result_ = |
| slot_mappings[kExecutorIndex]->slot; |
| VLOG(2) << "Map count:" << map_count |
| << "& current active slot:" << slot_mappings[kExecutorIndex]->slot; |
| modem_mbim->ProcessMbimResult(kModemSuccess); |
| return; |
| } |
| modem_mbim->ProcessMbimResult(kModemMessageProcessingError); |
| } |
| |
| void ModemMbim::SetDeviceSlotMappingsRspCb(MbimDevice* device, |
| GAsyncResult* res, |
| ModemMbim* modem_mbim) { |
| g_autoptr(MbimMessage) response = NULL; |
| g_autoptr(GError) error = NULL; |
| guint32 map_count = 0; |
| uint8_t physical_switch_slot = 1; |
| g_autoptr(MbimSlotArray) slot_mappings = NULL; |
| VLOG(2) << __func__; |
| auto switch_slot_tx_info = |
| dynamic_cast<SwitchSlotTxInfo*>(modem_mbim->tx_queue_[0].info_.get()); |
| physical_switch_slot = switch_slot_tx_info->physical_slot_; |
| response = mbim_device_command_finish(device, res, &error); |
| if (!response || |
| !mbim_message_response_get_result( |
| response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) || |
| !mbim_message_ms_basic_connect_extensions_device_slot_mappings_response_parse( |
| response, &map_count, &slot_mappings, &error)) { |
| LOG(ERROR) << "Sim slot switch to " << physical_switch_slot << " failed"; |
| modem_mbim->ProcessMbimResult(kModemMessageProcessingError); |
| return; |
| } |
| if (physical_switch_slot != slot_mappings[kExecutorIndex]->slot) { |
| LOG(ERROR) << "Sim slot switch to " << physical_switch_slot << " failed"; |
| // b/230851574 causes a slot switch to fail once inevitably. |
| if (modem_mbim->eid_read_failed_) { |
| LOG(INFO) << "Ignoring failed slot switch"; |
| modem_mbim->ProcessMbimResult(kModemSuccess); |
| return; |
| } |
| modem_mbim->ProcessMbimResult(kModemMessageProcessingError); |
| return; |
| } |
| modem_mbim->slot_info_.cached_active_slot_ = physical_switch_slot; |
| for (int i = 0; i < modem_mbim->slot_info_.slot_count_; i++) { |
| if (i != physical_switch_slot) { |
| modem_mbim->euicc_manager_->OnLogicalSlotUpdated( |
| modem_mbim->slot_info_.cached_active_slot_, std::nullopt); |
| continue; |
| } |
| modem_mbim->euicc_manager_->OnLogicalSlotUpdated( |
| modem_mbim->slot_info_.cached_active_slot_, kExecutorIndex); |
| } |
| LOG(INFO) << "Sim switch was successful to:" |
| << slot_mappings[kExecutorIndex]->slot; |
| modem_mbim->ProcessMbimResult(kModemSuccess); |
| } |
| |
| /* static */ |
| void ModemMbim::ClientIndicationCb(MbimDevice* device, |
| MbimMessage* notification, |
| ModemMbim* modem_mbim) { |
| MbimService service; |
| service = mbim_message_indicate_status_get_service(notification); |
| VLOG(2) << "Received notification for service:" |
| << mbim_service_get_string(service); |
| VLOG(2) << "Command received from the modem:" |
| << mbim_cid_get_printable( |
| service, mbim_message_indicate_status_get_cid(notification)); |
| switch (service) { |
| case MBIM_SERVICE_BASIC_CONNECT: |
| if (mbim_message_indicate_status_get_cid(notification) == |
| MBIM_CID_BASIC_CONNECT_SUBSCRIBER_READY_STATUS) { |
| modem_mbim->is_ready_state_valid = |
| GetReadyState(device, true /*is_notification*/, notification, |
| &modem_mbim->ready_state_); |
| LOG(INFO) << "Current sim status:" << modem_mbim->ready_state_; |
| if (modem_mbim->ready_state_ == |
| MBIM_SUBSCRIBER_READY_STATE_INITIALIZED) { |
| VLOG(2) << "Sim has one profile enabled"; |
| } else if (modem_mbim->ready_state_ == |
| MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED) { |
| VLOG(2) << "Sim not inserted"; |
| } |
| } |
| break; |
| default: |
| VLOG(2) << "Indication received is not handled"; |
| break; |
| } |
| return; |
| } |
| |
| void ModemMbim::CloseDevice() { |
| if (device_ && g_signal_handler_is_connected(device_.get(), indication_id_)) |
| g_signal_handler_disconnect(device_.get(), indication_id_); |
| device_.reset(); |
| } |
| |
| bool ModemMbim::State::Transition(ModemMbim::State::Value value) { |
| bool valid_transition; |
| switch (value) { |
| case kMbimUninitialized: |
| valid_transition = true; |
| break; |
| case kCloseChannel: |
| valid_transition = (value_ == kCheckSingleSim || value_ == kSlotInfo); |
| break; |
| case kMbimStarted: |
| valid_transition = |
| (value_ == kCloseChannel || value_ == kEidReadComplete); |
| break; |
| default: |
| // Most states can only transition from the previous state. |
| valid_transition = (value == value_ + 1); |
| } |
| if (valid_transition) { |
| LOG(INFO) << "Transitioning from state " << *this << " to state " |
| << State(value); |
| value_ = value; |
| } else { |
| LOG(ERROR) << "Cannot transition from state " << *this << " to state " |
| << State(value); |
| } |
| return valid_transition; |
| } |
| |
| std::ostream& operator<<(std::ostream& os, const ModemMbim::State state) { |
| switch (state.value_) { |
| case ModemMbim::State::kMbimUninitialized: |
| os << "Uninitialized"; |
| break; |
| case ModemMbim::State::kMbimInitializeStarted: |
| os << "InitializeStarted"; |
| break; |
| case ModemMbim::State::kReadImei: |
| os << "GetReadImei"; |
| break; |
| case ModemMbim::State::kGetSubscriberReadyState: |
| os << "GetSubscriberReadyState"; |
| break; |
| case ModemMbim::State::kCheckSingleSim: |
| os << "CheckSingleSim"; |
| break; |
| case ModemMbim::State::kSysQuery: |
| os << "GetSysQuery"; |
| break; |
| case ModemMbim::State::kDeviceSlotMapping: |
| os << "GetDeviceSlotMapping"; |
| break; |
| case ModemMbim::State::kSlotInfo: |
| os << "GetSlotInfo"; |
| break; |
| case ModemMbim::State::kCloseChannel: |
| os << "Close Channel"; |
| break; |
| case ModemMbim::State::kOpenChannel: |
| os << "Open Channel"; |
| break; |
| case ModemMbim::State::kReadEid: |
| os << "GetReadEid"; |
| break; |
| case ModemMbim::State::kEidReadComplete: |
| os << "kEidReadComplete"; |
| break; |
| case ModemMbim::State::kMbimStarted: |
| os << "MbimStarted"; |
| break; |
| } |
| return os; |
| } |
| |
| void ModemMbim::OnEuiccEventStart(const uint32_t physical_slot, |
| bool switch_slot_only, |
| EuiccEventStep step, |
| ResultCallback cb) { |
| LOG(INFO) << __func__ << ": slot=" << physical_slot |
| << " switch_slot_only=" << switch_slot_only |
| << " step=" << to_underlying(step); |
| switch (step) { |
| case EuiccEventStep::GET_MBIM_DEVICE: |
| CloseDevice(); |
| { |
| auto next_step = (switch_slot_only) ? EuiccEventStep::CLOSE_CHANNEL |
| : EuiccEventStep::GET_SLOT_MAPPING; |
| auto get_slot_mapping = base::BindOnce( |
| &ModemMbim::OnEuiccEventStart, weak_factory_.GetWeakPtr(), |
| physical_slot, switch_slot_only, next_step); |
| init_done_cb_ = base::BindOnce( |
| &RunNextStep, std::move(get_slot_mapping), std::move(cb)); |
| mbim_device_new(file_, /* cancellable */ NULL, |
| (GAsyncReadyCallback)MbimCreateNewDeviceCb, this); |
| } |
| break; |
| case EuiccEventStep::GET_SLOT_MAPPING: |
| if (!mbim_device_check_ms_mbimex_version(device_.get(), 2, 0)) { |
| OnEuiccEventStart(physical_slot, false /* switch_slot_only */ |
| , |
| EuiccEventStep::CLOSE_CHANNEL, std::move(cb)); |
| break; |
| } |
| SendMessage(MbimCmd::MbimType::kMbimDeviceSlotMapping, |
| std::make_unique<SwitchSlotTxInfo>(physical_slot), |
| std::move(cb), &ModemMbim::OnEuiccEventStart, physical_slot, |
| switch_slot_only, EuiccEventStep::GET_SLOT_INFO); |
| break; |
| case EuiccEventStep::GET_SLOT_INFO: { |
| SendMessage(MbimCmd::MbimType::kMbimSlotInfoStatus, |
| std::make_unique<SwitchSlotTxInfo>(physical_slot), |
| std::move(cb), &ModemMbim::OnEuiccEventStart, physical_slot, |
| switch_slot_only, EuiccEventStep::CLOSE_CHANNEL); |
| } break; |
| case EuiccEventStep::CLOSE_CHANNEL: { |
| // b/230851574 Channel must be closed before attempting a slot switch. |
| SendMessage(MbimCmd::MbimType::kMbimCloseChannel, |
| std::unique_ptr<TxInfo>(), std::move(cb), |
| &ModemMbim::OnEuiccEventStart, physical_slot, |
| switch_slot_only, EuiccEventStep::SET_SLOT_MAPPING); |
| } break; |
| case EuiccEventStep::SET_SLOT_MAPPING: { |
| auto next_step = (switch_slot_only) |
| ? EuiccEventStep::EUICC_EVENT_STEP_LAST |
| : EuiccEventStep::OPEN_CHANNEL; |
| // Bypass slot switch if we are already on the requested slot or if slot |
| // switch isn't supported |
| if (physical_slot == slot_info_.cached_active_slot_ || |
| !mbim_device_check_ms_mbimex_version(device_.get(), 2, 0)) { |
| OnEuiccEventStart(physical_slot, switch_slot_only, next_step, |
| std::move(cb)); |
| break; |
| } |
| SendMessage(MbimCmd::MbimType::kMbimSetDeviceSlotMapping, |
| std::make_unique<SwitchSlotTxInfo>(physical_slot), |
| std::move(cb), &ModemMbim::OnEuiccEventStart, physical_slot, |
| switch_slot_only, next_step); |
| } break; |
| case EuiccEventStep::OPEN_CHANNEL: |
| if (!slot_info_.IsEsimActive()) { |
| LOG(ERROR) << "Active slot does not have an eSIM"; |
| euicc_manager_->OnEuiccRemoved(slot_info_.cached_active_slot_ + 1); |
| std::move(cb).Run(kModemMessageProcessingError); |
| break; |
| } |
| SendMessage(MbimCmd::MbimType::kMbimOpenChannel, |
| std::make_unique<OpenChannelTxInfo>(AidIsdr()), std::move(cb), |
| &ModemMbim::OnEuiccEventStart, physical_slot, |
| switch_slot_only, EuiccEventStep::GET_EID); |
| break; |
| case EuiccEventStep::GET_EID: |
| SendMessage(MbimCmd::MbimType::kMbimSendEidApdu, |
| std::unique_ptr<TxInfo>(), std::move(cb), |
| &ModemMbim::OnEuiccEventStart, physical_slot, |
| switch_slot_only, EuiccEventStep::CHECK_EID); |
| break; |
| case EuiccEventStep::CHECK_EID: |
| if (eid_read_failed_) { |
| OnEidReadFailed(physical_slot, EidReadFailedStep::CLOSE_CHANNEL, |
| std::move(cb)); |
| break; |
| } |
| OnEuiccEventStart(physical_slot, switch_slot_only, |
| EuiccEventStep::EUICC_EVENT_STEP_LAST, std::move(cb)); |
| break; |
| case EuiccEventStep::EUICC_EVENT_STEP_LAST: |
| OnEuiccUpdated(); |
| std::move(cb).Run(kModemSuccess); |
| break; |
| } |
| } |
| |
| void ModemMbim::OnEidReadFailed(const uint32_t physical_slot, |
| EidReadFailedStep step, |
| ResultCallback cb) { |
| // b/230851574 An EID error may have occurred after a slot switch. |
| // Attempt to switch back to the original slot after closing the |
| // channel, while still reporting an error. |
| LOG(INFO) << __func__ << ": slot=" << physical_slot |
| << " step=" << to_underlying(step); |
| switch (step) { |
| case EidReadFailedStep::CLOSE_CHANNEL: |
| SendMessage(MbimCmd::MbimType::kMbimCloseChannel, |
| std::unique_ptr<TxInfo>(), std::move(cb), |
| &ModemMbim::OnEidReadFailed, physical_slot, |
| EidReadFailedStep::RESTORE_SLOT_ATTEMPT1); |
| break; |
| case EidReadFailedStep::RESTORE_SLOT_ATTEMPT1: |
| if (!slot_info_.get_slot_mapping_result_.has_value()) { |
| LOG(ERROR) << "Cannot restore slot after EID read failure"; |
| eid_read_failed_ = false; |
| std::move(cb).Run(kModemMessageProcessingError); |
| break; |
| } |
| // This attempt will most likely fail due to b/230851574 |
| SendMessage(MbimCmd::MbimType::kMbimSetDeviceSlotMapping, |
| std::make_unique<SwitchSlotTxInfo>( |
| slot_info_.get_slot_mapping_result_.value()), |
| std::move(cb), &ModemMbim::OnEidReadFailed, physical_slot, |
| EidReadFailedStep::RESTORE_SLOT_ATTEMPT2); |
| break; |
| case EidReadFailedStep::RESTORE_SLOT_ATTEMPT2: |
| SendMessage(MbimCmd::MbimType::kMbimSetDeviceSlotMapping, |
| std::make_unique<SwitchSlotTxInfo>( |
| slot_info_.get_slot_mapping_result_.value()), |
| std::move(cb), &ModemMbim::OnEidReadFailed, physical_slot, |
| EidReadFailedStep::STEP_LAST); |
| break; |
| case EidReadFailedStep::STEP_LAST: |
| eid_read_failed_ = false; |
| std::move(cb).Run(kModemMessageProcessingError); |
| break; |
| } |
| } |
| |
| void ModemMbim::ProcessEuiccEvent(EuiccEvent event, ResultCallback cb) { |
| LOG(INFO) << __func__ << ": " << event; |
| if (event.step == EuiccStep::START) { |
| auto on_euicc_event_start = base::BindOnce( |
| &ModemMbim::OnEuiccEventStart, weak_factory_.GetWeakPtr(), |
| event.slot - 1, false /* switch_slot_only */, |
| EuiccEventStep::GET_MBIM_DEVICE); |
| modem_manager_proxy_->WaitForModemAndInhibit(base::BindOnce( |
| &RunNextStep, std::move(on_euicc_event_start), std::move(cb))); |
| return; |
| } |
| if (event.step == EuiccStep::PENDING_NOTIFICATIONS) { |
| AcquireChannelAfterCardReady(event, std::move(cb)); |
| return; |
| } |
| if (event.step == EuiccStep::END) { |
| slot_info_.get_slot_mapping_result_.reset(); |
| auto close_device_and_uninhibit = base::BindOnce( |
| &ModemMbim::CloseDeviceAndUninhibit, weak_factory_.GetWeakPtr()); |
| // Close channel, followed by close device and uninhibit, and then execute |
| // cb |
| SendMessage(MbimCmd::MbimType::kMbimCloseChannel, std::unique_ptr<TxInfo>(), |
| std::move(cb), &ModemMbim::CloseDeviceAndUninhibit); |
| return; |
| } |
| } |
| |
| void ModemMbim::AcquireChannelAfterCardReady(EuiccEvent event, |
| ResultCallback cb) { |
| const guint MBIM_SUBSCRIBER_READY_STATE_NO_ESIM_PROFILE = 7; |
| if (!is_ready_state_valid || |
| !(ready_state_ == MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED || |
| ready_state_ == MBIM_SUBSCRIBER_READY_STATE_INITIALIZED || |
| ready_state_ == MBIM_SUBSCRIBER_READY_STATE_DEVICE_LOCKED || |
| ready_state_ == MBIM_SUBSCRIBER_READY_STATE_NO_ESIM_PROFILE)) { |
| if (retry_count_ > kMaxRetries) { |
| LOG(ERROR) << "Could not finish profile operation,ready_state_=" |
| << ready_state_ |
| << ", is_ready_state_valid=" << is_ready_state_valid; |
| std::move(cb).Run(kModemMessageProcessingError); |
| return; |
| } |
| retry_count_++; |
| executor_->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&ModemMbim::AcquireChannelAfterCardReady, |
| weak_factory_.GetWeakPtr(), std::move(event), |
| std::move(cb)), |
| kSimRefreshDelay); |
| return; |
| } |
| retry_count_ = 0; |
| ReacquireChannel(EuiccEventStep::OPEN_CHANNEL, AidIsdr(), std::move(cb)); |
| } |
| |
| void ModemMbim::CloseDeviceAndUninhibit(ResultCallback cb) { |
| CloseDevice(); |
| modem_manager_proxy_->ScheduleUninhibit(kUninhibitDelay); |
| std::move(cb).Run(kModemSuccess); |
| } |
| |
| void ModemMbim::RestoreActiveSlot(ResultCallback cb) { |
| LOG(INFO) << __func__; |
| if (!mbim_device_check_ms_mbimex_version(device_.get(), 2, 0)) { |
| std::move(cb).Run(kModemSuccess); |
| return; |
| } |
| if (!slot_info_.get_slot_mapping_result_.has_value()) { |
| LOG(ERROR) << "Could not find slot number to switch to"; |
| std::move(cb).Run(kModemMessageProcessingError); |
| return; |
| } |
| if (slot_info_.get_slot_mapping_result_ == slot_info_.cached_active_slot_) { |
| VLOG(2) << __func__ << "Already on the right slot"; |
| std::move(cb).Run(kModemSuccess); |
| return; |
| } |
| OnEuiccEventStart(slot_info_.get_slot_mapping_result_.value(), |
| true /* switch_slot_only */, |
| EuiccEventStep::GET_MBIM_DEVICE, std::move(cb)); |
| } |
| |
| bool ModemMbim::IsSimValidAfterEnable() { |
| VLOG(2) << __func__; |
| return false; |
| // The sim issues a proactive refresh after an enable. This |
| // function should return true immediately after the refresh completes, |
| // However, the LPA expects that this function does not read any |
| // other state variable. Thus, we simply return false until the LPA |
| // times out, and then finish the operation. This imposes a 15 sec penalty |
| // on every enable and 30 sec penalty on every disable. |
| // A workaround is to return true and complete the eSIM operation before the |
| // refresh. FinishProfileOp can gate the dbus response until the refresh is |
| // complete. However, this exposes UI issues. |
| } |
| |
| bool ModemMbim::IsSimValidAfterDisable() { |
| VLOG(2) << __func__; |
| return false; |
| } |
| |
| void ModemMbim::OpenConnection( |
| const std::vector<uint8_t>& aid, |
| base::OnceCallback<void(std::vector<uint8_t>)> cb) { |
| LOG(INFO) << __func__ << base::HexEncode(aid.data(), aid.size()); |
| ReacquireChannel(EuiccEventStep::CLOSE_CHANNEL, aid, |
| base::BindOnce(&ModemMbim::OpenConnectionResponse, |
| weak_factory_.GetWeakPtr(), std::move(cb))); |
| } |
| |
| void ModemMbim::TransmitApdu( |
| const std::vector<uint8_t>& apduCommand, |
| base::OnceCallback<void(std::vector<uint8_t>)> cb) { |
| LOG(INFO) << __func__ << ": APDU command=" |
| << base::HexEncode(apduCommand.data(), apduCommand.size()); |
| DCHECK(apduCommand.size() > 2) << "APDU does not have a header."; |
| CommandApdu apdu(apduCommand); |
| auto transmit_apdu_resp = |
| base::BindOnce(&ModemMbim::TransmitApduResponse, |
| weak_factory_.GetWeakPtr(), std::move(cb)); |
| tx_queue_.push_back({std::make_unique<ApduTxInfo>(std::move(apdu)), |
| AllocateId(), GetTagForSendApdu(), |
| std::move(transmit_apdu_resp)}); |
| TransmitFromQueue(); |
| } |
| |
| /* static */ |
| bool ModemMbim::ParseEidApduResponseForTesting(const MbimMessage* response, |
| std::string* eid) { |
| return ParseEidApduResponse(response, eid, nullptr); |
| } |
| |
| template <typename Func, typename... Args> |
| void ModemMbim::SendMessage(MbimCmd::MbimType type, |
| std::unique_ptr<TxInfo> tx_info, |
| ResultCallback cb, |
| Func&& next_step, |
| Args&&... args) { |
| // First, transmit an mbim message. If the message is successful, run |
| // next_step(args). |
| auto next_step_weak = |
| base::BindOnce(next_step, weak_factory_.GetWeakPtr(), args...); |
| auto run_next_step = |
| base::BindOnce(&ModemMbim::RunNextStepOrRetry, weak_factory_.GetWeakPtr(), |
| std::move(next_step_weak), std::move(cb)); |
| tx_queue_.push_back({std::move(tx_info), AllocateId(), |
| std::make_unique<MbimCmd>(type), |
| std::move(run_next_step)}); |
| TransmitFromQueue(); |
| } |
| |
| bool ModemMbim::SlotInfo::IsEuiccPresentOnAnySlot() const { |
| for (int i = 0; i < slot_count_; i++) { |
| if (IsEuicc(i)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool ModemMbim::SlotInfo::IsEuicc(int slot) const { |
| return (slot_state_[slot] == MBIM_UICC_SLOT_STATE_ACTIVE_ESIM) || |
| (slot_state_[slot] == MBIM_UICC_SLOT_STATE_ACTIVE_ESIM_NO_PROFILES); |
| } |
| |
| void ModemMbim::SlotInfo::InitSlotInfo(guint32 slot_count, guint32 map_count) { |
| slot_count_ = slot_count; |
| map_count_ = map_count; // number of executors/radios |
| slot_state_.clear(); |
| eid_.clear(); |
| for (int i = 0; i < slot_count_; i++) { |
| slot_state_.push_back(MBIM_UICC_SLOT_STATE_UNKNOWN); |
| eid_.emplace_back(); |
| } |
| } |
| |
| void ModemMbim::SlotInfo::SetEidActiveSlot(std::string eid) { |
| eid_[cached_active_slot_] = std::move(eid); |
| } |
| |
| void ModemMbim::SlotInfo::SetSlotStateActiveSlot(MbimUiccSlotState state) { |
| slot_state_[cached_active_slot_] = state; |
| } |
| |
| std::ostream& operator<<(std::ostream& os, const ModemMbim::SlotInfo& info) { |
| os << "map_count_: " << info.map_count_ |
| << " slot_count_: " << info.slot_count_ |
| << " cached_active_slot_: " << info.cached_active_slot_; |
| for (int i = 0; i < info.slot_count_; i++) { |
| os << " slot_state[" << i << "]: " << info.slot_state_[i] << " eid[" << i |
| << "]:" << info.eid_[i]; |
| } |
| return os; |
| } |
| |
| } // namespace hermes |