/*====================================================================* Copyright (c) 2021 Qualcomm Technologies, Inc. All Rights Reserved. Confidential and Proprietary - Qualcomm Technologies, Inc. *--------------------------------------------------------------------*/ /*====================================================================* * * pev1.c - QCA Plug-in Electric Vehicle Emulator; * * This program, in the current state, is not a finished product; * It has been released so that interested parties can begin to * see how the SLAC protocol might be implemented; * * Some key design features are: * * 1) the use of a channel variable to abstract ISO Layer 2 I/O; * the variable is used by functions openchannel, readmessage, * sendmessage and closechannel; * * 2) the use of a message variable to represent an IEEE 802.3 * Ethernet frame; the variable allows one frame to be used * and re-used throughout the program but supports multiple * frame buffers if needed; * * 3) the use of a session variable to support multiple PEV-EVSE * interactions without using threads or subrocesses; this has * not demonstrated in this version of the program; some more * work is needed; * * 4) the absence of threads or subprocesses so that the program * can be ported to hosts without a multi-tasking operating * system; * * 5) lots of debugging messages; these can be suppressed or * deleted if not wanted; * * 6) extened state machine incorporating HAR MME handler; * *--------------------------------------------------------------------*/ /*====================================================================* * system header files; *--------------------------------------------------------------------*/ #include #include #include #include #include #include /*====================================================================* * custom header files; *--------------------------------------------------------------------*/ #include "../tools/getoptv.h" #include "../tools/putoptv.h" #include "../tools/memory.h" #include "../tools/number.h" #include "../tools/types.h" #include "../tools/flags.h" #include "../tools/files.h" #include "../tools/error.h" #include "../tools/config.h" #include "../ether/channel.h" #include "../slac/slac.h" #include "../plc/plc.h" /*====================================================================* * custom source files; *--------------------------------------------------------------------*/ #ifndef MAKEFILE #include "../tools/getoptv.c" #include "../tools/putoptv.c" #include "../tools/version.c" #include "../tools/hexdump.c" #include "../tools/hexdecode.c" #include "../tools/hexencode.c" #include "../tools/hexstring.c" #include "../tools/decdecode.c" #include "../tools/decstring.c" #include "../tools/uintspec.c" #include "../tools/todigit.c" #include "../tools/strfbits.c" #include "../tools/config.c" #include "../tools/memincr.c" #include "../tools/debug.c" #include "../tools/error.c" #endif #ifndef MAKEFILE #include "../plc/Devices.c" #endif #ifndef MAKEFILE #include "../mme/EthernetHeader.c" #include "../mme/QualcommHeader.c" #include "../mme/HomePlugHeader1.c" #include "../mme/UnwantedMessage.c" #include "../mme/readmessage.c" #include "../mme/sendmessage.c" #endif #ifndef MAKEFILE #include "../ether/channel.c" #include "../ether/openchannel.c" #include "../ether/closechannel.c" #include "../ether/sendpacket.c" #include "../ether/readpacket.c" #endif #ifndef MAKEFILE #include "../slac/slac_session.c" #include "../slac/slac_connect.c" #include "../slac/pev_cm_slac_param.c" #include "../slac/pev_cm_start_atten_char.c" #include "../slac/pev_cm_atten_char.c" #include "../slac/pev_cm_mnbc_sound.c" #include "../slac/pev_cm_slac_match.c" #include "../slac/pev_cm_set_key.c" #endif /*====================================================================* * program constants; *--------------------------------------------------------------------*/ #define PLCDEVICE "PLC" #define PROFILE "pev.ini" #define SECTION "default" #define PEV_STATE_INIT 0 #define PEV_STATE_RESET 1 #define PEV_STATE_SLAC_PARAM_EXCHANGE 2 #define PEV_STATE_SOUNDING 3 #define PEV_STATE_ATTENUATION_PROFILE 4 #define PEV_STATE_SLAC_MATCH 5 #define PEV_STATE_CHARGING 6 //states 7-31 is reserved for any future slac transactions/MMEs #define PEV_STATE_HAR_LOADER 32 #define PEV_STATE_HAR_FIRMWARE 33 #define PEV_STATE_HAR_PIB 34 #define PEV_STATE_HAR_FW_PIB 35 #define PEV_STATE_HAR_LOADER_SDRAM 36 #define PEV_STATE_HAR_RESET_FACTORY_DEFAULT 37 #define PEV_STATE_HAR_BACKGROUND_PIB 38 #define PEV_STATE_HAR_DEVICE_REBOOTED 39 #define PEV_STATE_HAR_HIF_READY 40 #define PEV_STATE_HAR_POWERLINE_READY 41 #define PEV_STATE_HAR_AVLN_READY_PEER_AVAILABLE 42 #define PEV_STATE_HAR_LINKLOSS 43 #define PEV_VID "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" // VehicleIdentifier #define PEV_NMK "50E3F4934F855F7040784EA815CE9FA5" // Random NMK #define PEV_NID "B0F2E695666B03" // HomePlugAV /*====================================================================* * program variables; *--------------------------------------------------------------------*/ unsigned prev_session_state = 0; unsigned charging_time = SLAC_CHARGETIME; unsigned no_of_slac_loop = SLAC_LOOP; /*====================================================================* * * static void configure (); * * print template PEV-HLE configuration file on stdout so that * profile, section and element names match; * *--------------------------------------------------------------------*/ static void configure () { printf ("# file: %s\n", PROFILE); printf ("# ====================================================================\n"); printf ("# PEV-HLE initialization;\n"); printf ("# --------------------------------------------------------------------\n"); printf ("[%s]\n", SECTION); printf ("vehicle identifier = %s\n", PEV_VID); printf ("network membership key = %s\n", PEV_NMK); printf ("network identifier = %s\n", PEV_NID); printf ("attenuation threshold = %d\n", SLAC_LIMIT); printf ("msound pause = %d\n", SLAC_PAUSE); printf ("charge time = %d\n", charging_time); printf ("settle time = %d\n", SLAC_SETTLETIME); return; } /*====================================================================* * * void initialize (struct session * session, char const * profile, char const * section); * * read PEV-HLE configuration profile; initialize session variable; * *--------------------------------------------------------------------*/ static void initialize (struct session * session, char const * profile, char const * section) { session->next = session->prev = session; hexencode (session->PEV_ID, sizeof (session->PEV_ID), configstring (profile, section, "VehicleIdentifier", PEV_VID)); hexencode (session->NMK, sizeof (session->NMK), configstring (profile, section, "NetworkMembershipKey", PEV_NMK)); hexencode (session->NID, sizeof (session->NID), configstring (profile, section, "NetworkIdentifier", PEV_NID)); session->limit = confignumber (profile, section, "AttenuationThreshold", SLAC_LIMIT); session->pause = confignumber (profile, section, "MSoundPause", SLAC_PAUSE); session->settletime = confignumber (profile, section, "SettleTime", SLAC_SETTLETIME); session->chargetime = confignumber (profile, section, "ChargeTime", charging_time); session->state = PEV_STATE_INIT; memcpy (session->original_nmk, session->NMK, sizeof (session->original_nmk)); memcpy (session->original_nid, session->NID, sizeof (session->original_nid)); slac_session (session); return; } /*====================================================================* * * signed identifier (struct session * session, struct channel * channel); * * generate the run identifier and store in session variable; * * copy channel host address to session PEV MAC address; set session * PEV identifier to zeros; * *--------------------------------------------------------------------*/ static signed identifier (struct session * session, struct channel * channel) { time_t now; time (& now); memset (session->RunID, 0, sizeof (session->RunID)); memcpy (session->RunID, channel->host, ETHER_ADDR_LEN); memcpy (session->PEV_MAC, channel->host, sizeof (session->PEV_MAC)); return (0); } unsigned int get_session_state(struct plc* plc, unsigned int cur_session_state) { struct message* message = (struct message*)(plc->message); #ifndef __GNUC__ #pragma pack (push,1) #endif struct __packed vs_host_action_ind { struct ethernet_hdr ethernet; struct qualcomm_hdr qualcomm; uint8_t MACTION; uint8_t MAJOR_VERSION; uint8_t MINOR_VERSION; uint8_t session_id; uint16_t outstanding_retries; uint16_t retrytimer_in10ms; } *indicate = (struct vs_host_action_ind*)(message); #ifndef __GNUC__ #pragma pack (pop) #endif unsigned int state; signed action = indicate->MACTION; switch (action) { case 0: state = PEV_STATE_HAR_LOADER; break; case 1: state = PEV_STATE_HAR_FIRMWARE; break; case 2: state = PEV_STATE_HAR_PIB; break; case 3: state = PEV_STATE_HAR_FW_PIB; break; case 4: state = PEV_STATE_HAR_LOADER_SDRAM; break; case 6: state = PEV_STATE_HAR_BACKGROUND_PIB; break; case 7: state = PEV_STATE_HAR_DEVICE_REBOOTED; break; case 8: state = PEV_STATE_HAR_HIF_READY; break; case 9: state = PEV_STATE_HAR_POWERLINE_READY; break; case 10: state = PEV_STATE_HAR_AVLN_READY_PEER_AVAILABLE; break; case 11: state = PEV_STATE_HAR_LINKLOSS; break; default: debug(1, 0, "Unhandled HAR message"); break; } return state; } void set_session_state(struct session* session, unsigned int state) { prev_session_state = session->state; session->state = state; } signed mme_to_pev_state(struct session* session, struct plc* plc, uint16_t MMTYPE) { switch (MMTYPE) { case (CM_SLAC_PARAM | MMTYPE_CNF): set_session_state(session, PEV_STATE_SLAC_PARAM_EXCHANGE); break; case (CM_ATTEN_CHAR | MMTYPE_IND): set_session_state(session, PEV_STATE_ATTENUATION_PROFILE); break; case (CM_SLAC_MATCH | MMTYPE_CNF): set_session_state(session, PEV_STATE_SLAC_MATCH); break; case (VS_HOST_ACTION | MMTYPE_IND): set_session_state(session, get_session_state(plc, session->state)); break; default: return (-1); break; } return 0; } /*====================================================================* * * int main (int argc, char * argv[]); * * *--------------------------------------------------------------------*/ int main (int argc, char const * argv []) { extern struct channel channel; static char const * optv [] = { "cCdFi:l:n:N:p:P:qr:s:S:t:T:vw:x", "", "Qualcomm Atheros Plug-in Electric Vehicle Emulator", "c\tprint template configuration file on stdout", "C\tstop on count mismatch", "d\tdisplay debug information", "F\tflash non-volatile memory", #if defined (WINPCAP) || defined (LIBPCAP) "i n\thost interface is (n) [" LITERAL (CHANNEL_ETHNUMBER) "]", #else "i s\thost interface is (s) [" LITERAL (CHANNEL_ETHDEVICE) "]", #endif "l n\tloop slac (n) times [" LITERAL(SLAC_LOOP) "]", "n f\tuser firmware file is (f)", "N f\tfirmware file is (f)", "p f\tuser parameter file is (f)", "P f\tparameter file is (f)", "q\tsuppress normal output", "r s\tconfiguration profile is (s) [" LITERAL(PROFILE) "]", "s s\tconfiguration section is (s) [" LITERAL(SECTION) "]", "S f\tsoftloader file is (f)", "t n\tread timeout is (n) milliseconds [" LITERAL (SLAC_TIMEOUT) "]", "T n\tcharging time is (n) seconds [" LITERAL(SLAC_CHARGETIME) "]", "v\tverbose messages on stdout", "x\texit on error", (char const *) (0) }; #include "../plc/plc.c" struct session session; struct message message; char const * profile = PROFILE; char const * section = SECTION; struct homeplug1* homeplug; uint16_t MMType; char firmware[PLC_VERSION_STRING]; signed c; memset (& session, 0, sizeof (session)); memset (& message, 0, sizeof (message)); channel.timeout = SLAC_TIMEOUT; if (getenv (PLCDEVICE)) { #if defined (WINPCAP) || defined (LIBPCAP) channel.ifindex = atoi (getenv (PLCDEVICE)); #else channel.ifname = strdup (getenv (PLCDEVICE)); #endif } optind = 1; while (~ (c = getoptv (argc, argv, optv))) { switch (c) { case 'c': configure (); return (0); case 'C': _setbits (session.flags, SLAC_COMPARE); break; case 'd': _setbits (session.flags, (SLAC_VERBOSE | SLAC_SESSION)); break; case 'F': _setbits(plc.flags, PLC_FLASH_DEVICE); break; case 'i': #if defined (WINPCAP) || defined (LIBPCAP) channel.ifindex = atoi (optarg); #else channel.ifname = optarg; #endif break; case 'l': no_of_slac_loop = (unsigned)(uintspec(optarg, 0, UINT_MAX)); break; case 'n': if (!checkfilename(optarg)) { error(1, EINVAL, "%s", optarg); } if ((plc.nvm.file = open(plc.nvm.name = optarg, O_BINARY | O_CREAT | O_RDWR | O_TRUNC, FILE_FILEMODE)) == -1) { error(1, errno, "%s", plc.nvm.name); } break; case 'N': if (!checkfilename(optarg)) { error(1, EINVAL, "%s", optarg); } if ((plc.NVM.file = open(plc.NVM.name = optarg, O_BINARY | O_RDONLY)) == -1) { error(1, errno, "%s", plc.NVM.name); } if (panther_nvm_file(&plc.NVM)) { error(1, errno, "Bad panther image file: %s", plc.NVM.name); } break; case 'p': if (!checkfilename(optarg)) { error(1, EINVAL, "%s", optarg); } if ((plc.pib.file = open(plc.pib.name = optarg, O_BINARY | O_CREAT | O_RDWR | O_TRUNC, FILE_FILEMODE)) == -1) { error(1, errno, "%s", plc.pib.name); } break; case 'P': if (!checkfilename(optarg)) { error(1, EINVAL, "%s", optarg); } if ((plc.PIB.file = open(plc.PIB.name = optarg, O_BINARY | O_RDONLY)) == -1) { error(1, errno, "%s", plc.PIB.name); } if (panther_pib_file(&plc.PIB)) { error(1, errno, "Bad panther parameter file: %s", plc.PIB.name); } break; case 'q': _clrbits(channel.flags, CHANNEL_SILENCE); break; case 'r': profile = optarg; break; case 's': section = optarg; break; case 'S': if (!checkfilename(optarg)) { error(1, EINVAL, "%s", optarg); } if ((plc.SFT.file = open(plc.SFT.name = optarg, O_BINARY | O_RDONLY)) == -1) { error(1, errno, "%s", plc.SFT.name); } if (panther_nvm_file(&plc.SFT)) { error(1, errno, "Bad panther image file: %s", plc.SFT.name); } break; case 't': channel.timeout = (unsigned) (uintspec (optarg, 0, UINT_MAX)); break; case 'T': charging_time = (unsigned)(uintspec(optarg, 0, UINT_MAX)); break; case 'v': _setbits (channel.flags, CHANNEL_VERBOSE); break; case 'x': session.exit = session.exit? 0: 1; break; default: break; } } argc -= optind; argv += optind; if (argc) { debug (1, __func__, ERROR_TOOMANY); } openchannel (& channel); identifier (& session, & channel); initialize (& session, profile, section); plc.channel = &channel; plc.message = &message; if (_anyset(session.flags, SLAC_VERBOSE)) { _setbits(plc.flags, PLC_VERBOSE); } bool wait_in_loop_for_mme = true; bool identity_requested = false; bool identity_received = false; bool evse_slac_matched = false; for (unsigned int iteration = 0; iteration < no_of_slac_loop; iteration++) { printf("iteration = %d\n", iteration); if ((session.state == PEV_STATE_INIT) || (session.state == PEV_STATE_HAR_LINKLOSS)) { sleep(session.settletime); if (WaitForStart(&plc, firmware, sizeof(firmware))) { Failure(&plc, PLC_NODETECT); exit(1); } if (strcmp(firmware, "BootLoader")) { printf("Device not in bootloader mode. ChipID 0x%0x\n", plc.hardwareID); //Randomize the NMK plc.pushbutton = 2; if (PushButton(&plc)) { printf("Failed to randomize\n"); exit(1); } else { memset(firmware, 0, sizeof(firmware)); if (WaitForStart(&plc, firmware, sizeof(firmware))) { Failure(&plc, PLC_NODETECT); exit(1); } else { session.state = PEV_STATE_RESET; } } } else { if (plc.PIB.file == -1) { error(1, ECANCELED, "No PIB file specified"); } if (plc.NVM.file == -1) { error(1, ECANCELED, "No NVM file specified"); } if (plc.SFT.file == -1) { error(1, ECANCELED, "No Softloader file specified"); } } } while (true && (session.state != PEV_STATE_HAR_LINKLOSS)) { if (wait_in_loop_for_mme) { while ((plc.packetsize = readpacket(&channel, &message, sizeof(message))) >= 0) { homeplug = (struct homeplug1*)(&message); MMType = homeplug->homeplug.MMTYPE; if (!UnwantedMessage(&message, plc.packetsize, homeplug->homeplug.MMV, MMType)) { if (MMType == (CM_SET_KEY | MMTYPE_CNF)) { if (pev_cm_set_key_cnf(&session, &channel, &message)) { //handle failure of set key gracefully instead of exiting debug(1, __func__, "Can't set key"); } if (session.state == PEV_STATE_SLAC_MATCH) { evse_slac_matched = true; } } else if (MMType == (VS_MODULE_OPERATION | MMTYPE_CNF)) { if (process_get_nid_cnf(&plc, session.original_nid, session.original_nmk)) { debug(1, __func__, "Error in getting NMK & NID"); } identity_received = true; if (session.state == PEV_STATE_HAR_POWERLINE_READY) { break; } } else if (mme_to_pev_state(&session, &plc, MMType) == 0) { debug(0, __func__, "Received required MME. Break the read loop! MMTYPE %0x", MMType); break; } } } if (MMType == (VS_HOST_ACTION | MMTYPE_IND)) { //Send HAR response vs_host_action_response(&plc); } } else { wait_in_loop_for_mme = true; } switch (session.state) { case PEV_STATE_SLAC_PARAM_EXCHANGE: if (pev_cm_slac_param_cnf(&session, &channel, &message)) { debug(1, __func__, "Error in slac parameter exchange confirm"); } //Move to sounding state from here and make wait_in_loop_for_mme false prev_session_state = session.state; session.state = PEV_STATE_SOUNDING; wait_in_loop_for_mme = false; break; case PEV_STATE_SOUNDING: if (pev_cm_start_atten_char(&session, &channel, &message)) { debug(1, __func__, "Error in start attenuation char indication"); } if (pev_cm_mnbc_sound(&session, &channel, &message)) { debug(1, __func__, "Error in sounding"); } break; case PEV_STATE_ATTENUATION_PROFILE: if (pev_cm_atten_char_process(&session, &channel, &message)) { debug(1, __func__, "Error in decoding attenuation MME"); } if (slac_connect(&session)) { debug(1, __func__, "Calculated attenuation not in range"); } if (pev_cm_slac_match_req(&session, &channel, &message)) { debug(1, __func__, "Error in sending slac match"); } break; case PEV_STATE_SLAC_MATCH: if (pev_cm_slac_match_cnf(&session, &channel, &message)) { debug(1, __func__, "Error in decoding slac match"); } if (pev_cm_set_key_req(&session, &channel, &message)) { //handle failure of set key gracefully instead of exiting debug(1, __func__, "Can't set key in slac match"); } break; case PEV_STATE_CHARGING: debug(0, __func__, "Charging (%d) ...\n\n", session.counter++); sleep(session.chargetime); debug(0, __func__, "Disconnecting ..."); memcpy(session.NMK, session.original_nmk, sizeof(session.NMK)); memcpy(session.NID, session.original_nid, sizeof(session.NID)); if (pev_cm_set_key_req(&session, &channel, &message)) { debug(1, __func__, "Can't set key"); } break; case PEV_STATE_HAR_LOADER: //load FW and PIB debug(0, __func__, "HAR event 0x00. Boot device!"); if (BootDevice2_no_wait(&plc)) { return (-1); } if (_anyset(plc.flags, PLC_FLASH_DEVICE)) { if (WaitForStart(&plc, firmware, sizeof(firmware))) { return (-1); } FlashDevice2_no_wait(&plc, (PLC_COMMIT_FORCE | PLC_COMMIT_NORESET | PLC_COMMIT_FACTPIB)); } break; case PEV_STATE_HAR_FIRMWARE: //read device FW close(plc.NVM.file); if (ReadFirmware2(&plc)) { return (-1); } if ((plc.NVM.file = open(plc.NVM.name = plc.nvm.name, O_BINARY | O_RDONLY)) == -1) { error(1, errno, "%s", plc.NVM.name); } break; case PEV_STATE_HAR_PIB: //read device pib and issue reset //pib read not required as of now commented. #ifdef READ_PIB close(plc.PIB.file); if (ReadParameters2(&plc)) { return (-1); } if ((plc.PIB.file = open(plc.PIB.name = plc.pib.name, O_BINARY | O_RDONLY)) == -1) { error(1, errno, "%s", plc.PIB.name); } #endif if (ResetDevice(&plc)) { return (-1); } break; case PEV_STATE_HAR_FW_PIB: //read device FW and pib and issue reset close(plc.PIB.file); if (ReadParameters2(&plc)) { return (-1); } if ((plc.PIB.file = open(plc.PIB.name = plc.pib.name, O_BINARY | O_RDONLY)) == -1) { error(1, errno, "%s", plc.PIB.name); } close(plc.NVM.file); if (ReadFirmware2(&plc)) { return (-1); } if ((plc.NVM.file = open(plc.NVM.name = plc.nvm.name, O_BINARY | O_RDONLY)) == -1) { error(1, errno, "%s", plc.NVM.name); } if (ResetDevice(&plc)) { return (-1); } break; case PEV_STATE_HAR_LOADER_SDRAM: //load MCTL applets identity_requested = false; identity_received = false; evse_slac_matched = false; debug(0, __func__, "HAR event 0x04. Init device with MCTL applets!"); if (InitDevice2(&plc)) { return (-1); } break; case PEV_STATE_HAR_BACKGROUND_PIB: debug(0, __func__, "Background pib change occured"); //MMEs related to SLAC are missing during pib read. so don't do pib read for now. TODO #ifdef READ_PIB //read device pib close(plc.PIB.file); if (ReadParameters2(&plc)) { return (-1); } if ((plc.PIB.file = open(plc.PIB.name = plc.pib.name, O_BINARY | O_RDONLY)) == -1) { error(1, errno, "%s", plc.PIB.name); } #endif break; case PEV_STATE_HAR_DEVICE_REBOOTED: debug(0, __func__, "Host has rebooted"); break; case PEV_STATE_HAR_HIF_READY: debug(0, __func__, "Device ready for Hif communication"); //get the original PEV key if (get_nid_nmk_req(&plc)) { debug(1, __func__, "Error in getting nid"); } identity_requested = true; break; case PEV_STATE_HAR_POWERLINE_READY: debug(0, __func__, "Device is ready to communicate on powerline but not a member of AVLN yet"); if ((!identity_requested)) { //get the original PEV key if (get_nid_nmk_req(&plc)) { debug(1, __func__, "Error in getting nid"); } identity_requested = true; } if (identity_received && ((prev_session_state == PEV_STATE_HAR_HIF_READY) || (prev_session_state == PEV_STATE_RESET))) { memincr(session.RunID, sizeof(session.RunID)); evse_slac_matched = false; if (pev_cm_slac_param_req(&session, &channel, &message)) { debug(1, __func__, "Error in slac parameter exchange"); } } break; case PEV_STATE_HAR_AVLN_READY_PEER_AVAILABLE: debug(0, __func__, "Device is a member of AVLN and there are peers available"); if (prev_session_state == PEV_STATE_SLAC_MATCH || evse_slac_matched) { //Move to charging state from here and make wait_in_loop_for_mme false debug(0, __func__, "Move to charging state"); prev_session_state = session.state; session.state = PEV_STATE_CHARGING; wait_in_loop_for_mme = false; } break; case PEV_STATE_HAR_LINKLOSS: debug(0, __func__, "Device has disconnected from AVLN and there are no peers available"); break; default: debug(1, __func__, "Illegal state!"); break; } } } closechannel (& channel); return (0); }