Hey all, figured I'd document this since I got OpenWrt running on the CDS-9010. Hoping someone more competent than I can get this running.
What is it?
The CDS-9010 is a POTS-over-LTE box and 4G router. Available for $25-50 used. Hardware-wise it's basically a standard-ish MT7621 router with an LTE modem slot. Specs: MT7621A @ 880MHz, 128MB RAM, 32MB flash, MT7615 WiFi, Quectel EC25-AF 4G, 2x FXS ports, battery backup.
What works?
The Unielec U7621-06-32M firmware boots and runs! WiFi, ethernet work. LTE card connects but disconnects about 30 seconds after the device boots. It also has issues booting the image off flash, but it will boot over TFTP (you need serial access)
What doesn't work:
LEDs and buttons are mapped wrong (different GPIOs than Unielec). I do not know how to do DTS, but I tried. LTE card also does not work.
Happy to answer questions if anyone else has one of these.
Also side note: the 4G card is Unlocked and works with AT&T, FirstNet, T-Mobile USA, US Cellular, and Verizon Wireless, plus MVNOs using these networks, or more generally, in ITU region 2. (bands 2/4/5/12/13/14/66/71 are supported)
I've created a WikiDevi page with details from my device. I assume you have determined the pin out for the serial connection. If so can you share the details? Also is it using the standard 115200 rate used by most MediaTek devices?
Serial access. Pin 1 starts closest to blue ethernet port, pin 3 (yellow) on header is TX and pin 4 (white) is RX. I used the SIM port as ground point.
Dump (required for default password):
Connect router to PC and serial adapter, press 4 at the U-Boot menu. Have the router sitting at a blank MT7621:#/ uboot prompt, then run dump_flash,py on the serial port - it will dump the stock NAND which can be restored with getimage over TFTP.
Password:
cat CDS-dump1.bin | grep SUPER will give you the super web password.
dump_flash.py:
#!/usr/bin/env python3
import serial
import sys
import time
import re
CHUNK_SIZE = 0x20000 # 128KB per read
SERIAL_PORT = "/dev/ttyUSB0"
BAUD = 115200
def parse_hex_output(output):
"""Extract hex bytes from spi read output"""
# spi read outputs: "XX XX XX XX ..."
# Filter out the command echo and prompt lines
hex_bytes = []
lines = output.split("\n")
for line in lines:
line = line.strip()
# Skip empty, prompts, command echoes
if not line or line.startswith("MT7621") or line.startswith("spi ") or line.startswith("read len"):
continue
# Try to parse as hex bytes
parts = line.split()
for p in parts:
p = p.strip()
if len(p) <= 2 and all(c in "0123456789abcdefABCDEF" for c in p):
if len(p) == 1:
p = "0" + p
try:
hex_bytes.append(int(p, 16))
except:
pass
return bytes(hex_bytes)
def dump_flash(start_offset, total_size, output_file):
ser = serial.Serial(SERIAL_PORT, BAUD, timeout=30)
time.sleep(0.5)
# Clear any pending data
ser.reset_input_buffer()
with open(output_file, "wb") as f:
offset = start_offset
end = start_offset + total_size
while offset < end:
chunk = min(CHUNK_SIZE, end - offset)
cmd = f"spi read 0x{offset:X} 0x{chunk:X}\n"
print(f"Reading 0x{offset:X} - 0x{offset+chunk:X}...", end=" ", flush=True)
ser.write(cmd.encode())
time.sleep(0.1)
# Wait for data - 128KB takes a while
output = b""
deadline = time.time() + 60 # 60s timeout per chunk
while time.time() < deadline:
if ser.in_waiting:
output += ser.read(ser.in_waiting)
time.sleep(0.1)
else:
# Check if we got the prompt back
if b"MT7621 #" in output[-50:]:
break
time.sleep(0.2)
data = parse_hex_output(output.decode("latin-1"))
f.write(data)
print(f"got {len(data)} bytes")
if len(data) != chunk:
print(f"WARNING: expected {chunk} bytes, got {len(data)}")
offset += chunk
ser.close()
print(f"Done! Wrote {output_file}")
if __name__ == "__main__":
if len(sys.argv) < 4:
print("Usage: python dump_flash.py <start_hex> <size_hex> <output.bin>")
print("Example: python dump_flash.py 0x0 0x2000000 full_dump.bin")
sys.exit(1)
start = int(sys.argv[1], 16)
size = int(sys.argv[2], 16)
outfile = sys.argv[3]
dump_flash(start, size, outfile)
The serial console disables after U-BOOT, but you can login with username superadmin and that password at 192.168.25.1 over ssh or the browser web UI. It's pretty well featured, but I would like to be able to help test a real OS.