blob: 56dc012a5c4a02c7b21a7f7042714ea415836488 [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 <ddktl/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/fidl/llcpp/coding.h>
#include <lib/zx/channel.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <cstdio>
#include <future>
#include <thread>
#define QMI_INIT_REQ \
{ 1, 15, 0, 0, 0, 0, 0, 1, 34, 0, 4, 0, 1, 1, 0, 2 }
#define QMI_INIT_RESP \
{ 1, 23, 0, 128, 0, 0, 1, 1, 34, 0, 12, 0, 2, 4, 0, 0, 0, 0, 0, 1, 2, 0, 2, 1 }
#define QMI_IMEI_REQ \
{ 1, 12, 0, 0, 2, 1, 0, 1, 0, 37, 0, 0, 0 }
#define QMI_IMEI_RESP \
{ \
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 \
}
#define QMI_POWER_STA_REQ \
{ 1, 12, 0, 0, 2, 1, 0, 1, 0, 45, 0, 0, 0 }
#define QMI_PERIO_EVENT \
{ 1, 11, 0, 128, 0, 0, 2, 0, 39, 0, 0, 0 }
#define QMI_NONSENSE_RESP \
{ 1, 0 }
#define MIN(a, b) ((a) > (b) ? (b) : (a))
const uint8_t qmi_init_req[] = QMI_INIT_REQ;
const uint8_t qmi_imei_req[] = QMI_IMEI_REQ;
const uint8_t qmi_init_resp[] = QMI_INIT_RESP;
const uint8_t qmi_imei_resp[] = QMI_IMEI_RESP;
const uint8_t qmi_perio_event[] = QMI_PERIO_EVENT;
const uint8_t qmi_nonsense_resp[] = QMI_NONSENSE_RESP;
namespace fidl_qmi_transport = llcpp::fuchsia::hardware::telephony::transport;
namespace fidl_tel_snoop = ::llcpp::fuchsia::telephony::snoop;
namespace qmi_fake {
Device::Device(zx_device_t* device) : parent_(device) {}
#define DEV(c) static_cast<Device*>(c)
static zx_protocol_device_t qmi_fake_device_ops = {
.version = DEVICE_OPS_VERSION,
.unbind = [](void* ctx) { DEV(ctx)->Unbind(); },
.get_protocol = [](void* ctx, uint32_t proto_id, void* out_proto) -> zx_status_t {
return DEV(ctx)->GetProtocol(proto_id, out_proto);
},
.release = [](void* ctx) { DEV(ctx)->Release(); },
.message = [](void* ctx, fidl_msg_t* msg, fidl_txn_t* txn) -> zx_status_t {
return DEV(ctx)->DdkMessage(msg, txn);
},
};
#undef DEV
void Device::SetChannel(::zx::channel transport,
fidl_qmi_transport::Qmi::Interface::SetChannelCompleter::Sync completer) {
zx_status_t status = ZX_OK;
zx_status_t set_channel_res = SetChannelToDevice(transport.release());
fidl_qmi_transport::Qmi_SetChannel_Result result;
if (set_channel_res == ZX_OK) {
result.set_response(fidl_qmi_transport::Qmi_SetChannel_Response{});
} else {
result.set_err(set_channel_res);
}
completer.Reply(std::move(result));
if (status != ZX_OK) {
goto done;
}
if (set_channel_res == ZX_OK) {
status = SetAsyncWait();
if (status != ZX_OK) {
CloseQmiChannel();
}
}
done:
return;
}
void Device::SetNetwork(bool connected,
fidl_qmi_transport::Qmi::Interface::SetNetworkCompleter::Sync completer) {
SetNetworkStatusToDevice(connected);
completer.Reply();
}
void Device::SetSnoopChannel(
::zx::channel interface,
fidl_qmi_transport::Qmi::Interface::SetSnoopChannelCompleter::Sync completer) {
zx_status_t set_snoop_res = SetSnoopChannelToDevice(interface.release());
fidl_qmi_transport::Qmi_SetSnoopChannel_Result result;
if (set_snoop_res == ZX_OK) {
result.set_response(fidl_qmi_transport::Qmi_SetSnoopChannel_Response{});
} else {
result.set_err(set_snoop_res);
}
completer.Reply(std::move(result));
}
zx_status_t Device::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
DdkTransaction transaction(txn);
fidl_qmi_transport::Qmi::Dispatch(this, msg, &transaction);
return transaction.Status();
}
zx_status_t Device::SetSnoopChannelToDevice(zx_handle_t channel) {
zx_status_t result = ZX_OK;
zx_port_packet_t packet;
zx_status_t status;
// Initialize a port to watch whether the other handle of snoop channel has
// closed
if (snoop_channel_port_ == ZX_HANDLE_INVALID) {
status = zx_port_create(0, &snoop_channel_port_);
if (status != ZX_OK) {
zxlogf(ERROR,
"qmi-fake: failed to create a port to watch snoop channel: "
"%s\n",
zx_status_get_string(status));
return status;
}
} else {
status = zx_port_wait(snoop_channel_port_, 0, &packet);
if (status == ZX_ERR_TIMED_OUT) {
zxlogf(ERROR, "qmi-fake: timed out: %s\n", zx_status_get_string(status));
} else if (packet.signal.observed & ZX_CHANNEL_PEER_CLOSED) {
zxlogf(INFO, "qmi-fake: snoop channel peer closed\n");
snoop_channel_ = ZX_HANDLE_INVALID;
}
}
if (snoop_channel_ != ZX_HANDLE_INVALID) {
zxlogf(ERROR, "snoop channel already connected\n");
result = ZX_ERR_ALREADY_BOUND;
} else if (channel == ZX_HANDLE_INVALID) {
zxlogf(ERROR, "get invalid snoop channel handle\n");
result = ZX_ERR_BAD_HANDLE;
} else {
snoop_channel_ = channel;
zx_object_wait_async(snoop_channel_, snoop_channel_port_, 0, ZX_CHANNEL_PEER_CLOSED,
ZX_WAIT_ASYNC_ONCE);
}
return result;
}
zx_status_t Device::CloseQmiChannel() {
zx_status_t ret_val = ZX_OK;
if (qmi_channel_ != ZX_HANDLE_INVALID) {
ret_val = zx_handle_close(qmi_channel_);
qmi_channel_ = ZX_HANDLE_INVALID;
}
return ret_val;
}
zx_handle_t Device::GetQmiChannel() { return qmi_channel_; }
zx_status_t Device::SetAsyncWait() {
zx_status_t status =
zx_object_wait_async(qmi_channel_, qmi_channel_port_, CHANNEL_MSG,
ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, ZX_WAIT_ASYNC_ONCE);
return status;
}
static void sent_fake_qmi_msg(zx_handle_t channel, uint8_t* resp, uint32_t resp_size) {
zx_status_t status;
status = zx_channel_write(channel, 0, resp, resp_size, NULL, 0);
if (status < 0) {
zxlogf(ERROR, "qmi-fake-transport: failed to write message to channel: %s\n",
zx_status_get_string(status));
}
}
void Device::SnoopQmiMsg(uint8_t* snoop_data, uint32_t snoop_data_len,
fidl_tel_snoop::Direction direction) {
if (snoop_channel_) {
fidl_tel_snoop::Message snoop_msg;
fidl_tel_snoop::QmiMessage qmi_msg;
uint32_t current_length = 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);
snoop_msg.set_qmi_message(qmi_msg);
fidl_tel_snoop::Publisher::Call::SendMessage(zx::unowned_channel(snoop_channel_),
std::move(snoop_msg));
}
}
void Device::ReplyQmiMsg(uint8_t* req, uint32_t req_size, uint8_t* resp, uint32_t resp_size) {
memset(resp, 170, resp_size);
if (0 == memcmp(req, qmi_init_req, sizeof(qmi_init_req))) {
memcpy(resp, qmi_perio_event, MIN(sizeof(qmi_perio_event), resp_size));
SnoopQmiMsg(resp, resp_size, fidl_tel_snoop::Direction::FROM_MODEM);
sent_fake_qmi_msg(qmi_channel_, resp, resp_size);
memcpy(resp, qmi_init_resp, MIN(sizeof(qmi_init_resp), resp_size));
SnoopQmiMsg(resp, resp_size, fidl_tel_snoop::Direction::FROM_MODEM);
sent_fake_qmi_msg(qmi_channel_, resp, resp_size);
} else if (0 == memcmp(req, qmi_imei_req, sizeof(qmi_imei_req))) {
memcpy(resp, qmi_imei_resp, MIN(sizeof(qmi_imei_resp), resp_size));
SnoopQmiMsg(resp, resp_size, fidl_tel_snoop::Direction::FROM_MODEM);
sent_fake_qmi_msg(qmi_channel_, resp, resp_size);
memcpy(resp, qmi_perio_event, MIN(sizeof(qmi_perio_event), resp_size));
SnoopQmiMsg(resp, resp_size, fidl_tel_snoop::Direction::FROM_MODEM);
sent_fake_qmi_msg(qmi_channel_, resp, resp_size);
} else {
zxlogf(ERROR, "qmi-fake-driver: unexpected qmi msg received\n");
memcpy(resp, qmi_nonsense_resp, MIN(sizeof(qmi_nonsense_resp), resp_size));
SnoopQmiMsg(resp, resp_size, fidl_tel_snoop::Direction::FROM_MODEM);
sent_fake_qmi_msg(qmi_channel_, resp, resp_size);
}
}
static int qmi_fake_transport_thread(void* cookie) {
assert(cookie != NULL);
Device* device_ptr = static_cast<Device*>(cookie);
if (device_ptr->max_packet_size_ > 2048) {
zxlogf(ERROR, "qmi-fake-transport: packet too big: %d\n", device_ptr->max_packet_size_);
return ZX_ERR_IO_REFUSED;
}
uint8_t buffer[device_ptr->max_packet_size_];
uint8_t repl_buffer[device_ptr->max_packet_size_];
uint32_t length = sizeof(repl_buffer);
uint32_t length_read = 0;
zx_port_packet_t packet;
zxlogf(INFO, "qmi-fake-transport: event loop initialized\n");
while (true) {
zx_status_t status = zx_port_wait(device_ptr->qmi_channel_port_, ZX_TIME_INFINITE, &packet);
if (status == ZX_ERR_TIMED_OUT) {
zxlogf(ERROR, "qmi-fake-transport: timed out: %s\n", zx_status_get_string(status));
} else {
if (packet.key == CHANNEL_MSG) {
if (packet.signal.observed & ZX_CHANNEL_PEER_CLOSED) {
zxlogf(ERROR, "qmi-fake-transport: channel closed\n");
status = device_ptr->CloseQmiChannel();
continue;
}
status = zx_channel_read(device_ptr->GetQmiChannel(), 0, buffer, NULL, sizeof(buffer), 0,
&length_read, NULL);
if (status != ZX_OK) {
zxlogf(ERROR, "qmi-fake-transport: failed to read channel: %s\n",
zx_status_get_string(status));
return status;
}
device_ptr->SnoopQmiMsg(buffer, sizeof(buffer), fidl_tel_snoop::Direction::TO_MODEM);
// TODO (jiamingw): parse QMI msg, form reply and write back to channel.
device_ptr->ReplyQmiMsg(buffer, length_read, repl_buffer, length);
status = device_ptr->SetAsyncWait();
if (status != ZX_OK) {
return status;
}
}
}
}
return 0;
}
zx_status_t Device::Bind() {
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;
zx_status_t status = device_add(parent_, &args, &zxdev_);
if (status != ZX_OK) {
zxlogf(ERROR, "qmi-fake: could not add device: %d\n", status);
return status;
}
// create a port to watch qmi messages
if (qmi_channel_port_ == ZX_HANDLE_INVALID) {
status = zx_port_create(0, &qmi_channel_port_);
if (status != ZX_OK) {
zxlogf(ERROR, "qmi-fake-transport: failed to create a port: %s\n",
zx_status_get_string(status));
return status;
}
}
// create the handler thread
int thread_result = thrd_create_with_name(&fake_qmi_thread_, qmi_fake_transport_thread,
(void*)this, "qmi_fake_transport_thread");
if (thread_result != thrd_success) {
zxlogf(ERROR, "qmi-fake-transport: failed to create transport thread (%d)\n", thread_result);
status = ZX_ERR_INTERNAL;
}
// set max_packet_size_ to maximum for now
max_packet_size_ = 0x7FF;
return status;
}
void Device::Release() { delete this; }
void Device::Unbind() { device_remove(zxdev_); }
zx_status_t Device::GetProtocol(uint32_t proto_id, void* out_proto) {
if (proto_id != ZX_PROTOCOL_QMI_TRANSPORT) {
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}
zx_status_t Device::SetChannelToDevice(zx_handle_t transport) {
zx_status_t result = ZX_OK;
if (qmi_channel_ != ZX_HANDLE_INVALID) {
zxlogf(ERROR, "qmi-fake: already bound, failing\n");
result = ZX_ERR_ALREADY_BOUND;
} else if (transport == ZX_HANDLE_INVALID) {
zxlogf(ERROR, "qmi-fake: invalid channel handle\n");
result = ZX_ERR_BAD_HANDLE;
} else {
qmi_channel_ = transport;
}
return result;
}
zx_status_t Device::SetNetworkStatusToDevice(bool connected) {
connected_ = connected;
return ZX_OK;
}
} // namespace qmi_fake