Skip to content

Commit

Permalink
fruity: Boost NCM performance with multi-transfers
Browse files Browse the repository at this point in the history
Co-authored-by: Håvard Sørbø <[email protected]>
  • Loading branch information
oleavr and hsorbo committed Dec 8, 2024
1 parent de54b7e commit a68dc36
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 47 deletions.
132 changes: 103 additions & 29 deletions src/fruity/ncm.vala
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ namespace Frida.Fruity {
private uint16 ndp_out_payload_remainder;
private uint16 ndp_out_alignment;
private uint16 ntb_out_max_datagrams;
private size_t max_in_transfers;
private size_t max_out_transfers;
private VirtualNetworkStack? _netstack;
private Gee.Queue<Bytes> pending_output = new Gee.ArrayQueue<Bytes> ();
private bool writing = false;
Expand All @@ -39,9 +41,11 @@ namespace Frida.Fruity {
private Cancellable io_cancellable = new Cancellable ();

private const uint16 TRANSFER_HEADER_SIZE = 4 + 2 + 2 + 2 + 2;
private const size_t MAX_TRANSFER_MEMORY = 60 * 1518;

private enum CdcRequest {
GET_NTB_PARAMETERS = 0x80,
SET_NTB_INPUT_SIZE = 0x86,
}

private const uint16 NTB_PARAMETERS_MIN_SIZE = 28;
Expand Down Expand Up @@ -93,24 +97,52 @@ namespace Frida.Fruity {
make_user_error_message (@"Unable to claim USB CDC-NCM interface ($(e.message))"));
}

var raw_ntb_params = yield device.control_transfer (
uint8 raw_ntb_params[NTB_PARAMETERS_MIN_SIZE];
var raw_ntb_params_size = yield device.control_transfer (
LibUSB.RequestRecipient.INTERFACE | LibUSB.RequestType.CLASS | LibUSB.EndpointDirection.IN,
CdcRequest.GET_NTB_PARAMETERS,
0,
config.ctrl_iface,
NTB_PARAMETERS_MIN_SIZE,
raw_ntb_params,
1000,
cancellable);
if (raw_ntb_params.get_size () < NTB_PARAMETERS_MIN_SIZE)
if (raw_ntb_params_size < NTB_PARAMETERS_MIN_SIZE)
throw new Error.PROTOCOL ("Truncated NTB parameters response");
var ntb_params = new Buffer (raw_ntb_params, LITTLE_ENDIAN);
ntb_in_max_size = ntb_params.read_uint32 (4);
ntb_out_max_size = ntb_params.read_uint32 (16);
var ntb_params = new Buffer (new Bytes (raw_ntb_params[:raw_ntb_params_size]), LITTLE_ENDIAN);
uint32 device_ntb_in_max_size = ntb_params.read_uint32 (4);
ntb_in_max_size = uint32.min (device_ntb_in_max_size, 16384);
ntb_out_max_size = uint32.min (ntb_params.read_uint32 (16), 16384);
ndp_out_divisor = ntb_params.read_uint16 (20);
ndp_out_payload_remainder = ntb_params.read_uint16 (22);
ndp_out_alignment = ntb_params.read_uint16 (24);
ntb_out_max_datagrams = ntb_params.read_uint16 (26);

if (ntb_in_max_size != device_ntb_in_max_size) {
var ntb_size_buf = new BufferBuilder (LITTLE_ENDIAN)
.append_uint32 (ntb_in_max_size)
.build ();
yield device.control_transfer (
LibUSB.RequestRecipient.INTERFACE | LibUSB.RequestType.CLASS | LibUSB.EndpointDirection.OUT,
CdcRequest.SET_NTB_INPUT_SIZE,
0,
config.ctrl_iface,
ntb_size_buf.get_data (),
1000,
cancellable);
}

var speed = device.raw_device.get_device_speed ();
if (speed >= LibUSB.Speed.SUPER) {
max_in_transfers = (5 * MAX_TRANSFER_MEMORY) / ntb_in_max_size;
max_out_transfers = (5 * MAX_TRANSFER_MEMORY) / ntb_out_max_size;
} else if (speed == LibUSB.Speed.HIGH) {
max_in_transfers = MAX_TRANSFER_MEMORY / ntb_in_max_size;
max_out_transfers = MAX_TRANSFER_MEMORY / ntb_out_max_size;
} else {
max_in_transfers = 4;
max_out_transfers = 4;
}

Usb.check (handle.set_interface_alt_setting (config.data_iface, config.data_altsetting),
"Failed to set USB interface alt setting");

Expand All @@ -128,20 +160,40 @@ namespace Frida.Fruity {
}

private async void process_incoming_datagrams () {
var data = new uint8[ntb_in_max_size];

var pending = new Gee.ArrayQueue<Promise<Bytes>> ();
while (true) {
for (uint i = pending.size; i != max_in_transfers; i++) {
var request = transfer_next_input_batch ();
pending.offer (request);
}

try {
size_t n = yield device.bulk_transfer (config.rx_address, data, uint.MAX, io_cancellable);
handle_ncm_frame (data[:n]);
var frame = yield pending.poll ().future.wait_async (io_cancellable);
handle_ncm_frame (frame);
} catch (GLib.Error e) {
return;
}
}
}

private void handle_ncm_frame (uint8[] data) throws GLib.Error {
var input = new DataInputStream (new MemoryInputStream.from_data (data));
private Promise<Bytes> transfer_next_input_batch () {
var request = new Promise<Bytes> ();
do_transfer_input_batch.begin (request);
return request;
}

private async void do_transfer_input_batch (Promise<Bytes> request) {
var data = new uint8[ntb_in_max_size];
try {
size_t n = yield device.bulk_transfer (config.rx_address, data, uint.MAX, io_cancellable);
request.resolve (new Bytes (data[:n]));
} catch (GLib.Error e) {
request.reject (e);
}
}

private void handle_ncm_frame (Bytes frame) throws GLib.Error {
var input = new DataInputStream (new MemoryInputStream.from_bytes (frame));
input.byte_order = LITTLE_ENDIAN;

uint8 raw_signature[4 + 1];
Expand Down Expand Up @@ -204,28 +256,50 @@ namespace Frida.Fruity {
}

private async void process_pending_output () {
while (!pending_output.is_empty) {
size_t num_datagrams = ntb_out_max_datagrams;
TransferLayout layout;
while ((layout = TransferLayout.compute (pending_output, num_datagrams, ndp_out_alignment,
ndp_out_divisor, ndp_out_payload_remainder)).size > ntb_out_max_size) {
num_datagrams--;
}

var batch = new Gee.ArrayList<Bytes> ();
for (var i = 0; i != layout.offsets.size; i++)
batch.add (pending_output.poll ());
try {
while (!pending_output.is_empty) {
var pending = new Gee.ArrayList<Promise<uint>> ();

var transfer = build_output_transfer (batch, layout, next_outgoing_sequence++);
do {
var request = transfer_next_output_batch ();
pending.add (request);
} while (pending.size < max_out_transfers && !pending_output.is_empty);

try {
yield device.bulk_transfer (config.tx_address, transfer.get_data (), uint.MAX, io_cancellable);
} catch (GLib.Error e) {
break;
foreach (var request in pending)
yield request.future.wait_async (io_cancellable);
}
} catch (GLib.Error e) {
} finally {
writing = false;
}
}

writing = false;
private Promise<uint> transfer_next_output_batch () {
size_t num_datagrams = ntb_out_max_datagrams;
TransferLayout layout;
while ((layout = TransferLayout.compute (pending_output, num_datagrams, ndp_out_alignment,
ndp_out_divisor, ndp_out_payload_remainder)).size > ntb_out_max_size) {
num_datagrams--;
}

var batch = new Gee.ArrayList<Bytes> ();
for (var i = 0; i != layout.offsets.size; i++)
batch.add (pending_output.poll ());

var transfer = build_output_transfer (batch, layout, next_outgoing_sequence++);

var request = new Promise<uint> ();
do_transfer_output_batch.begin (transfer, request);
return request;
}

private async void do_transfer_output_batch (Bytes transfer, Promise<uint> request) {
try {
var size = yield device.bulk_transfer (config.tx_address, transfer.get_data (), uint.MAX, io_cancellable);
request.resolve ((uint) size);
} catch (GLib.Error e) {
request.reject (e);
}
}

private class TransferLayout {
Expand Down
47 changes: 29 additions & 18 deletions src/fruity/usb.vala
Original file line number Diff line number Diff line change
Expand Up @@ -72,36 +72,38 @@ namespace Frida.Fruity {
}

public async bool maybe_modeswitch (Cancellable? cancellable) throws Error, IOError {
var response = yield control_transfer (
uint8 current_mode[4];
var n = yield control_transfer (
LibUSB.RequestRecipient.DEVICE | LibUSB.RequestType.VENDOR | LibUSB.EndpointDirection.IN,
AppleSpecificRequest.GET_MODE,
0,
0,
4,
current_mode,
1000,
cancellable);
string mode = parse_mode (response);
string mode = parse_mode (current_mode[:n]);
bool is_initial_mode = mode == MODE_INITIAL_UNTETHERED || mode == MODE_INITIAL_TETHERED;
if (!is_initial_mode)
return false;

response = yield control_transfer (
uint8 set_mode_result[1];
var set_mode_result_size = yield control_transfer (
LibUSB.RequestRecipient.DEVICE | LibUSB.RequestType.VENDOR | LibUSB.EndpointDirection.IN,
AppleSpecificRequest.SET_MODE,
0,
3,
1,
set_mode_result,
1000,
cancellable);
if (response.get_size () != 1 || response[0] != 0x00)
if (set_mode_result_size != 1 || set_mode_result[0] != 0x00)
return false;

return true;
}

private static string parse_mode (Bytes mode) throws Error {
private static string parse_mode (uint8[] mode) throws Error {
var result = new StringBuilder.sized (7);
foreach (uint8 byte in mode.get_data ()) {
foreach (uint8 byte in mode) {
if (result.len != 0)
result.append_c (':');
result.append_printf ("%u", byte);
Expand Down Expand Up @@ -147,16 +149,17 @@ namespace Frida.Fruity {

public async Bytes read_string_descriptor_bytes (uint8 index, uint16 language_id, Cancellable? cancellable)
throws Error, IOError {
var response = yield control_transfer (
uint8 response[1024];
var response_size = yield control_transfer (
LibUSB.RequestRecipient.DEVICE | LibUSB.RequestType.STANDARD | LibUSB.EndpointDirection.IN,
LibUSB.StandardRequest.GET_DESCRIPTOR,
(LibUSB.DescriptorType.STRING << 8) | index,
language_id,
1024,
response,
1000,
cancellable);
try {
var input = new DataInputStream (new MemoryInputStream.from_bytes (response));
var input = new DataInputStream (new MemoryInputStream.from_data (response[:response_size]));
input.byte_order = LITTLE_ENDIAN;

uint8 length = input.read_byte ();
Expand All @@ -167,26 +170,29 @@ namespace Frida.Fruity {
if (type != LibUSB.DescriptorType.STRING)
throw new Error.PROTOCOL ("Invalid string descriptor type");

size_t remainder = response.get_size () - 2;
size_t remainder = response_size - 2;
length -= 2;
if (length > remainder)
throw new Error.PROTOCOL ("Invalid string descriptor length");

return response[2:2 + length];
return new Bytes (response[2:2 + length]);
} catch (GLib.Error e) {
throw new Error.PROTOCOL ("%s", e.message);
}
}

public async Bytes control_transfer (uint8 request_type, uint8 request, uint16 val, uint16 index, uint16 length,
public async size_t control_transfer (uint8 request_type, uint8 request, uint16 val, uint16 index, uint8[] buffer,
uint timeout, Cancellable? cancellable) throws Error, IOError {
var op = backend.allocate_usb_operation ();
unowned LibUSB.Transfer transfer = op.transfer;
var ready_closure = new TransferReadyClosure (control_transfer.callback);

var buffer = new uint8[sizeof (LibUSB.ControlSetup) + length];
LibUSB.Transfer.fill_control_setup (buffer, request_type, request, val, index, length);
transfer.fill_control_transfer (_handle, buffer, on_transfer_ready, ready_closure, timeout);
size_t control_setup_size = 8;
var transfer_buffer = new uint8[control_setup_size + buffer.length];
LibUSB.Transfer.fill_control_setup (transfer_buffer, request_type, request, val, index, (uint16) buffer.length);
if ((request_type & LibUSB.EndpointDirection.IN) == 0)
Memory.copy ((uint8 *) transfer_buffer + control_setup_size, buffer, buffer.length);
transfer.fill_control_transfer (_handle, transfer_buffer, on_transfer_ready, ready_closure, timeout);

var cancel_source = new CancellableSource (cancellable);
cancel_source.set_callback (() => {
Expand All @@ -206,7 +212,12 @@ namespace Frida.Fruity {

Usb.check_transfer (transfer.status, "Control transfer failed");

return new Bytes (((uint8[]) transfer.control_get_data ())[:transfer.actual_length]);
var n = transfer.actual_length;

if ((request_type & LibUSB.EndpointDirection.IN) != 0)
Memory.copy (buffer, transfer.control_get_data (), n);

return n;
}

public async size_t bulk_transfer (uint8 endpoint, uint8[] buffer, uint timeout, Cancellable? cancellable)
Expand Down

0 comments on commit a68dc36

Please sign in to comment.