/* * Copyright (C) 2010 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 // TI MSP430F2350 MCU Serial Packet Format // // 0 1 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +0 // | 'S' | 't' | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +2 // | OpCode | Seq No | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +4 // | Length High | Length Low | // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +6 // | | // | | // | | // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ // | CRC-16 High | CRC-16 Low | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // Sequence number is even for MCU->CPU packets and odd for CPU->MCU packets. // CRC is a CRC-16 CCITT (polynomial 0x1021, init value is 0xFFFF). typedef uint8_t MCUOpCode; // Commands requiring acks. #define kMCUOpCode_NoOp 0x00 #define kMCUOpCode_PowerOnReset 0x01 #define kMCUOpCode_GetVersion 0x02 // Response: <2:major>.<2:minor>[.<4:revision>] #define kMCUOpCode_SILControl 0x04 // Request: <1:action> #define kMCUSILAction_Invalid 0xFF #define kMCUSILAction_Off 0 #define kMCUSILAction_OffStr "off" #define kMCUSILAction_On 1 #define kMCUSILAction_OnStr "on" #define kMCUSILAction_SlowFlash 2 #define kMCUSILAction_SlowStr "slow" #define kMCUSILAction_FastFlash 3 #define kMCUSILAction_FastStr "fast" #define kMCUSILAction_1Blink 4 #define kMCUSILAction_1BlinkStr "1blink" #define kMCUSILAction_3Blinks 5 #define kMCUSILAction_3BlinksStr "3blinks" #define kMCUOpCode_PairControl 0x05 // Request: <1:0=unpair, 1=pair> <1:remote UID> #define kMCUPairAction_Unpair 0x00 #define kMCUPairAction_Pair 0x01 #define kMCUOpCode_BlueSteelControl 0x06 // Request: <1:mode> <1:subMode> #define kMCUBSControl_Invalid 0xff #define kMCUBSControl_Reset 0 #define kMCUBSControl_ResetStr "reset" #define kMCUBSControl_EnterTestMode 1 #define kMCUBSControl_EnterTestModeStr "testmode" #define kMCUBSControl_PassthroughMode 2 #define kMCUBSControl_PassthroughModeStr "passthru" #define kMCUBSControl_InfoFrameEnable 5 #define kMCUBSControl_InfoFrameEnableStr "info" #define kMCUOpCode_SystemState 0x07 // Request: <1:state> #define kMCUSystemState_NoChange 0x00 #define kMCUSystemState_Standby 0x01 #define kMCUSystemState_Normal 0x02 #define kMCUOpCode_AppleIRData 0x10 #define kMCUOpCode_Repeat 0x11 #define kMCUOpCode_3rdPartyIRData 0x12 // Acks to commands. #define kMCUOpCode_Ack 0x80 #define kMCUOpCode_NakUnknown 0x81 #define kMCUOpCode_NakUnsupported 0x82 #define kMCUOpCode_NakBadCRC 0x83 #define kMCUOpCode_NakNotNow 0x84 #define kMCUOpCode_NakMessageTooLong 0x85 #define kMCUOpCode_NakOther 0x9F #define MCUOpCode_IsAckOrNak( X ) \ ( ( (X) == kMCUOpCode_Ack ) || \ ( (X) == kMCUOpCode_NakUnknown ) || \ ( (X) == kMCUOpCode_NakUnsupported ) || \ ( (X) == kMCUOpCode_NakBadCRC ) || \ ( (X) == kMCUOpCode_NakNotNow ) || \ ( (X) == kMCUOpCode_NakMessageTooLong ) || \ ( (X) == kMCUOpCode_NakOther ) ) // No response necessary. #define kMCUOpCode_DebugData 0xFF // Set to 1 to print text contents of MCU debug packets #define PRINT_MCU_DEBUG 0 // Constants #define kMCUResponseTimeout 4000000 #define kMCUTaskStackSize 8192 #define kMCUMaxRecvSize 32 #define kMCUBaudRate 250000 #define kMCUWordPeriodUS (1000000 / (kMCUBaudRate/10)) #define kMCUFifoSize 1024 #define kMCUNotNowDelayUS 100000 #define kMCUNotNowAttempts 100 #define kMCUReadTimeoutUS 1000000 #define kMCUSendAttempts 5 #define kMCUFifoLatencyUS (kMCUWordPeriodUS * kMCUFifoSize) #define kMCUReadSleepUS (kMCUFifoLatencyUS / 4) struct McuRecvPacket { uint8_t opcode; uint8_t buf[kMCUMaxRecvSize]; size_t len; }; static bool gIsMCUInitialized = false; static struct task_event mcu_gate; static struct task_event mcu_packet_arrived; static struct task_event mcu_packet_consumed; static bool mcu_recv_valid; static struct McuRecvPacket mcu_recv; static int mcu_send_command(int opcode, uint8_t *data, size_t data_len); static int mcu_set_sil(int action); static int mcu_set_bs(int data0, int data1); // data1 == -1 if unused. static int mcu_get_version(char *version, size_t length); static int mcu_task(void *arg); static int mcu_send_packet(MCUOpCode opcode, uint8_t *data, size_t length); static int mcu_read_packet(MCUOpCode *outOpcode, uint8_t *outData, size_t maxLen, size_t *outLen); static int mcu_read_response(MCUOpCode *outOpcode, uint8_t *outData, size_t maxLen, size_t *outLen); static unsigned long crc_ccitt(unsigned long crc, unsigned char* p, unsigned long len); int mcu_init(void) { int ret = -1; char version[kMCUMaxRecvSize]; uart_hw_init_extended(MCU_SERIAL_PORT, kMCUBaudRate, 8, PARITY_NONE, 1); ret = uart_hw_set_rx_buf(MCU_SERIAL_PORT, true, kMCUFifoSize); if (ret != 0) panic("mcu_init() uart_hw_set_rx_buf failed"); // send break to resync stream uart_send_break(MCU_SERIAL_PORT, true); task_sleep(kMCUWordPeriodUS * 2); // at least one word uart_send_break(MCU_SERIAL_PORT, false); event_init(&mcu_gate, EVENT_FLAG_AUTO_UNSIGNAL, true); event_init(&mcu_packet_arrived, EVENT_FLAG_AUTO_UNSIGNAL, false); event_init(&mcu_packet_consumed, EVENT_FLAG_AUTO_UNSIGNAL, false); task_start(task_create("mcu", &mcu_task, NULL, kMCUTaskStackSize)); // first, retrieve MCU version string ret = mcu_get_version(version, sizeof(version)); // constant slow-blink: device is booting if (ret == 0) { ret = mcu_set_sil(kMCUSILAction_SlowFlash); } // record and report whether MCU was successfully initialized gIsMCUInitialized = (ret == 0); if (gIsMCUInitialized) { dprintf(DEBUG_INFO, "MCU version: %s\n", version); } else { dprintf(DEBUG_CRITICAL, "mcu_init() failed\n"); } return ret; } int mcu_start_recover(void) { int ret = -1; event_wait(&mcu_gate); // constant fast-blink: plug into iTunes if (gIsMCUInitialized) { ret = mcu_set_sil(kMCUSILAction_FastFlash); } event_signal(&mcu_gate); return ret; } int mcu_start_boot(void) { int ret = -1; event_wait(&mcu_gate); // constant slow-blink: device is booting if (gIsMCUInitialized) { ret = mcu_set_sil(kMCUSILAction_SlowFlash); } event_signal(&mcu_gate); return ret; } int mcu_set_passthrough_mode(bool on) { int ret = -1; if (gIsMCUInitialized) { event_wait(&mcu_gate); if (on) { ret = mcu_set_bs(kMCUBSControl_EnterTestMode, -1); if (ret != 0) goto exit; ret = mcu_set_bs(kMCUBSControl_PassthroughMode, -1); if (ret != 0) goto exit; } else { ret = mcu_set_bs(kMCUBSControl_Reset, -1); } exit: if (ret != 0) { dprintf(DEBUG_CRITICAL, "mcu_set_passthrough_mode %d failed\n", on); } event_signal(&mcu_gate); } return ret; } int mcu_send_info_frames(bool on) { int ret = -1; if (gIsMCUInitialized) { event_wait(&mcu_gate); ret = mcu_set_bs(kMCUBSControl_InfoFrameEnable, on ? 1 : 0); event_signal(&mcu_gate); if (ret != 0) { dprintf(DEBUG_CRITICAL, "mcu_send_info_farmes %d failed\n", on); } } return ret; } int mcu_quiesce_uart(void) { int ret = -1; ret = uart_hw_set_rx_buf(MCU_SERIAL_PORT, false, kMCUFifoSize); if (ret != 0) panic("uart can not be put into polling mode!"); return ret; } static int mcu_send_command(int command, uint8_t *data, size_t data_len) { int retries = kMCUSendAttempts; int not_now_retries = kMCUNotNowAttempts; int ret = -1; do { ret = mcu_send_packet(command, data, data_len); if (ret != 0) continue; uint8_t opcode; ret = mcu_read_response(&opcode, NULL, 0, NULL); if (ret != 0) continue; if (opcode != kMCUOpCode_Ack) ret = -1; if (opcode == kMCUOpCode_NakNotNow) { // MCU isn't in the right state for this command. Allow // more time than usual for this transient state to pass. if (--not_now_retries > 0) { task_sleep(kMCUNotNowDelayUS); continue; } else { break; } } } while ((ret != 0) && (--retries > 0)); return ret; } static int mcu_set_sil(int action) { uint8_t data = action; return mcu_send_command(kMCUOpCode_SILControl, &data, 1); } static int mcu_set_bs(int data0, int data1) { uint8_t buf[2] = { data0, data1 }; return mcu_send_command(kMCUOpCode_BlueSteelControl, buf, data1 >= 0 ? 2 : 1); } static int mcu_get_version(char *version, size_t length) { int ret; ret = mcu_send_packet(kMCUOpCode_GetVersion, NULL, 0); if (ret != 0) return ret; uint8_t opcode; size_t outLength; ret = mcu_read_response(&opcode, (uint8_t *)version, length, &outLength); if (ret != 0) return ret; if (opcode != kMCUOpCode_Ack) return -1; if (version) version[outLength-1] = 0; return 0; } static int mcu_task(void *arg) { struct McuRecvPacket new_recv; bzero(&new_recv, sizeof(new_recv)); for (;;) { int ret = mcu_read_packet(&new_recv.opcode, new_recv.buf, sizeof(new_recv.buf), &new_recv.len); if (ret != 0) continue; // Process uninteresting packets here. if (new_recv.opcode == kMCUOpCode_DebugData) { #if PRINT_MCU_DEBUG size_t i; dprintf(DEBUG_INFO, "mcu: \""); for (i = 0; i < new_recv.len; ++i) dprintf(DEBUG_INFO, "%c", new_recv.buf[i]); dprintf(DEBUG_INFO, "\"\n"); #endif continue; } if (!MCUOpCode_IsAckOrNak(new_recv.opcode)) continue; // Signal packet arrival. memcpy(&mcu_recv, &new_recv, sizeof(mcu_recv)); mcu_recv_valid = true; event_signal(&mcu_packet_arrived); // Wait for packet consumption. while (mcu_recv_valid) { event_wait(&mcu_packet_consumed); } } return 0; } static int mcu_send_packet(MCUOpCode opcode, uint8_t *payload, size_t length) { if (length > 0xFFFF) return -1; uint16_t crc = 0xffff; uint8_t header[6] = { 'S', 't', opcode, 0x01, (length >> 8) & 0xFF, length & 0xFF }; dprintf(DEBUG_SPEW, "mcu_send_packet 0x%02x\n", opcode); for (unsigned i = 0; i < sizeof(header); i++) { uart_putc(MCU_SERIAL_PORT, header[i]); } for (unsigned i = 0; i < length; i++) { uart_putc(MCU_SERIAL_PORT, payload[i]); } crc = crc_ccitt(crc, header, sizeof(header)); crc = crc_ccitt(crc, payload, length); uart_putc(MCU_SERIAL_PORT, (crc >> 8) & 0xFF); uart_putc(MCU_SERIAL_PORT, crc & 0xFF); return 0; } static int mcu_read_response(MCUOpCode *outOpcode, uint8_t *outData, size_t maxLen, size_t *outLen) { // wait at most a second utime_t timeout = system_time() + kMCUResponseTimeout; // Wait for a packet. while (!mcu_recv_valid) { utime_t now = system_time(); if (now >= timeout) { dprintf(DEBUG_INFO, "mcu response timeout\n"); return -1; } event_wait_timeout(&mcu_packet_arrived, timeout - now); } dprintf(DEBUG_SPEW, "received mcu packet type %#x\n", mcu_recv.opcode); if (outOpcode != NULL) *outOpcode = mcu_recv.opcode; if (outData) memcpy(outData, mcu_recv.buf, maxLen < mcu_recv.len ? maxLen : mcu_recv.len); if (outLen) *outLen = mcu_recv.len; if (mcu_recv.len > maxLen) { dprintf(DEBUG_SPEW, "mcu packet exceeds maxLen (%d vs %d)\n", (int) mcu_recv.len, (int) maxLen); } mcu_recv_valid = false; event_signal(&mcu_packet_consumed); return 0; } static int mcu_read_packet(MCUOpCode *outOpcode, uint8_t *outData, size_t maxLen, size_t *outLen) { utime_t timeout = system_time() + kMCUReadTimeoutUS; int ret = -1; size_t offset = 0; size_t length = 0; uint16_t crc = 0xffff; for (;;) { int ch; for (;;) { ch = uart_getc(MCU_SERIAL_PORT, false); if (ch >= 0) break; if (system_time() >= timeout) goto exit; task_sleep(kMCUReadSleepUS); } if (offset == 0) { if (ch != 'S') continue; } else if (offset == 1) { if (ch != 't') { offset = (ch == 'S') ? 1 : 0; continue; } } else if (offset == 2) { if (outOpcode != NULL) *outOpcode = ch; } else if (offset == 3) { // ignore sequence number } else if (offset == 4) { length = (ch & 0xFF) << 8; } else if (offset == 5) { length |= (ch & 0xFF); } else if (offset < (length + 6)) { if ((maxLen + 6) > offset) { outData[offset-6] = ch; } } else if ((offset - (length + 6)) == 0) { if (((crc >> 8) & 0xFF) != ch) { dprintf(DEBUG_INFO, "mcu checksum failure\n"); goto exit; } } else if ((offset - (length + 6)) == 1) { if ((crc & 0xFF) != ch) { dprintf(DEBUG_INFO, "mcu checksum failure\n"); goto exit; } if (outLen != NULL) *outLen = (maxLen < length) ? maxLen : length; ret = 0; goto exit; } if (offset < (length + 6)) { uint8_t b = ch; crc = crc_ccitt(crc, &b, sizeof(b)); } offset++; } dprintf(DEBUG_INFO, "mcu read timeout\n"); exit: return ret; } const int order = 16; const unsigned long polynom = 0x1021; static unsigned long crc_ccitt(unsigned long crc, unsigned char* p, unsigned long len) { // fast bit by bit algorithm without augmented zero bytes. // does not use lookup table, suited for polynom orders between 1...32. unsigned long i, j, c, bit; unsigned long crchighbit = (unsigned long)1<<(order-1); for (i=0; i>=1) { bit = crc & crchighbit; crc<<= 1; if (c & j) bit ^= crchighbit; if (bit) crc ^= polynom; } } return crc; } #if WITH_MENU static int mcu_send_raw(int command, uint8_t *data, int data_len) { int ret; uint8_t opcode; ret = mcu_send_packet(command, data, data_len); if (ret != 0) { printf("mcu_send_packet failed: %d\n", ret); return ret; } ret = mcu_read_response(&opcode, NULL, 0, NULL); if (ret != 0) { printf("mcu_read_response failed: %d\n", ret); return ret; } return 0; } static int do_mcu(int argc, struct cmd_arg *args) { if (!gIsMCUInitialized) { if (0 == mcu_init()) { printf("WARNING: mcu not previously initialized"); } else { printf("ERROR: unable to initialize mcu"); return -1; } } if ((argc > 1) && !strcmp(args[1].str, "version")) { char version[32]; if (mcu_get_version(version, sizeof(version)) == 0) { printf("%s\n", version); } return 0; } if ((argc > 2) && !strcmp(args[1].str, "sil")) { int action = kMCUSILAction_Invalid; if (!strcmp(args[2].str, kMCUSILAction_OffStr)) action = kMCUSILAction_Off; else if (!strcmp(args[2].str, kMCUSILAction_OnStr)) action = kMCUSILAction_On; else if (!strcmp(args[2].str, kMCUSILAction_SlowStr)) action = kMCUSILAction_SlowFlash; else if (!strcmp(args[2].str, kMCUSILAction_FastStr)) action = kMCUSILAction_FastFlash; else if (!strcmp(args[2].str, kMCUSILAction_1BlinkStr)) action = kMCUSILAction_1Blink; else if (!strcmp(args[2].str, kMCUSILAction_3BlinksStr)) action = kMCUSILAction_3Blinks; if (action != kMCUSILAction_Invalid) { return mcu_set_sil(action); } } if ((argc > 2) && !strcmp(args[1].str, "bs")) { int action = kMCUBSControl_Invalid; int extra = -1; if (!strcmp(args[2].str, kMCUBSControl_ResetStr)) action = kMCUBSControl_Reset; else if (!strcmp(args[2].str, kMCUBSControl_EnterTestModeStr)) action = kMCUBSControl_EnterTestMode; else if (!strcmp(args[2].str, kMCUBSControl_PassthroughModeStr)) action = kMCUBSControl_PassthroughMode; else if (!strcmp(args[2].str, kMCUBSControl_InfoFrameEnableStr) && argc > 3) { action = kMCUBSControl_InfoFrameEnable; extra = args[3].u; } if (action != kMCUBSControl_Invalid) { return mcu_set_bs(action, extra); } } if ((argc > 2) && !strcmp(args[1].str, "raw")) { int i; uint8_t command = args[2].u; uint8_t data[8]; size_t data_len = argc - 3; if (data_len > sizeof(data)) { printf("Max %zu raw bytes\n", sizeof(data) + 1); return -1; } for (i = 3; i < argc; ++i) data[i - 3] = args[i].u; return mcu_send_raw(command, data, data_len); } printf("%s version\n", args[0].str); printf("%s sil [%s|%s|%s|%s|%s|%s]\n", args[0].str, kMCUSILAction_OffStr, kMCUSILAction_OnStr, kMCUSILAction_SlowStr, kMCUSILAction_FastStr, kMCUSILAction_1BlinkStr, kMCUSILAction_3BlinksStr); printf("%s bs [%s|%s|%s|%s]\n", args[0].str, kMCUBSControl_ResetStr, kMCUBSControl_EnterTestModeStr, kMCUBSControl_PassthroughModeStr, kMCUBSControl_InfoFrameEnableStr); printf("%s raw ...\n", args[0].str); return -1; } MENU_COMMAND_DEVELOPMENT(mcu, do_mcu, "MCU control", NULL); #endif