/* * Copyright (C) 2013 Apple Inc. All rights reserved. * * This document is the property of Apple Inc. * It is considered confidential and proprietary. * * This document may not be reproduced or transmitted in any form, * in whole or in part, without the express written permission of * Apple Inc. */ #include #include #include #include #include #include #include #include "tbt_control_port.h" #include "tbt_cp_crc.h" #include "tbt_xdomain.h" #include "tbt_protocol.h" #define CPDBG_DUMP_PACKET DEBUG_NEVER #define CPDBG_INIT DEBUG_INFO #define CPDBG_IO DEBUG_SPEW #define CPDBG_EVENT DEBUG_SPEW struct xd_service { struct list_node list; // Callback function to handle incoming xdomain requests for this service tbt_xd_callback_t request_callback; // Callback function to handle incoming xdomain responses for this service tbt_xd_callback_t response_callback; // Private data passed in when the service was register void *priv; }; struct tbt_cp { nhi_t *nhi; uint8_t rx_buffer[256]; uint8_t tx_buffer[256]; uint8_t pdf_next_seq[TBT_CFG_MAX_PDF + 1]; bool shutdown; struct task *polling_task; struct list_node xd_services; uint32_t *read_req_buffer; uint32_t read_req_header; uint16_t read_req_quads; bool read_req_pending; struct task_event read_event; uint32_t write_req_header; bool write_req_pending; struct task_event write_event; }; static void tbt_cp_process_xd_request(tbt_cp_t *cp, uint8_t *packet, size_t bytes); static void tbt_cp_process_xd_response(tbt_cp_t *cp, uint8_t *packet, size_t bytes); static void tbt_cp_process_read_response(tbt_cp_t *cp, uint8_t *packet, size_t bytes); static void tbt_cp_process_write_response(tbt_cp_t *cp, uint8_t *packet, size_t bytes); static void tbt_cp_process_plug_event(tbt_cp_t *cp, uint8_t *packet, size_t bytes); static void tbt_cp_process_error(tbt_cp_t *cp, uint8_t *packet, size_t bytes); static int tbt_cp_polling_task(void *arg); static tbt_cp_t *g_tbt_cp; void tbt_cp_dump_route_string(uint64_t route_string) { if (route_string & (1ULL << 63)) printf("CM:"); printf("%02x", route_string & 0x3F); for (int i = 1; i < 7; i++) { if (((route_string >> (i * 8)) & 0x3F) == 0) break; printf(",%02x", (route_string >> (i * 8)) & 0x3F); } } uint64_t tbt_cp_packet_get_route_string(const uint8_t *packet) { uint64_t route_string = 0; for (int i = 0; i < 8; i++) route_string |= (uint64_t)packet[i] << ((8 - i - 1) * 8); return route_string; } void tbt_cp_packet_set_route_string(uint8_t *packet, uint64_t route_string) { for (int i = 0; i < 8; i++) packet[i] = route_string >> ((8 - i - 1) * 8); } void tbt_cp_dump_xdomain_message(uint8_t *packet, size_t bytes) { #if CPDBG_DUMP_PACKET <= DEBUG_LEVEL uint64_t route = tbt_cp_packet_get_route_string(packet); uint32_t header; uint32_t seq; uint32_t payload_len; char *len_message = ""; size_t offset; memcpy(&header, packet + 8, sizeof(uint32_t)); header = htonl(header); seq = (header >> 27) & 0x3; payload_len = header & 0x3f; if (payload_len * 4 > bytes - 12) len_message = " (INVALID)"; printf("Route String: "); tbt_cp_dump_route_string(route); printf("\n"); printf("Length: 0x%02x%s\n", payload_len, len_message); printf("Sequence Num: 0x%02x\n", seq); printf("Payload:\n"); if (payload_len * 4 > bytes - 12) payload_len = (bytes - 12) / 4; offset = 0; while (offset < payload_len * 4) { for (int i = 0; i < 16 && offset < payload_len * 4; i++, offset++) printf("%02x ", packet[offset + 12]); printf("\n"); } #endif } void tbt_cp_dump_message(uint8_t *packet, size_t bytes) { #if CPDBG_DUMP_PACKET <= DEBUG_LEVEL uint64_t route = tbt_cp_packet_get_route_string(packet); uint32_t value; printf("Route String: "); tbt_cp_dump_route_string(route); printf("\nPayload:\n"); for (size_t i = 0; i < bytes - 12; i += 4) { memcpy(&value, packet + 8 + i, 4); printf("%08x ", value); if (i % 16 == 12) printf("\n"); } if ((bytes - 12) % 16 != 0) printf("\n"); memcpy(&value, packet + bytes - 4, 4); printf("CRC: %08x\n", value); #endif } tbt_cp_t *tbt_cp_create(nhi_t *nhi) { tbt_cp_t *cp; cp = calloc(sizeof(*cp), 1); dprintf(CPDBG_INIT, "tbt_cp: initializing\n"); cp->nhi = nhi; cp->xd_services = LIST_INITIAL_VALUE(cp->xd_services); // Control messages are sent/received on ring 0 // 16 buffers should be plenty. The packets are 256 bytes each // These functions will panic if someone already took the ring nhi_init_tx_ring(nhi, 0, 16, 256); nhi_init_rx_ring(nhi, 0, 16, 256); event_init(&cp->read_event, EVENT_FLAG_AUTO_UNSIGNAL, false); event_init(&cp->write_event, EVENT_FLAG_AUTO_UNSIGNAL, false); // start a thread to handle packets for the control port cp->polling_task = task_create("tbt_polling", tbt_cp_polling_task, cp, 0); task_start(cp->polling_task); // Pointer used for debug command access to the control port, assumes only 1 exists g_tbt_cp = cp; return cp; } void tbt_cp_quiesce_and_free(tbt_cp_t *cp) { cp->shutdown = true; task_wait_on(cp->polling_task); task_destroy(cp->polling_task); cp->polling_task = NULL; nhi_disable_tx_ring(cp->nhi, 0); nhi_disable_rx_ring(cp->nhi, 0); free(cp); } void *tbt_cp_register_xd_service(tbt_cp_t *cp, tbt_xd_callback_t req_cb, tbt_xd_callback_t resp_cb, void *priv) { struct xd_service *new_service; ASSERT(req_cb != NULL); ASSERT(resp_cb != NULL); new_service = calloc(sizeof(*new_service), 1); new_service->request_callback = req_cb; new_service->response_callback = resp_cb; new_service->priv = priv; list_add_tail(&cp->xd_services, &new_service->list); return (void *)&new_service->list; } void tbt_cp_unregister_xd_service(tbt_cp_t *cp, void *handle) { struct list_node *item = (struct list_node *)handle; struct xd_service *service; if (!list_in_list(item)) panic("unregistering nonregistered xdomain service"); service = containerof(item, struct xd_service, list); list_delete(item); free(service); } bool tbt_cp_packet_available(tbt_cp_t *cp) { ASSERT(cp != NULL); return nhi_rx_buffer_available(cp->nhi, 0); } void tbt_cp_process_packet(tbt_cp_t *cp) { uint8_t eof_pdf; int32_t bytes; uint32_t packet_crc; uint32_t calc_crc; bytes = nhi_rx_buffer(cp->nhi, 0, cp->rx_buffer, sizeof(cp->rx_buffer), NULL, &eof_pdf); if (bytes < 0) { dprintf(DEBUG_CRITICAL, "tbt_cp: error %d receiving packet\n", bytes); return; } // Packet must be big enough for header + CRC if (bytes < TBT_CFG_HEADER_LEN + 4) { dprintf(DEBUG_CRITICAL, "tbt_cp: runt packet\n"); return; } calc_crc = tbt_cp_crc(cp->rx_buffer, bytes - 4); memcpy(&packet_crc, cp->rx_buffer + bytes - 4, 4); packet_crc = ntohl(packet_crc); if (calc_crc != packet_crc) { dprintf(DEBUG_CRITICAL, "tbt_cp: CRC mismatch %x v %x\n", calc_crc, packet_crc); return; } switch (eof_pdf) { case TBT_CFG_READ_PDF: tbt_cp_dump_message(cp->rx_buffer, bytes); tbt_cp_process_read_response(cp, cp->rx_buffer, bytes - 4); break; case TBT_CFG_WRITE_PDF: tbt_cp_dump_message(cp->rx_buffer, bytes); tbt_cp_process_write_response(cp, cp->rx_buffer, bytes - 4); break; case TBT_CFG_PLUG_EVENT_PDF: tbt_cp_process_plug_event(cp, cp->rx_buffer, bytes - 4); break; case TBT_CFG_ERROR_PDF: tbt_cp_dump_message(cp->rx_buffer, bytes); tbt_cp_process_error(cp, cp->rx_buffer, bytes - 4); break; case TBT_CFG_XDOMAIN_REQUEST_PDF: tbt_cp_dump_xdomain_message(cp->rx_buffer, bytes); tbt_cp_process_xd_request(cp, cp->rx_buffer, bytes - 4); break; case TBT_CFG_XDOMAIN_RESPONSE_PDF: tbt_cp_dump_xdomain_message(cp->rx_buffer, bytes); tbt_cp_process_xd_response(cp, cp->rx_buffer, bytes - 4); break; default: dprintf(DEBUG_INFO, "tbt_cp: got unexpected PDF 0x%02x\n", eof_pdf); tbt_cp_dump_message(cp->rx_buffer, bytes); break; } } // Control port packets have a 2-bit sequence number. The OS stack increments the // sequence number per PDF, so we'll do the same. This function gets the next // sequence number and increments a 2-bit counter to provide for the subsequent value uint8_t tbt_cp_next_pdf_seq(tbt_cp_t *cp, uint8_t pdf) { uint8_t next; ASSERT(cp != NULL); ASSERT(pdf <= TBT_CFG_MAX_PDF); next = cp->pdf_next_seq[pdf]; cp->pdf_next_seq[pdf] = (next + 1) & 3; return next; } // Queues the given packet to be sent bool tbt_cp_send(tbt_cp_t *cp, uint8_t pdf, uint8_t *packet, size_t len) { uint32_t crc; nhi_sgl_t sgl[2]; ASSERT(len <= TBT_CFG_MAX_HEADER_AND_PAYLOAD); crc = htonl(tbt_cp_crc(packet, len)); sgl[0].buffer = packet; sgl[0].bytes = len; sgl[0].next = &sgl[1]; sgl[1].buffer = &crc; sgl[1].bytes = sizeof(crc); sgl[1].next = NULL; return nhi_send_sgl(cp->nhi, 0, sgl, pdf, pdf) == NHI_STATUS_OK; } void tbt_cp_send_acknowledgement(tbt_cp_t *cp, uint64_t route_string, uint8_t port, uint8_t code) { uint32_t packet[3]; packet[0] = route_string >> 32; packet[1] = route_string; packet[2] = htonl((port << 8) | code); tbt_cp_send(cp, TBT_CFG_ERROR_PDF, (uint8_t *)packet, sizeof(packet)); } bool tbt_cp_read(tbt_cp_t *cp, uint64_t topology_id, uint8_t space, uint8_t port, uint16_t index, uint32_t *buffer, uint16_t quadlets) { bool ret; uint32_t request[3]; uint32_t header; uint8_t pdf = TBT_CFG_READ_PDF; uint8_t seq = tbt_cp_next_pdf_seq(cp, pdf); ASSERT(quadlets < 60); dprintf(CPDBG_IO, "tbt_cp: reading %d quads from %08llx %x:%02x:%04x\n", quadlets, topology_id, space, port, index); request[0] = htonl(topology_id >> 32); request[1] = htonl(topology_id & 0xffffffff); header = 0; header |= (index & 0x1fff) << 0; header |= (quadlets & 0x3f) << 13; header |= (port & 0x3f) << 19; header |= (space & 0x3) << 25; header |= seq << 27; request[2] = htonl(header); if (!tbt_cp_send(cp, pdf, (uint8_t *)request, sizeof(request))) return false; // If we ever switch to interrupts, this will need to be reconsidered cp->read_req_header = header; cp->read_req_buffer = buffer; cp->read_req_quads = quadlets; cp->read_req_pending = true; event_wait_timeout(&cp->read_event, 200000); // if the read request is no longer pending, then tbt_cp_process_read_response saw a good response ret = !cp->read_req_pending; cp->read_req_pending = false; if (ret) { for (unsigned i = 0; i < quadlets; i++) buffer[i] = ntohl(buffer[i]); } return ret; } bool tbt_cp_write(tbt_cp_t *cp, uint64_t topology_id, uint8_t space, uint8_t port, uint16_t index, const uint32_t *buffer, uint16_t quadlets) { uint8_t pdf = TBT_CFG_WRITE_PDF; uint8_t seq = tbt_cp_next_pdf_seq(cp, pdf); uint32_t header; uint32_t topo_high; uint32_t topo_low; uint32_t swapped; bool ret; dprintf(CPDBG_IO, "tbt_cp: write %d quads to %016llx %x:%02x:%04x\n", quadlets, topology_id, space, port, index); ASSERT(quadlets < 60); topo_high = htonl(topology_id >> 32); topo_low = htonl(topology_id & 0xffffffff); header = 0; header |= (index & 0x1fff) << 0; header |= (quadlets & 0x3f) << 13; header |= (port & 0x3f) << 19; header |= (space & 0x3) << 25; header |= seq << 27; header = htonl(header); memcpy(cp->tx_buffer + 0, &topo_high, 4); memcpy(cp->tx_buffer + 4, &topo_low, 4); memcpy(cp->tx_buffer + 8, &header, 4); for(uint16_t i = 0; i < quadlets; i++) { swapped = htonl(buffer[i]); memcpy(cp->tx_buffer + 12 + 4 * i, &swapped, 4); } if (!tbt_cp_send(cp, pdf, cp->tx_buffer, 12 + 4 * quadlets)) return false; // If we ever use interrupts, this will need to be reordered compared to the send call cp->write_req_header = ntohl(header); cp->write_req_pending = true; event_wait_timeout(&cp->write_event, 200000); // If write_req_pending is false, then tbt_cp_process_write_response saw a good response ret = !cp->write_req_pending; cp->write_req_pending = false; return true; } static void tbt_cp_process_read_response(tbt_cp_t *cp, uint8_t *packet, size_t bytes) { uint16_t len; uint32_t header; if (!cp->read_req_pending) { dprintf(DEBUG_CRITICAL, "tbt_cp: spurious read request response\n"); return; } memcpy(&header, packet + 8, 4); header = ntohl(header); len = (header >> 13) & 0x3f; dprintf(CPDBG_IO, "tbt_cp: read response header=0x%08x bytes=%u\n", header, (unsigned)bytes); // The far end is supposed to just copy the entire header back to us. They seem // to put garbage in the port number when we aren't reading port space, so // ignore it in the comparison if ((header & 0x1E07FFFF) != (cp->read_req_header & 0x1E07FFFF)) { dprintf(DEBUG_CRITICAL, "tbt_cp: read request header mismatch, got %x expected %x mismatch %x\n", header, cp->read_req_header, header ^ cp->read_req_header); return; } // Belt and suspenders check on the length, which is included in the header if (len != cp->read_req_quads) { dprintf(DEBUG_CRITICAL, "tbt_cp: read request len mismatch, got %u expected %u\n", len, cp->read_req_quads); return; } if (bytes != (len * 4) + 12) { dprintf(DEBUG_CRITICAL, "tbt_cp: read request payload len mismatch, got %u expected %u\n", bytes, (len * 4) + 16); return; } memcpy(cp->read_req_buffer, packet + 12, cp->read_req_quads * 4); cp->read_req_pending = false; event_signal(&cp->read_event); } static void tbt_cp_process_write_response(tbt_cp_t *cp, uint8_t *packet, size_t bytes) { uint32_t header; if (!cp->write_req_pending) { dprintf(DEBUG_CRITICAL, "tbt_cp: spurious write request response\n"); return; } memcpy(&header, packet + 8, 4); header = ntohl(header); dprintf(CPDBG_IO, "tbt_cp: write response header=0x%08x bytes=%u\n", header, (unsigned)bytes); // The far end is supposed to just copy the entire header back to us. We'll // mask off the reserved bits, but otherwise everything had better match if ((header & 0x1fffffff) != cp->write_req_header) { dprintf(DEBUG_CRITICAL, "tbt_cp: write request header mismatch, got %x expected %x\n", header, cp->read_req_header); return; } cp->write_req_pending = false; event_signal(&cp->write_event); } static void tbt_cp_process_plug_event(tbt_cp_t *cp, uint8_t *packet, size_t bytes) { uint64_t route_string; uint32_t details; uint8_t port; // Send the acknowledgement back to the sender, clearing the CM bit route_string = tbt_cp_packet_get_route_string(packet); route_string &= ~(1ULL << 63); memcpy(&details, packet + 8, sizeof(details)); details = ntohl(details); port = details & 0x3f; dprintf(CPDBG_EVENT, "tbt_cp: Got plug event from %016llx for port %x\n", route_string, port); tbt_cp_send_acknowledgement(cp, route_string, port, 7); } static void tbt_cp_process_error(tbt_cp_t *cp, uint8_t *packet, size_t bytes) { uint64_t route_string; // XXX dprintf(DEBUG_INFO, "Got an error packet. We should probably handle these?\n"); route_string = tbt_cp_packet_get_route_string(packet); } static void tbt_cp_process_xd_request(tbt_cp_t *cp, uint8_t *packet, size_t bytes) { struct xd_service *service; list_for_every_entry(&cp->xd_services, service, struct xd_service, list) { service->request_callback(cp, packet, bytes, service->priv); } } static void tbt_cp_process_xd_response(tbt_cp_t *cp, uint8_t *packet, size_t bytes) { struct xd_service *service; list_for_every_entry(&cp->xd_services, service, struct xd_service, list) { service->response_callback(cp, packet, bytes, service->priv); } } static int tbt_cp_polling_task(void *arg) { tbt_cp_t *cp = (tbt_cp_t *)arg; dprintf(CPDBG_INIT, "tbt_cp: polling task started\n"); while (!cp->shutdown) { while (tbt_cp_packet_available(cp)) { tbt_cp_process_packet(cp); } task_yield(); } dprintf(CPDBG_INIT, "tbt_cp: polling task finished\n"); return 0; } #if DEBUG_BUILD static int do_tbtcp_debug_cmd(int argc, struct cmd_arg *args) { int result = -1; const char *cmd = NULL; // Nothing to do if there's no control port if (g_tbt_cp == NULL) return -1; if (argc >= 2) cmd = args[1].str; if (cmd != NULL) { if (strcmp(cmd, "read") == 0 && argc >= 6) { uint64_t topo_id = args[2].n; uint8_t space = args[3].n; uint8_t port = args[4].n; uint16_t index = args[5].n; uint32_t buffer; if (tbt_cp_read(g_tbt_cp, topo_id, space, port, index, &buffer, 1)) { printf("0x%08x\n", buffer); result = 0; } else { printf("read failure\n"); } } else if (strcmp(cmd, "write") == 0 && argc >= 7) { uint64_t topo_id = args[2].n; uint8_t space = args[3].n; uint8_t port = args[4].n; uint16_t index = args[5].n; uint32_t buffer = args[6].n; if (tbt_cp_write(g_tbt_cp, topo_id, space, port, index, &buffer, 1)) { result = 0; } else { printf("write failure\n"); } } } return result; } #endif MENU_COMMAND_DEBUG(tbtcp, do_tbtcp_debug_cmd, "TBT control port debug commands", NULL);