lpac/driver/apdu/gbinder_hidl.c
Robert Marko 3bde4a1d3d
driver(APDU): add QMI backend (#131)
* driver: pass EUICC APDU or HTTP struct to driver fini OP

Currently, the .fini driver OP can only be used with global variables, but
that will be an issue when we cant use global variables.

This will be used with direct QMI in order to free the memory used by the
driver private structure instead of global variables.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>

* driver: apdu: rename QRTR QMI helpers to QMI helpers

Almost all of the QRTR QMI helpers will be reused for direct QMI so,
lets rename the sources to indicate that.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>

* driver: apdu: extract common QMI code

Support for using QMI directly and not over QRTR would use a lot of the
same code as QMI over QRTR, so in order to prevent code duplication lets
extract that common code so it can be reused.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>

* driver: apdu: qmi-helpers: allow compiling without libqrtr

Direct QMI backend wont use any of the libqmi QRTR functionality so it
wont depend on libqrtr and thus we need to make sure QMI helpers still
compile without libqrtr in the system.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>

* driver: apdu: dont use global variables for QMI

Since we split out the common QMI code then we cannot be using global
variables since existing QRTR and the coming QMI drivers will clash
by trying to use the same global variable names with the common code.

So, lets move to passing a structure which is driver specific instead.

Since we are not using global variables anymore, we cannot be using atexit
anymore, so lets move that cleanup step to driver finish OP instead.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>

* driver: apdu: add QMI backend

Previously QMI over QRTR support was added, however that is only present in
modern Qualcomm modems and only when running in PCIe mode and its quite
common to still only use even the latest modems via USB.

In that case, they are all still controllable via QMI but it is directly
exposed as a character device in Linux so we can reuse most of the code
from QMI over QRTR support but drop the support for libqrtr to talk to
the modems.

We require the QMI device path to be passed via QMI_DEVICE env variable
for the backend to operate.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>

* docs: ENVVARS: document QMI backend

Document the new direct QMI backend ENV variables as well as make it
clear that UIM_SLOT is not a QMI QRTR only variable.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>

* driver: apdu: qmi-helpers: support opening QMI device in proxy mode

Currently, we are using QMI_DEVICE_OPEN_FLAGS_NONE to open the QMI device
and thus we are requesting exclusive access to the device while we need
to talk to it.

This will work fine as long as we are the only thing trying to use that
QMI device at the same time or the device was not already opened in proxy
mode.

This is an issue since ModemManager will open the device in proxy mode so
that qmicli or other applications can still talk to the same QMI device
but it will break lpac from trying to use QMI.

So, lets try and open the device in proxy mode, libqmi will the start the
qmi-proxy service automatically as its built and installed as part of it.

Signed-off-by: Robert Marko <robert.marko@sartura.hr>

---------

Signed-off-by: Robert Marko <robert.marko@sartura.hr>
2024-07-24 15:59:37 +08:00

327 lines
10 KiB
C

// vim: expandtab sw=4 ts=4:
#include "gbinder_hidl.h"
#include <euicc/euicc.h>
#include <euicc/hexutil.h>
#include <euicc/interface.h>
#include <gbinder.h>
#include <stdio.h>
#include <stdlib.h>
#define DEBUG (getenv("GBINDER_APDU_DEBUG") != NULL && strcmp("true", getenv("GBINDER_APDU_DEBUG")) == 0)
#define HIDL_SERVICE_DEVICE "/dev/hwbinder"
#define HIDL_SERVICE_IFACE "android.hardware.radio@1.0::IRadio"
#define HIDL_SERVICE_IFACE_CALLBACK "android.hardware.radio@1.0::IRadioResponse"
// ref: IRadio
#define HIDL_SERVICE_SET_RESPONSE_FUNCTIONS GBINDER_FIRST_CALL_TRANSACTION
#define HIDL_SERVICE_ICC_OPEN_LOGICAL_CHANNEL (GBINDER_FIRST_CALL_TRANSACTION + 105)
#define HIDL_SERVICE_ICC_CLOSE_LOGICAL_CHANNEL (GBINDER_FIRST_CALL_TRANSACTION + 106)
#define HIDL_SERVICE_ICC_TRANSMIT_APDU_LOGICAL_CHANNEL (GBINDER_FIRST_CALL_TRANSACTION + 107)
// ref: IRadioResponse
#define HIDL_SERVICE_ICC_OPEN_LOGICAL_CHANNEL_CALLBACK (GBINDER_FIRST_CALL_TRANSACTION + 104)
#define HIDL_SERVICE_ICC_CLOSE_LOGICAL_CHANNEL_CALLBACK (GBINDER_FIRST_CALL_TRANSACTION + 105)
#define HIDL_SERVICE_ICC_TRANSMIT_APDU_LOGICAL_CHANNEL_CALLBACK (GBINDER_FIRST_CALL_TRANSACTION + 106)
static int lastChannelId = -1;
struct radio_response_info {
int32_t type;
int32_t serial;
int32_t error;
};
struct icc_io_result {
int32_t sw1;
int32_t sw2;
GBinderHidlString simResponse;
};
struct sim_apdu {
int32_t sessionId;
int32_t cla;
int32_t instruction;
int32_t p1;
int32_t p2;
int32_t p3;
GBinderHidlString data;
};
static const GBinderWriterField sim_apdu_f[] = {
GBINDER_WRITER_FIELD_HIDL_STRING(struct sim_apdu, data),
GBINDER_WRITER_FIELD_END()
};
static const GBinderWriterType sim_apdu_t = {
GBINDER_WRITER_STRUCT_NAME_AND_SIZE(struct sim_apdu), sim_apdu_f
};
static GBinderServiceManager *sm;
// IRadioResponse
static GBinderLocalObject *response_callback;
// IRadio
static GBinderRemoteObject *remote;
static GBinderClient *client;
static GMainLoop *binder_loop;
static int lastIntResp = -1;
static int lastRadioErr = 0;
static struct icc_io_result lastIccIoResult = {0};
static GBinderLocalReply *radio_response_transact(
GBinderLocalObject *obj,
GBinderRemoteRequest *req,
guint code, guint flags, int *status, void *user_data)
{
GBinderReader reader;
gbinder_remote_request_init_reader(req, &reader);
const struct radio_response_info *resp =
gbinder_reader_read_hidl_struct(&reader, struct radio_response_info);
lastRadioErr = resp->error;
if (lastRadioErr != 0)
goto out;
switch (code) {
case HIDL_SERVICE_ICC_OPEN_LOGICAL_CHANNEL_CALLBACK:
gbinder_reader_read_int32(&reader, &lastIntResp);
break;
case HIDL_SERVICE_ICC_TRANSMIT_APDU_LOGICAL_CHANNEL_CALLBACK:
const struct icc_io_result *icc_io_res = gbinder_reader_read_hidl_struct(&reader, struct icc_io_result);
// We cannot rely on the *req pointer being valid after we return
lastIccIoResult.sw1 = icc_io_res->sw1;
lastIccIoResult.sw2 = icc_io_res->sw2;
lastIccIoResult.simResponse.data.str = strndup(icc_io_res->simResponse.data.str, icc_io_res->simResponse.len);
lastIccIoResult.simResponse.len = icc_io_res->simResponse.len;
lastIccIoResult.simResponse.owns_buffer = TRUE;
break;
}
out:
g_main_loop_quit(binder_loop);
return NULL;
}
static void cleanup_channel(int id)
{
GBinderLocalRequest *req = gbinder_client_new_request(client);
GBinderWriter writer;
gbinder_local_request_init_writer(req, &writer);
gbinder_writer_append_int32(&writer, 1000);
gbinder_writer_append_int32(&writer, id);
gbinder_client_transact_sync_oneway(client, HIDL_SERVICE_ICC_CLOSE_LOGICAL_CHANNEL, req);
gbinder_local_request_unref(req);
g_main_loop_run(binder_loop);
}
static void cleanup(void)
{
if (lastChannelId != -1) {
fprintf(stderr, "Cleaning up leaked APDU channel %d\n", lastChannelId);
cleanup_channel(lastChannelId);
lastChannelId = -1;
}
}
static void sighandler(int sig)
{
// This would trigger atexit() hooks
exit(0);
}
static int try_open_slot(int slotId, const uint8_t *aid, uint32_t aid_len)
{
// First, try to connect to the HIDL service for this slot
char fqname[255];
snprintf(fqname, 255, "%s/slot%d", HIDL_SERVICE_IFACE, slotId);
fprintf(stderr, "Attempting to connect to %s\n", fqname);
int status = 0;
sm = gbinder_servicemanager_new(HIDL_SERVICE_DEVICE);
remote = gbinder_remote_object_ref(
gbinder_servicemanager_get_service_sync(sm, fqname, &status));
client = gbinder_client_new(remote, HIDL_SERVICE_IFACE);
if (!client) {
fprintf(stderr, "Failed to connect to IRadio\n");
gbinder_client_unref(client);
gbinder_remote_object_unref(remote);
gbinder_servicemanager_unref(sm);
return -1;
}
response_callback = gbinder_servicemanager_new_local_object(
sm, HIDL_SERVICE_IFACE_CALLBACK, radio_response_transact, NULL);
GBinderLocalRequest *req = gbinder_client_new_request(client);
GBinderWriter writer;
gbinder_local_request_init_writer(req, &writer);
gbinder_writer_append_local_object(&writer, response_callback);
gbinder_writer_append_local_object(&writer, NULL);
gbinder_client_transact_sync_reply(client, HIDL_SERVICE_SET_RESPONSE_FUNCTIONS, req, &status);
gbinder_local_request_unref(req);
if (status < 0) {
fprintf(stderr, "Failed to call IRadio::setResponseFunctions");
return -1;
}
// Now, try to open the AID
uint8_t aid_hex[255];
euicc_hexutil_bin2hex(aid_hex, 255, aid, aid_len);
req = gbinder_client_new_request(client);
gbinder_local_request_init_writer(req, &writer);
gbinder_writer_append_int32(&writer, 1000);
gbinder_writer_append_hidl_string_copy(&writer, aid_hex);
gbinder_writer_append_int32(&writer, 0);
status = gbinder_client_transact_sync_oneway(client, HIDL_SERVICE_ICC_OPEN_LOGICAL_CHANNEL, req);
gbinder_local_request_unref(req);
if (status < 0) {
fprintf(stderr, "Failed to call IRadio::iccOpenLogicalChannel: %d\n", status);
return status;
}
g_main_loop_run(binder_loop);
if (lastRadioErr != 0) {
fprintf(stderr, "Failed to open APDU logical channel: %d\n", lastRadioErr);
return -lastRadioErr;
}
fprintf(stderr, "opened logical channel id: %d\n", lastIntResp);
return lastIntResp;
}
static int apdu_interface_connect(struct euicc_ctx *ctx)
{
return 0;
}
static void apdu_interface_disconnect(struct euicc_ctx *ctx)
{
cleanup();
}
static int apdu_interface_logic_channel_open(struct euicc_ctx *ctx, const uint8_t *aid, uint8_t aid_len)
{
// We only start to use gbinder connection here, because only now can we detect whether
// a given slot is a valid eSIM slot. This way we can automatically fall back in the case
// where a device has only one eSIM -- we don't want to force the user to choose in this case.
int res = try_open_slot(1, aid, aid_len);
if (res < 0)
res = try_open_slot(2, aid, aid_len);
if (res >= 0)
lastChannelId = res;
return res;
}
static void apdu_interface_logic_channel_close(struct euicc_ctx *ctx, uint8_t channel)
{
cleanup_channel(channel);
if (lastChannelId == channel)
lastChannelId = -1;
// Only do this cleanup here, because on exit these objects will be destroyed anyway
gbinder_client_unref(client);
gbinder_remote_object_unref(remote);
gbinder_servicemanager_unref(sm);
}
static int apdu_interface_transmit(struct euicc_ctx *ctx, uint8_t **rx, uint32_t *rx_len, const uint8_t *tx, uint32_t tx_len)
{
GBinderLocalRequest *req = gbinder_client_new_request(client);
GBinderWriter writer;
gbinder_local_request_init_writer(req, &writer);
gbinder_writer_append_int32(&writer, 1000);
uint8_t tx_hex[4096] = {0};
euicc_hexutil_bin2hex(tx_hex, 4096, &tx[5], tx_len - 5);
if (DEBUG)
fprintf(stderr, "APDU req: %s\n", tx_hex);
struct sim_apdu apdu = {
.sessionId = lastChannelId,
.cla = tx[0],
.instruction = tx[1],
.p1 = tx[2],
.p2 = tx[3],
.p3 = tx[4],
.data = {
.data = {
.str = (const char *) tx_hex
},
.len = strlen(tx_hex) + 1,
.owns_buffer = FALSE,
},
};
gbinder_writer_append_struct(&writer, &apdu, &sim_apdu_t, NULL);
int status = gbinder_client_transact_sync_oneway(client, HIDL_SERVICE_ICC_TRANSMIT_APDU_LOGICAL_CHANNEL, req);
gbinder_local_request_unref(req);
if (status < 0) {
fprintf(stderr, "Failed to call IRadio::iccTransmitApduLogicalChannel: %d\n", status);
return status;
}
g_main_loop_run(binder_loop);
if (lastRadioErr != 0) {
return -lastRadioErr;
}
if (DEBUG)
fprintf(stderr, "APDU resp: %d%d %d %s\n", lastIccIoResult.sw1, lastIccIoResult.sw2, lastIccIoResult.simResponse.len, lastIccIoResult.simResponse.data.str);
*rx_len = lastIccIoResult.simResponse.len / 2 + 2;
*rx = calloc(*rx_len, sizeof(uint8_t));
euicc_hexutil_hex2bin_r(*rx, *rx_len, lastIccIoResult.simResponse.data.str, lastIccIoResult.simResponse.len);
(*rx)[*rx_len - 2] = lastIccIoResult.sw1;
(*rx)[*rx_len - 1] = lastIccIoResult.sw2;
// see radio_response_transact -- this is our buffer.
free((void *) lastIccIoResult.simResponse.data.str);
return 0;
}
static int libapduinterface_init(struct euicc_apdu_interface *ifstruct)
{
ifstruct->connect = apdu_interface_connect;
ifstruct->disconnect = apdu_interface_disconnect;
ifstruct->logic_channel_open = apdu_interface_logic_channel_open;
ifstruct->logic_channel_close = apdu_interface_logic_channel_close;
ifstruct->transmit = apdu_interface_transmit;
// Install cleanup routine
atexit(cleanup);
signal(SIGINT, sighandler);
// The glib loop is detached from any client object, so create it here.
binder_loop = g_main_loop_new(NULL, FALSE);
return 0;
}
static int libapduinterface_main(int argc, char **argv)
{
return 0;
}
static void libapduinterface_fini(struct euicc_apdu_interface *ifstruct)
{
}
const struct euicc_driver driver_apdu_gbinder_hidl = {
.type = DRIVER_APDU,
.name = "gbinder_hidl",
.init = libapduinterface_init,
.main = libapduinterface_main,
.fini = libapduinterface_fini,
};