├── .gitignore ├── mipsregs.h ├── start.S ├── linker.lds ├── Makefile ├── Readme.md └── blink.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | 3 | # Generated files 4 | *.bin 5 | *.elf 6 | *.o 7 | *.map 8 | -------------------------------------------------------------------------------- /mipsregs.h: -------------------------------------------------------------------------------- 1 | #define t0 $8 /* temporary values */ 2 | #define sp $29 /* stack pointer */ 3 | -------------------------------------------------------------------------------- /start.S: -------------------------------------------------------------------------------- 1 | #include "mipsregs.h" 2 | 3 | // make it accessible outside 4 | .globl _start 5 | 6 | // Tell binutils it's a function 7 | .ent _start 8 | .text 9 | 10 | _start: 11 | // Set up a stack 12 | li sp, __stack 13 | 14 | // And jump to C 15 | la t0, entrypoint 16 | jr t0 17 | nop 18 | 19 | .end _start 20 | -------------------------------------------------------------------------------- /linker.lds: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH(mips) 2 | 3 | ENTRY(_start) 4 | 5 | SECTIONS 6 | { 7 | /* Our base address */ 8 | /* . = STARTADDRESS; */ 9 | 10 | /* Code */ 11 | .text : { 12 | *(.text) 13 | } 14 | 15 | /* static data */ 16 | .rodata : { 17 | *(.rodata) 18 | *(.rodata.*) 19 | } 20 | /* non-static data */ 21 | .data : { 22 | *(.data*) 23 | } 24 | . = ALIGN(4); 25 | __stack = . + 0x1000; 26 | } 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CROSS_COMPILE = /opt/x-tools/mipsel-unknown-elf/bin/mipsel-unknown-elf- 2 | 3 | AS = $(CROSS_COMPILE)as -mips32 4 | CC = $(CROSS_COMPILE)gcc 5 | CXX = $(CROSS_COMPILE)g++ 6 | LD = $(CROSS_COMPILE)ld 7 | OBJCOPY = $(CROSS_COMPILE)objcopy 8 | 9 | CFLAGS = -Os -DSTARTADDRESS=$(STARTADDRESS) -Wall -Wextra -nostdlib 10 | CXXFLAGS = -std=c++17 -fno-rtti -fno-unwind-tables -fno-exceptions 11 | 12 | STARTADDRESS = 0x1000000 13 | 14 | TARGETS = blink.bin 15 | 16 | .PHONY: all 17 | all: $(TARGETS) 18 | 19 | %.bin: %.elf 20 | $(OBJCOPY) -O binary $< $@ 21 | 22 | # FIXME: order of objects should not matter 23 | %.elf: start.o %.o 24 | $(LD) -Ttext $(STARTADDRESS) -T linker.lds -Map $(@:.elf=.map) -o $@ $+ 25 | 26 | %.o: %.[Sc] 27 | $(CC) $(CFLAGS) -c -o $@ $< 28 | 29 | %.o: %.cpp 30 | $(CXX) $(CFLAGS) $(CXXFLAGS) -c -o $@ $< 31 | 32 | .PHONY: clean 33 | clean: 34 | rm -f *.o *.elf *.bin *~ *.map 35 | 36 | # ELF images can be used in cutter/radare2 37 | .PRECIOUS: %.elf 38 | 39 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Experiments with the Meross MSS310 Smart Plug 2 | 3 | This repository contains code to experiment with the Meross MSS310 4 | smart plug. The plug contains a daughter board with a Mediatek MT7688KN 5 | SoC. The device boots into u-boot, which then boots the real application 6 | which uses the eCos operating system. 7 | 8 | Note: the code assumes hardware revision 1. With hardware revision 2, 9 | Meross uses an MT7682SN SoC, which contains an ARM core instead of a 10 | MIPS core. 11 | 12 | The initial scaffold for the code in this repository has been taken 13 | from Nicholas FitzRoy-Dale’s [bare-metal hello world][1]. It is used to 14 | determine how to run my own software on the device and only serves as 15 | proof-of-concept. The real application will get its own repository, 16 | some day..., perhaps... . 17 | 18 | # Running the blink application 19 | 20 | - Get a cross compiler for MIPS. Or simply build one yourself using 21 | `crosstool-ng`. Run `make`. You probably need to change the path to 22 | the cross compiler in the `Makefile`. 23 | 24 | - Open the plug and solder four wires to the CPU board (RX, TX, GND, 25 | 3V3) and connect an USB-to-serial port to RX, TX and GND. Use your 26 | preferred terminal program to connect to the serial port. Parameters 27 | are 57600,8N1. 28 | 29 | - Do **NOT** plug the device into a power outlet! Instead, connect 30 | an external power-supply, 3.3V capable of delivering 500mA. While 31 | the board draws less than 200mA when running, it doesn't boot if 32 | the power supply's current limit is set to 200mA. 33 | 34 | - Hit `4` immediately when then device boots. This should drop you on 35 | the u-boot command line. 36 | 37 | - Enter `loadb 1000000` in the serial console, then disconnect your 38 | terminal. 39 | 40 | - From the host's command line, run `kermit -s blink.bin` to upload 41 | the compiled binary. 42 | 43 | - Connect to the MSS10 via the terminal program again. The `loadb` 44 | command should have terminated (you might need to hit the `Return` 45 | key). Enter `go 1000000` and the LEDs on the device should start 46 | cycling through all color combinations. Pressing the button should 47 | reboot the device. 48 | 49 | 50 | [1]: https://github.com/nfd/ci20-hello-world 51 | -------------------------------------------------------------------------------- /blink.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * A simple test program for testing lowlevel access to the MSS310's 3 | * - LEDs (green LED, red LED) 4 | * - Button 5 | * - UART (debug pins) 6 | * 7 | * When run on the MSS310, this program: 8 | * - cycles through all LED colors (red/green/yellow) 9 | * - prints some text on the serial console 10 | * - resets the CPU when the button on the device is pressed 11 | * - resets the CPU when the # key is sent via the serial interface 12 | * - switches the relay off when the 0 key is pressed 13 | * - switches the relay on when the 1 key is pressed 14 | * 15 | * Please don't judge the code quality: its all just proof-of-concept code. 16 | */ 17 | 18 | #include 19 | 20 | #define RSTCTL 0x10000034 /* Reset control register */ 21 | 22 | #define GPIO2_MODE 0x10000064 /* GPIO2 purpose selection */ 23 | 24 | #define TGLB_REG 0x10000100 /* Timers global control register */ 25 | 26 | #define TGBL_T0RST (1 << 8) /* Timer 0 reset */ 27 | #define TGBL_T1RST (1 << 10) /* Timer 1 reset */ 28 | 29 | #define T0CTL_REG 0x10000110 /* Timer 0 control register */ 30 | #define T0LMT_REG 0x10000114 /* Timer 0 limit register */ 31 | #define T0_REG 0x10000118 /* Timer 0 counter register */ 32 | 33 | #define T1CTL_REG 0x10000130 /* Timer 1 control register */ 34 | #define T1LMT_REG 0x10000134 /* Timer 1 limit register */ 35 | #define T1_REG 0x10000138 /* Timer 1 counter register */ 36 | 37 | #define TxCTL_Tx_PRES_OFFSET 16 /* Timer 0/1 prescale offset */ 38 | #define TxCTL_Tx_PRES_MASK 0xFFFF /* Timer 0/1 prescale mask */ 39 | #define TxCTL_TxEN (1 << 7) /* Timer 0/1 enable */ 40 | #define TxCTL_TxAL (1 << 4) /* Timer 0/1 auto-load enable */ 41 | #define TxCTL_TxAL_STATUS (1 << 3) /* Timer 0/1 auto load enable status */ 42 | 43 | #define GPIO_CTRL_1 0x10000604 /* Direction control register */ 44 | #define GPIO_DATA_1 0x10000624 /* Data register */ 45 | #define GPIO_DSET_1 0x10000634 /* Data set register */ 46 | #define GPIO_DCLR_1 0x10000644 /* Data clear register */ 47 | 48 | /* UART is expected to be configured from u-boot already */ 49 | #define UART0_RBR 0x10000C00 /* RX buffer register */ 50 | #define UART0_THR 0x10000C00 /* TX holding register */ 51 | #define UART0_LSR 0x10000C14 /* Line status register */ 52 | 53 | #define UART_LSR_DR (1 << 0) /* Data ready */ 54 | #define UART_LSR_THRE (1 << 5) /* TX holding register empty */ 55 | 56 | #define SYS_RST (1 << 0) /* Whole system reset */ 57 | 58 | #define GPIO_RELAY (1 << 0) 59 | #define GPIO_LED_PIN_RED (1 << 1) 60 | #define GPIO_LED_PIN_GREEN (1 << 2) 61 | #define GPIO_BUTTON (1 << 3) 62 | 63 | 64 | static inline void write_l(uint32_t addr, unsigned int val) 65 | { 66 | volatile auto* ptr = reinterpret_cast(addr); 67 | *ptr = val; 68 | } 69 | 70 | 71 | static inline unsigned int read_l(uint32_t addr) 72 | { 73 | volatile auto* ptr = reinterpret_cast(addr); 74 | return *ptr; 75 | } 76 | 77 | 78 | static inline void reset_cpu() 79 | { 80 | write_l(RSTCTL, SYS_RST); 81 | } 82 | 83 | 84 | static void reset_if_button_pressed() 85 | { 86 | /* Button is pressed if port is low */ 87 | if (!(read_l(GPIO_DATA_1) & GPIO_BUTTON)) { 88 | reset_cpu(); 89 | } 90 | } 91 | 92 | 93 | static void serial_input() 94 | { 95 | if (read_l(UART0_LSR) & UART_LSR_DR) { 96 | /* Something in the RX buffer */ 97 | auto received = read_l(UART0_RBR) & 0xFF; 98 | switch (received) { 99 | case '#': 100 | reset_cpu(); 101 | break; 102 | case '0': 103 | write_l(GPIO_DCLR_1, GPIO_RELAY); 104 | break; 105 | case '1': 106 | write_l(GPIO_DSET_1, GPIO_RELAY); 107 | break; 108 | } 109 | } 110 | } 111 | 112 | /* 113 | * Delay using timer 0 for exact timeout. 114 | * 115 | * Still a dumb delay, but this time with exact timeout. 116 | */ 117 | static void timer_delay(unsigned int delay_in_ms) 118 | { 119 | // start by disabling timer 120 | write_l(T0CTL_REG, 0); 121 | write_l(TGLB_REG, 0); 122 | 123 | write_l(T0LMT_REG, delay_in_ms); 124 | 125 | // enable the timer, set prescale to 1ms 126 | write_l(T0CTL_REG, (1000 << TxCTL_Tx_PRES_OFFSET) | TxCTL_TxEN); 127 | 128 | // Busy wait until timer expires. 129 | while ((read_l(T0CTL_REG) & TxCTL_TxEN) != 0) { 130 | // sleep 131 | } 132 | } 133 | 134 | 135 | /* A dumb delay routine */ 136 | static void delay() 137 | { 138 | const auto milliseconds = 500; 139 | const auto granularity = 100; 140 | static_assert(milliseconds % granularity == 0, "milliseconds should be a multiple of granularity"); 141 | for (auto i = 0; i < (milliseconds / granularity); i++) { 142 | reset_if_button_pressed(); 143 | serial_input(); 144 | timer_delay(granularity); 145 | } 146 | } 147 | 148 | 149 | /* Send the given string to the UART */ 150 | static void print_string(const char *s) 151 | { 152 | for (auto* p = s; *p; ++p) { 153 | while ((read_l(UART0_LSR) & UART_LSR_THRE) == 0) { 154 | /* Wait until there's some space in the FIFO */ 155 | } 156 | write_l(UART0_THR, *p); 157 | } 158 | } 159 | 160 | extern "C" 161 | void entrypoint(void) 162 | { 163 | write_l(GPIO2_MODE, 0x05550550); /* Value determined from original app */ 164 | /* Configure GPIO pins as output */ 165 | write_l(GPIO_CTRL_1, GPIO_RELAY | GPIO_LED_PIN_RED | GPIO_LED_PIN_GREEN); 166 | while (1) { 167 | write_l(GPIO_DCLR_1, GPIO_LED_PIN_RED); 168 | delay(); 169 | print_string("Hello 1\r\n"); 170 | write_l(GPIO_DCLR_1, GPIO_LED_PIN_GREEN); 171 | delay(); 172 | print_string("Hello 2\r\n"); 173 | write_l(GPIO_DSET_1, GPIO_LED_PIN_RED); 174 | delay(); 175 | print_string("Hello 3\r\n"); 176 | write_l(GPIO_DSET_1, GPIO_LED_PIN_GREEN); 177 | delay(); 178 | print_string("Hello 4\r\n"); 179 | } 180 | } 181 | --------------------------------------------------------------------------------