blob: 7c78fa78ab57851459e6894a1328fb9908892e32 [file] [log] [blame]
// Copyright 2018 The Fuchsia 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 "fake-device.h"
#include <lib/async/cpp/task.h>
#include <lib/zx/channel.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <cstdio>
#include <future>
#include <ddktl/fidl.h>
namespace fidl_qmi_transport = fuchsia_hardware_telephony_transport;
namespace fidl_tel_snoop = fuchsia_telephony_snoop;
namespace qmi_fake {
constexpr uint8_t kQmiInitReq[] = {1, 15, 0, 0, 0, 0, 0, 1, 34, 0, 4, 0, 1, 1, 0, 2};
constexpr uint8_t kQmiImeiReq[] = {1, 12, 0, 0, 2, 1, 0, 1, 0, 37, 0, 0, 0};
constexpr uint8_t kQmiInitResp[] = {1, 23, 0, 128, 0, 0, 1, 1, 34, 0, 12, 0,
2, 4, 0, 0, 0, 0, 0, 1, 2, 0, 2, 1};
constexpr uint8_t kQmiImeiResp[] = {1, 41, 0, 128, 2, 1, 2, 1, 0, 37, 0, 29, 0, 2,
4, 0, 0, 0, 0, 0, 16, 1, 0, 48, 17, 15, 0, 51,
53, 57, 50, 54, 48, 48, 56, 48, 49, 54, 56, 51, 53, 49};
constexpr uint8_t kQmiPerioEvent[] = {1, 11, 0, 128, 0, 0, 2, 0, 39, 0, 0, 0};
constexpr uint8_t kQmiNonsenseResp[] = {1, 0};
constexpr uint32_t kTelCtrlPlanePktMax = 2048;
QmiDevice::QmiDevice(zx_device_t* device) : Device(device) {}
#define DEV(c) static_cast<QmiDevice*>(c)
static zx_protocol_device_t qmi_fake_device_ops = {
.version = DEVICE_OPS_VERSION,
.get_protocol = [](void* ctx, uint32_t proto_id, void* out_proto) -> zx_status_t {
return DEV(ctx)->GetProtocol(proto_id, out_proto);
},
.unbind = [](void* ctx) { DEV(ctx)->Unbind(); },
.release = [](void* ctx) { DEV(ctx)->Release(); },
.message = [](void* ctx, fidl_incoming_msg_t* msg, fidl_txn_t* txn) -> zx_status_t {
return DEV(ctx)->DdkMessage(msg, txn);
},
};
#undef DEV
static void sent_fake_qmi_msg(zx::channel& channel, uint8_t* resp, uint32_t resp_size) {
zx_status_t status;
status = channel.write(0, resp, resp_size, NULL, 0);
if (status < 0) {
zxlogf(ERROR, "qmi-fake-transport: failed to write message to channel: %s",
zx_status_get_string(status));
}
}
void QmiDevice::SnoopCtrlMsg(uint8_t* snoop_data, uint32_t snoop_data_len,
fidl_tel_snoop::wire::Direction direction) {
if (GetCtrlSnoopChannel()) {
fidl_tel_snoop::wire::QmiMessage qmi_msg;
uint32_t current_length =
std::min(static_cast<std::size_t>(snoop_data_len), sizeof(qmi_msg.opaque_bytes));
qmi_msg.is_partial_copy = snoop_data_len > current_length;
qmi_msg.direction = direction;
qmi_msg.timestamp = zx_clock_get_monotonic();
memcpy(qmi_msg.opaque_bytes.data_, snoop_data, current_length);
auto snoop_msg = fidl_tel_snoop::wire::Message::WithQmiMessage(
fidl::ObjectView<fidl_tel_snoop::wire::QmiMessage>::FromExternal(&qmi_msg));
zxlogf(INFO, "qmi-fake-transport: snoop msg %u %u %u %u sent", qmi_msg.opaque_bytes.data_[0],
qmi_msg.opaque_bytes.data_[1], qmi_msg.opaque_bytes.data_[2],
qmi_msg.opaque_bytes.data_[3]);
// TODO(fxbug.dev/97955) Consider handling the error instead of ignoring it.
(void)fidl::WireCall<fidl_tel_snoop::Publisher>(
zx::unowned_channel(GetCtrlSnoopChannel().get()))
->SendMessage(std::move(snoop_msg));
}
}
void QmiDevice::ReplyCtrlMsg(uint8_t* req, uint32_t req_size, uint8_t* resp, uint32_t resp_size) {
memset(resp, 170, resp_size);
if (0 == memcmp(req, kQmiInitReq, sizeof(kQmiInitReq))) {
memcpy(resp, kQmiPerioEvent,
std::min(sizeof(kQmiPerioEvent), static_cast<std::size_t>(resp_size)));
sent_fake_qmi_msg(GetCtrlChannel(), resp, resp_size);
SnoopCtrlMsg(resp, resp_size, fidl_tel_snoop::wire::Direction::kFromModem);
memcpy(resp, kQmiInitResp, std::min(sizeof(kQmiInitResp), static_cast<std::size_t>(resp_size)));
sent_fake_qmi_msg(GetCtrlChannel(), resp, resp_size);
SnoopCtrlMsg(resp, resp_size, fidl_tel_snoop::wire::Direction::kFromModem);
} else if (0 == memcmp(req, kQmiImeiReq, sizeof(kQmiImeiReq))) {
memcpy(resp, kQmiImeiResp, std::min(sizeof(kQmiImeiResp), static_cast<std::size_t>(resp_size)));
sent_fake_qmi_msg(GetCtrlChannel(), resp, resp_size);
SnoopCtrlMsg(resp, resp_size, fidl_tel_snoop::wire::Direction::kFromModem);
memcpy(resp, kQmiPerioEvent,
std::min(sizeof(kQmiPerioEvent), static_cast<std::size_t>(resp_size)));
sent_fake_qmi_msg(GetCtrlChannel(), resp, resp_size);
SnoopCtrlMsg(resp, resp_size, fidl_tel_snoop::wire::Direction::kFromModem);
} else {
zxlogf(ERROR, "qmi-fake-driver: unexpected qmi msg received");
memcpy(resp, kQmiNonsenseResp,
std::min(sizeof(kQmiNonsenseResp), static_cast<std::size_t>(resp_size)));
sent_fake_qmi_msg(GetCtrlChannel(), resp, resp_size);
SnoopCtrlMsg(resp, resp_size, fidl_tel_snoop::wire::Direction::kFromModem);
}
}
static int qmi_fake_transport_thread(void* cookie) {
assert(cookie != NULL);
QmiDevice* device_ptr = static_cast<QmiDevice*>(cookie);
uint32_t req_len = 0;
uint8_t req_buf[kTelCtrlPlanePktMax];
uint8_t resp_buf[kTelCtrlPlanePktMax];
zx_port_packet_t packet;
zxlogf(INFO, "qmi-fake-transport: event loop initialized");
while (true) {
zx_status_t status = device_ptr->GetCtrlChannelPort().wait(zx::time::infinite(), &packet);
if (status == ZX_ERR_TIMED_OUT) {
zxlogf(ERROR, "qmi-fake-transport: timed out: %s", zx_status_get_string(status));
} else if (status == ZX_OK) {
switch (packet.key) {
case tel_fake::kChannelMsg:
if (packet.signal.observed & ZX_CHANNEL_PEER_CLOSED) {
zxlogf(ERROR, "qmi-fake-transport: channel closed");
status = device_ptr->CloseCtrlChannel();
continue;
}
status = device_ptr->GetCtrlChannel().read(0, req_buf, NULL, kTelCtrlPlanePktMax, 0,
&req_len, NULL);
if (status != ZX_OK) {
zxlogf(ERROR, "qmi-fake-transport: failed to read channel: %s",
zx_status_get_string(status));
return status;
}
device_ptr->SnoopCtrlMsg(req_buf, kTelCtrlPlanePktMax,
fidl_tel_snoop::wire::Direction::kToModem);
// TODO (jiamingw): parse QMI msg, form reply and write back to channel.
device_ptr->ReplyCtrlMsg(req_buf, req_len, resp_buf, kTelCtrlPlanePktMax);
status = device_ptr->SetAsyncWait();
if (status != ZX_OK) {
return status;
}
break;
case tel_fake::kTerminateMsg:
device_ptr->EventLoopCleanup();
return 0;
default:
zxlogf(ERROR, "qmi-fake-transport: qmi_port undefined key %lu", packet.key);
assert(0);
}
} else {
zxlogf(ERROR, "qmi-fake-transport: qmi_port err %d", status);
assert(0);
}
}
return 0;
}
zx_status_t QmiDevice::Bind() {
// create a port to watch qmi messages
zx_status_t status = zx::port::create(0, &GetCtrlChannelPort());
if (status != ZX_OK) {
zxlogf(ERROR, "qmi-fake-transport: failed to create a port: %s", zx_status_get_string(status));
return status;
}
// create the handler thread
GetCtrlThrd() = std::thread(qmi_fake_transport_thread, this);
device_add_args_t args = {};
args.version = DEVICE_ADD_ARGS_VERSION;
args.name = "qmi-fake";
args.ctx = this;
args.ops = &qmi_fake_device_ops;
args.proto_id = ZX_PROTOCOL_QMI_TRANSPORT;
status = device_add(GetParentDevice(), &args, &GetTelDevPtr());
if (status != ZX_OK) {
zxlogf(ERROR, "qmi-fake-transport: could not add device: %d", status);
zx_port_packet_t packet = {};
packet.key = tel_fake::kTerminateMsg;
GetCtrlChannelPort().queue(&packet);
zxlogf(INFO, "qmi-fake-transport: joining thread");
GetCtrlThrd().join();
return status;
}
return status;
}
} // namespace qmi_fake