/* SPDX-License-Identifier: Apache-2.0 */ /** * @file mctp.c * * @brief Code file for MCTP transport library * * @details As per MCTP specification, all MCTP fields are Big Endian * * @copyright Copyright (C) 2024 Jackrabbit Founders LLC. All rights reserved. * * @date Jan 2024 * @author Barrett Edwards * */ /* INCLUDES ==================================================================*/ /* gettid() */ #define _GNU_SOURCE /* exit() */ //#include /* errno */ #include /* printf() */ #include /* free() */ #include /* memset() * memcpy() */ #include /* pthread_t * pthread_create() * pthread_join() * pthread_getthreadid_np() */ #include /* __u32 */ #include /* sem_t * sem_init() * sem_timedwait() */ #include /* autl_prnt_buf() */ #include #include #include #include "main.h" /* MACROS ====================================================================*/ //#define MCTP_VERBOSE #ifdef MCTP_VERBOSE #define INIT unsigned step = 0; #define ENTER if (m->verbose & MCTP_VERBOSE_THREADS) printf("%d:%s Enter\n", gettid(), __FUNCTION__); #define STEP step++; if (m->verbose & MCTP_VERBOSE_STEPS) printf("%d:%s STEP: %u\n", gettid(), __FUNCTION__, step); #define HEX32(k, i) if (m->verbose & MCTP_VERBOSE_STEPS) printf("%d:%s STEP: %u %s: 0x%x\n", gettid(), __FUNCTION__, step, k, i); #define INT32(k, i) if (m->verbose & MCTP_VERBOSE_STEPS) printf("%d:%s STEP: %u %s: %d\n", gettid(), __FUNCTION__, step, k, i); #define ERR32(k, i) if (m->verbose & MCTP_VERBOSE_ERROR) printf("%d:%s STEP: %u ERR: %s: %d\n", gettid(), __FUNCTION__, step, k, i); #define EXIT(rc) if (m->verbose & MCTP_VERBOSE_THREADS) printf("%d:%s Exit: %d\n", gettid(), __FUNCTION__,rc); #else #define INIT #define ENTER #define STEP #define HEX32(k, i) #define INT32(k, i) #define ERR32(k, i) #define EXIT(rc) #endif /* ENUMERATIONS ==============================================================*/ /* STRUCTS ===================================================================*/ /* GLOBAL VARIABLES ==========================================================*/ /** * String representation of MCTP Threads Run Mode (RM) */ const char *STR_MCRM[] = { "Server", // MCRM_SERVER = 0, "Client" // MCRM_CLIENT = 1 }; /* String representation of MCTP Message Type Codes (MT) * * See DSP0239 v1.9.0 Table 1. */ const char *STR_MCMT[] = { "CONTROL", // MCMT_CONTROL = 0x00, "PLDM", // MCMT_PLDM = 0x01, "NCSI", // MCMT_NCSI = 0x02, "ETHERNET", // MCMT_ETHERNET = 0x03, "NVMEMI", // MCMT_NVMEMI = 0x04, "SPDM", // MCMT_SPDM = 0x05, "SECURE", // MCMT_SECURE = 0x06, "CXLFMAPI", // MCMT_CXLFMAPI = 0x07, "CXLCCI ", // MCMT_CXLCCI = 0x08, "VDM_PCI", // MCMT_VDM_PCI = 0x7E, "VDM_IANA" // MCMT_VDM_IANA = 0x7F }; /* PROTOTYPES ================================================================*/ /* FUNCTIONS =================================================================*/ /** * Convenience function to fill MCTP Header fields * * @param mm struct mctp_msg* to fill * @param dest Destination EID * @param src Source EID * @param owner Bit indicating if SRC is the owner * @param tag Tag ID to track multiple outstanding commands */ void mctp_fill_msg_hdr( struct mctp_msg *mm, __u8 dest, __u8 src, __u8 owner, __u8 tag) { mm->dst = dest; mm->src = src; mm->owner = owner; mm->tag = tag; } /** * Free memory allocated by init function * * STEPS * 1: Verify input * 2: Close socket connection * 3: Destroy Mutexes * 4: Free queues * 5: Free mctp struct memory */ int mctp_free(struct mctp *m) { INIT struct mctp_version *head, *curr, *next; int rv; ENTER // Initialize variables rv = 1; STEP // 1: Verify input if (m == NULL) { errno = EINVAL; rv = -1; goto end; } STEP // 2: Close socket connection if (m->conn != m->sock) close(m->conn); close(m->sock); STEP // 3: Destroy Mutexes pthread_mutex_destroy(&m->mtx); pthread_cond_destroy(&m->cond); pthread_mutex_destroy(&m->tags_mtx); STEP // 4: Free queues pq_free(m->rpq); pq_free(m->rmq); pq_free(m->tpq); pq_free(m->tmq); pq_free(m->taq); pq_free(m->acq); pq_free(m->pkts); pq_free(m->msgs); pq_free(m->actions); STEP // 5 Free MCTP Versions array head = m->mctp_versions; while (head != NULL) { curr = head; head = head->next_type; while (curr != NULL) { next = curr->next_entry; free(curr); curr = next; } } STEP // 6: Free mctp struct memory free(m); rv = 0; end: EXIT(rv); return rv; } /** * Get the verbosity bit mask */ __u32 mctp_get_verbosity(struct mctp *m) { return m->verbose; } /** * Initialize an mctp object * * STEPS * 1: Allocate memory for mctp struct * 2: Initialize message handlers * 3: Initialize message_handler thread * 4: Initialize UUID * 5: Initialize mutex variables */ struct mctp *mctp_init() { struct mctp *m; // STEP 1: Allocate memory for mctp struct m = (struct mctp*) calloc (1, sizeof(struct mctp)); if (m == NULL) { errno = EFAULT; return NULL; } // STEP 2: Initialize message handlers m->handlers[MCMT_CONTROL] = mctp_ctrl_handler; // STEP 3: Initialize message_handler thread m->fn_sr = mctp_socket_reader; m->fn_pr = mctp_packet_reader; m->fn_mh = mctp_message_handler; m->fn_pw = mctp_packet_writer; m->fn_sw = mctp_socket_writer; m->fn_st = mctp_submission_thread; m->fn_ct = mctp_completion_thread; // STEP 4: Initialize UUID uuid_generate(m->uuid); memcpy(m->state.uuid, m->uuid, MCLN_UUID); // STEP 5: Initialize mutex variables pthread_mutex_init(&m->mtx, NULL); pthread_cond_init(&m->cond, NULL); pthread_mutex_init(&m->tags_mtx, NULL); // STEP 6: Initialize mctp_versions array m->mctp_versions = NULL; mctp_set_version(m, MCMT_BASE, 0xF1,0xF3,0xF1,0x00); mctp_set_version(m, MCMT_CONTROL, 0xF1,0xF3,0xF1,0x00); return m; } /** * Determine the number of packets needed for this MCTP Message * * @return the number of packets, 0 if error */ int mctp_pkt_count(struct mctp_msg *mm) { int rv; // Initialize variables rv = 0; switch (mm->type) { case MCMT_CONTROL: rv = 1; break; // All MCTP control messages are 1 packet long case MCMT_PLDM: case MCMT_NCSI: case MCMT_ETHERNET: case MCMT_NVMEMI: case MCMT_SPDM: case MCMT_SECURE: case MCMT_CXLFMAPI: case MCMT_CSE: case MCMT_CXLCCI: case MCMT_VDM_PCI: case MCMT_VDM_IANA: { // Compute the number of MCLN_BTU sized packets needed rv = mm->len / MCLN_BTU; if ((mm->len % MCLN_BTU) > 0 ) rv++; } break; default: break; } return rv; } /** * Print an MCTP Transport Header */ void mctp_prnt_hdr(struct mctp_hdr *mh) { if (mh == NULL) return; printf("MCTP Header:\n"); printf("Header version: 0x%x\n", mh->ver); printf("Destination EID: 0x%02x\n", mh->dest); printf("Source EID: 0x%02x\n", mh->src); printf("Start of Message: %d\n", mh->som); printf("End of Message: %d\n", mh->eom); printf("Packet Sequence #: 0x%x\n", mh->seq); printf("Tag Owner: %d\n", mh->owner); printf("Tag: 0x%x\n", mh->tag); } /** * Print MCTP Pkt */ void mctp_prnt_pkt(struct mctp_pkt *mp) { if (mp == NULL) return; // Print the MCTP Transport Header mctp_prnt_hdr(&mp->hdr); // Print the payload autl_prnt_buf(mp, sizeof(struct mctp_pkt), 4, 1); } /** * Print MCTP Packet Wrapper */ void mctp_prnt_pkt_wrapper(struct mctp_pkt_wrapper *pw) { if (pw == NULL) return; printf("MCTP Packet Wrapper:\n"); timespec_print(&pw->ts); printf("Next: %p\n", pw->next); mctp_prnt_pkt(&pw->pkt); } /* * Print an MCTP Message Type */ void mctp_prnt_type(struct mctp_type *mt) { printf("MCTP Type:\n"); printf("Integrity Check: %d\n", mt->IC); printf("Message Type: 0x%02x %s\n", mt->type, mcmt(mt->type)); } /** * Print the current MCTP Endpoint Configuration */ void mctp_prnt_state(struct mctp_state *ms) { char buf[37]; // Convert UUID into String for printing uuid_unparse(ms->uuid, buf); printf("MCTP State:\n"); printf("Endpoint ID: %02x\n", ms->eid); printf("Bus Owner EID: %02x\n", ms->bus_owner_eid); printf("Verbose Flags: %08x\n", ms->verbose); printf("UUID: %s\n", buf); } /** * Print MCTP Message */ void mctp_prnt_msg(struct mctp_msg *mm) { if (mm == NULL) return; printf("MCTP Message:\n"); printf("Destination EID: 0x%02x\n", mm->dst); printf("Source EID: 0x%02x\n", mm->src); printf("Type: 0x%02x - %s\n", mm->type, mcmt(mm->type)); printf("Tag Owner: %d\n", mm->owner); printf("Tag: %d\n", mm->tag); printf("Payload Len: %d\n", mm->len); printf("Payload:\n"); // Print the payload in bytes autl_prnt_buf(mm->payload, mm->len, 4, 1); } /** * Retire an MCTP action * * Checks in the mctp_msg and mctp_action to the central free pools * @param m struct mctp* * @param a struct mctp_action* */ void mctp_retire(struct mctp* m, struct mctp_action *a) { struct mctp_pkt_wrapper *pw, *next; // Check in msg if (a->req != NULL) pq_push(m->msgs, a->req); if (a->rsp != NULL) pq_push(m->msgs, a->rsp); if (a->pw != NULL) { pw = a->pw; do { next = pw->next; pw->next = NULL; pq_push(m->pkts, pw); pw = next; } while (pw != NULL); } // Clear action memset(a, 0, sizeof(struct mctp_action)); // Check in action pq_push(m->actions, a); } /** * Start the threads * * @return 0 on success * -1 on socket create failure (both) * -2 on socket bind failure (server) * -3 on socket connect failure (client) * 1 on pthread_create() failure (both) * 2 on connection_thread start failure (both) * * STEPS * 1: Store parameters in mctp object * 2: Create socket * 3: Set parameters for server socket * 4: Configure socket * 5: Start connection thread */ int mctp_run(struct mctp *m, int port, __u32 address, int mode, int use_threads, int dontblock) { INIT int rv; sem_t sem; struct timespec ts, delta; ENTER // Initialize variables rv = -1; delta.tv_sec = 1; delta.tv_nsec = 0; STEP // 1: Store parameters in mctp object m->port = port; m->mode = mode; m->use_threads = use_threads; m->wait = use_threads; STEP // 2: Create socket m->sock = socket(AF_INET, SOCK_STREAM, 0); if ( m->sock < 0 ) { ERR32("Could not create socket. rv:", m->sock); rv = -1; goto close; } STEP // 3: Set parameters for server socket memset( &m->sa_server, 0, sizeof(struct sockaddr_in)); m->sa_server.sin_family = AF_INET; m->sa_server.sin_port = htons(port); m->sa_server.sin_addr.s_addr = address; STEP // 4: Configure Socket if ( mode == MCRM_SERVER ) { // Bind to socket rv = bind(m->sock, (struct sockaddr *) &m->sa_server, sizeof(struct sockaddr_in)); if ( rv < 0 ) { ERR32("Could not bind socket. rv", rv); rv = -2; goto close; } // Listen on socket listen(m->sock,5); } else { // Connect to the server as a client rv = connect(m->sock, (struct sockaddr *) &m->sa_server, sizeof(struct sockaddr_in)); if ( rv < 0 ) { ERR32("Socket connect failed. rv:", rv); rv = -3; goto close; } m->conn = m->sock; } // Set struct mctp pointer in Connection Handler object m->ch.m = m; m->ch.dontblock = dontblock; m->ch.sem = NULL; STEP // 5: Start Connection Handler Thread // If the user specified dontblock, then start the connection handler thread function as a independent thread and return if (dontblock) { // Initialize sempahore sem_init(&sem, 0, 0); m->ch.sem = &sem; // Start thread rv = pthread_create( &m->pt_ch, NULL, mctp_connection_handler, (void*) &m->ch); if ( rv != 0 ) { ERR32("Could not create Connection Handler Thread", rv); rv = 1; goto close; } // Compute timeout to wait for semaphore timespec_get(&ts, CLOCK_MONOTONIC); timespec_add(&ts, &delta, &ts); // Pend on a semaphore until all the threads are running rv = sem_timedwait(&sem, &ts); sem_destroy(&sem); if (rv != 0) { ERR32("Threads failed to start", rv); rv = 2; goto close; } } else { mctp_connection_handler(&m->ch); } rv = 0; goto end; close: close(m->sock); end: EXIT(rv) return rv; } /** * Specify the function to call for a MCTP Message type */ void mctp_set_handler ( struct mctp *m, int type, int (*func)(struct mctp *m, struct mctp_action *ma)) { if (type < MCMT_MAX) m->handlers[type] = func; } /** * Set the function to be called as the message handler thread */ void mctp_set_mh(struct mctp *m, void *(*fn)(void*arg)) { m->fn_mh = fn; } /** * Set the verbosity bit mask */ void mctp_set_verbosity(struct mctp *m, __u32 level) { m->verbose = level; m->state.verbose = level; } /** * Method to request mctp threads to stop * * This is called by the thread functions to say they exited abnormally * This is called when a thread has experienced an error and needs to tell the main thread to stop all the other threads * Pend upon the mutex which will unlock when the main thread calls pthread_cond_wait() * When the lock is obtained, tell the main thread to stop all the threads by setting a bit, * then issue signal, then unlock, then exit */ void mctp_request_stop(struct mctp *m) { pthread_mutex_lock(&m->mtx); { m->stop_threads = 2; pthread_cond_signal(&m->cond); } pthread_mutex_unlock(&m->mtx); } /** * Instruct all threads to stop, wait, join * * This can only be called by an external thread, not by any of the child mctp threads * * @detail Pend upon the mutex which will unlock when the main thread calls pthread_cond_wait() * When the lock is obtained, tell the main thread to stop all the threads by setting a bit, * then issue signal, then unlock, then exit */ int mctp_stop(struct mctp *m) { pthread_mutex_lock(&m->mtx); { // If we get the mutex and the threads haven't started, // then the connection thread has pended on the accept() and won't // return, so cancel the thread if ( ( m->use_threads != 0 ) && ( m->all_threads_started == 0 ) ) { pthread_cancel(m->pt_ch); close(m->sock); } else { m->stop_threads = 1; pthread_cond_signal(&m->cond); } } pthread_mutex_unlock(&m->mtx); // Join with the connection handler thread before exiting function pthread_join(m->pt_ch, NULL); return 0; } /** * Submit an object for transmission * * @param m struct mctp* * @param type mctp message type * @param obj Pointer to serialized data buffer to send * @param len Length of object in bytes * @param retry Number of attempts to send the object. -1=forever, -2=default * @param user_data void* to a user data object to keep with the action until completion * @param fn_submitted Function to call when action is submitted to tmq * @param fn_completed Function to call when response to action is received * @param fn_failed Function to call when retry attempts have elapsed * @return struct mctp_action* of the action submitted. NULL on error and sets errno * * STEPS * 1: Validate inputs * 2. Prepare message * 3. Prepare action * 4. Submit action */ struct mctp_action *mctp_submit( struct mctp *m, int type, void *obj, size_t len, int retry, struct timespec *delta, void *user_data, void (*fn_submitted)(struct mctp *m, struct mctp_action *a), void (*fn_completed)(struct mctp *m, struct mctp_action *a), void (*fn_failed)(struct mctp *m, struct mctp_action *a) ) { INIT struct mctp_action *ma; struct mctp_msg *mm; sem_t sem; int rv; ENTER // Initialize varialbes rv = 1; ma = NULL; STEP // 1: Validate inputs if (m == NULL) goto end; if (obj == NULL) goto end; if (len == 0) goto end; STEP // 2. Prepare Message // Check out msg mm = pq_pop(m->msgs, 1); if (mm == NULL) goto end; // Fill out msg mm->owner = 1; mm->type = type; mm->len = len; memcpy(&mm->payload, obj, len); STEP // 3. Prepare Action // Check out action ma = pq_pop(m->actions, 1); if (ma == NULL) goto end; // Fill out action memset(ma, 0, sizeof(struct mctp_action)); ma->valid = 1; ma->req = mm; if (retry < -1) ma->max = MCTP_ACTION_DEFAULT_RETRY_NUM; else ma->max = retry; timespec_get(&ma->created, CLOCK_MONOTONIC); ma->user_data = user_data; ma->fn_submitted = fn_submitted; ma->fn_completed = fn_completed; ma->fn_failed = fn_failed; // Set mctp_action.sem to NULL if we are not going to pend on the sempahore if (delta == NULL) ma->sem = NULL; else { // Initialize Semaphore sem_init(&sem, 0, 0); ma->sem = &sem; } STEP // 4. Submit action rv = pq_push(m->taq, ma); if (rv != 0) { ma = NULL; errno = EBUSY; goto end; } STEP // 5: Pend on semaphore if (delta != NULL) { // Compute absolute timeout timespec_get(&ma->timeout, CLOCK_MONOTONIC); timespec_add(&ma->timeout, delta, &ma->timeout); // Pend on semaphore rv = sem_timedwait(&sem, &ma->timeout); // Clean up the one time use semaphore sem_destroy(&sem); // Check response. If the semaphore timedpout, then return NULL to caller if (rv != 0) ma = NULL; } end: EXIT(rv) return ma; } /* Functions to return a string representation of an object*/ const char *mcmt(unsigned u) { int rv; switch (u) { case MCMT_CONTROL: rv = 0; break; // 0x00 case MCMT_PLDM: rv = 1; break; // 0x01 case MCMT_NCSI: rv = 2; break; // 0x02 case MCMT_ETHERNET: rv = 3; break; // 0x03 case MCMT_NVMEMI: rv = 4; break; // 0x04 case MCMT_SPDM: rv = 5; break; // 0x05 case MCMT_SECURE: rv = 6; break; // 0x06 case MCMT_CXLFMAPI: rv = 7; break; // 0x07 case MCMT_CXLCCI: rv = 8; break; // 0x08 case MCMT_VDM_PCI: rv = 9; break; // 0x7E case MCMT_VDM_IANA: rv = 10; break; // 0x7F default: return NULL; } return STR_MCMT[rv]; } const char *mcrm(unsigned u) { if (u >= MCRM_MAX) return NULL; return STR_MCRM[u]; }