GC150ATA
descrição geral
Display LCD VGA modelo LiteON SYS-GC150ATA
É um painel LCD comum. Meu interesse nele está em reprogramar o firmware pra fazer coisas inusitadas. Como ele tem um sistema de OSD (On-Screen Display), podemos fazer animações por cima do vídeo que estiver vindo pelo conector VGA. Dá também pra fazer isso de forma interativa, já que o display tem 4 botões (além do liga-desliga) na parte da frente.
Principais componentes
CPU
MYSON mtv312mv64 - variante de 8051
Datasheet em: http://www.telemaster.ru/minitv/pdf/MTV312MV64.pdf
chip de vídeo
Controlador GENESIS gmZAN1
Datasheet em: http://www.datasheetarchive.com/dlmain/Datasheets-312/182051.pdf
EPROM
Memória EPROM serial (I2C) ATMEL 24C21
Datasheet em: http://www.datasheetcatalog.com/datasheets_pdf/2/4/C/2/24C21.shtml
Estratégias de Engenharia Reversa
Dump de fw
Extrair o firmware original é um objetivo importante, pois, com isso, é possível implementar um emulador para o dispositivo, no qual podemos fazer um número de testes com muito mais flexibilidade do que quando temos apenas o hardware original em mãos. Outra vantagem de se ter a extração de uma imagem de firmware é poder estudar o disassembly das rotinas originais do dispositivo e, com isso, aprender mais detalhes sobre o funcionamento de todos os componentes com os quais o microcontrolador interfaceia. Segue abaixo uma listagem exaustiva de todas as técnicas que eu pude conceber para se fazer o dump do firmware deste monitor LCD.
Via Soquete PLCC
Desoldar o chip, conectá-lo a um conversor PLCC→DIP e utilizar o TL866CS para fazer o dump.
CONTRAS: Procedimento invasivo. Ter que soldar um soquete no lugar, e depois ter que remover/reinserir toda vez que for atualizar o firmware. Pode danificar o componente pelo esforço mecânico.
Em 22 de agosto de 2014, Felipe Sanches e Gabriel Almeida de Souza desoldaram o chip usando um soprador térmico. O chip foi colocado num adaptador PLCC44→DIP40 e tentamos usar o TL866CS para fazer o dump. O resultado (usando o algoritmo de dump "87C51 @PLCC44") foi um arquivo cheio de 0xFF, ou seja, um dump incorreto.
Temos receio de que o chip possa ter sido danificado pelo calor do soprador térmico ou também pelo uso de um algoritmo de leitura incorreto.
Em 30 de agosto de 2014, soldamos um soquete PLCC e reinserimos o chip, mas o monitor não voltou a ligar. Revisamos todas as conexões do soquete de acordo com o esquemático e detectamos alguns pontos com falhas nas solda. Após corrigir todos os pontos de solda, re-inserimos o chip e o aparelho ligou. O botão "power" e o led indicador funcionam e a mensagem "NO SIGNAL, PLEASE CHECK CABLE" aparece na tela, o que leva a crer que o firmware está sendo executado para controlar essas ações. Entretanto a mensagem aparece com vários caracteres errados. E a cada vez que o monitor é ligado, o erro tem um padrão diferente. Suponho que o chip do microcontrolador tenha sido de fato danificado pelo calor.
Em 07 de setembro de 2014, eu (Felipe Sanches) resolvi partir para a abordagem de utilizar um sniffer para registrar os valores numéricos dos comandos enviados pelo microcontrolador para o chip de vídeo, na esperança de, mesmo com um eventual mal-funcionamento do microcontrolador, conseguir dados mais precisos sobre o funcionamento do chip de vídeo. Existe documentação oficial do chip de vídeo fornecido pelo próprio fabricante. Entretanto, os endereços de memória e os valores dos registradores são omitidos no datasheet sob a alegação de que os procedimentos de configuração são feitos pela API fornecida pelo fabricante. Analisar a implementação dessa API poderia ser uma forma de descobrir o mapeamento de valores dos registradores. Entretanto, não tenho acesso a essa API/biblioteca. Provavelmente isso é fornecido apenas para parceiros comerciais do fabricante do chip de vídeo.
Trabalhando com a hipótese de que o microcontrolador havia sido danificado, observei que o display continuava exibindo caracteres gráficos na tela e que portanto, pelo menos parte das informações ainda estavam sendo enviadas para o chip de vídeo. Ao tentar interfacear um sniffer (implementado com um Arduino), acabei conseguindo fazer o LCD voltar a funcionar corretamente. Aparentemente o problema era um mal-contato ou uma solda mal feita no pino do soquete PLCC correspondente à porta P1.0 do 8051. Ao manusear o soquete, o problema foi resolvido e com isso elimina-se a teoria de que o microcontrolador tenha sido danificado pelo calor do soprador. De fato, liguei até mesmo um cabo VGA e o display está em perfeitas condições.
Via I2C
A placa mãe tem um conector de 4 vias com a label P303. Foi a primeira coisa que me saltou aos olhos quando abri o equipamento pois era o único conector da placa que não tinha cabo nenhum conectado. Portanto estava bem claro pra mim que deve se tratar de uma interface de programação do dispositivo que é usada na fábrica pra gravar o firmware inicial.
Os sinais desse conector são:
- VCC (5V)
- SCL
- SDA
- GND
As duas vias do barramento I²C estão ligadas aos pinos HSCL e HSDA do 8051.
O datasheet descreve o procedimento para apagar a memória flash por meio da interface I²C, mas imagino que dê também pra fazer o dump. Um dos comandos se chama "data read" e aparentemente é usado para verificação de uma gravação.
O datasheet diz também que o slave address da interface I²C é especificado pelo firmware por meio de uma escrita ao SFR (special function register) ISPSLV (In-System Programming Slave Address)
<quote> After Power On/Reset, The MTV312M runs the original Program Code. Once the S/W detects an ISP request (by key or IIC), S/W can accept the request following the steps below:
- Clear watchdog to prevent reset during ISP period.
- Disable all interrupt to prevent CPU wake-up.
- Write IIC address of ISP slave to ISPSLV for communication.
- Write 93h to ISP enable register (ISPEN) to enable ISP.
- Enter 8051 idle mode.
</quote>
Como não temos acesso ao firmware (ainda) não temos como saber qual é o endereço usado. Então resolvi tentar todos! De acordo com a descrição do protocolo de programação da Flash, são 6 bits de endereços possíveis (apesar de I²C permitir 7 bits, um deles - o menos significativo - é usado pra indicar se é acesso de comando ou acesso de dado).
Usando a porta VGA
Isso é genial! http://www.instructables.com/file/FZTD0AYFFAOSLO5
Tentei fazer, mas ainda não rolou. :-P
O conector VGA contém uma interface I²C (chamada DDC - Display Data Channel) que é usada para que o monitor informe ao PC suas características como resolução, profundidade de cores, etc. E também permite setar a intensidade de brilho, ajuste de componentes de cor, etc...
Aí a gente pode simplesmente escrever código C que manipula de forma arbitrária essa "porta" I²C pra controlar o que a gente quiser. O adaptador é super simples. É só um conector VGA com os 4 fios que nos interessam (GND, VCC, SDA e SCL).
Inicialmente não consegui baixar o código do cara, por que o site dele estava extremamente lerdo. Então copiei uns trechos do source do ddccontrol-0.4.2 e adaptei para o meu propósito, implementando um algoritmo de dump com base na descrição do datasheet e usando a porta VGA do meu notebook como bridge i2c.
Depois de fazer modprobe i2c-dev e modprobe i915, o comando i2cdetect -l me listou várias interfaces /dev/i2c-0 até /dev/i2c-6.
Fiquei na dúvida de qual era a correta mas a minha intuição apontava para a /dev/i2c-1 (descrita como "i915 gmbus vga") e para a /dev/i2c-6 (descrita como "DPDDC-B") Então fiquei rodando meu código alternando entre essas duas opções pra ver no que dava.
No dia seguinte consegui baixar o código do site do cara, mas ainda não tive tempo de testá-lo.
Para tirar a dúvida sobre qual o /dev correto, dei uma olhada no código do driver da placa Intel i915 no kernel Linux e vi que o nome "gmbus vga" corresponde ao GPIOA que é usado para controlar o DDC, então o /dev correto é mesmo o /dev/i2c-1 no meu laptop.
Usando o USBTinyISP
https://learn.adafruit.com/usbtinyisp/
Pensei na possibilidade de modificar o dispositivo para se comunicar por I²C também. Ele é originalmente projetado pra falar SPI (Serial Peripheral Interface).
Cheguei a montar um e compilei o firmware a partir do source, mas quando gravei ele no attiny2313, não funcionou. Quando gravei de volta a imagem de fw pré-compilada disponivel pra download no site, voltou a funcionar. Talvez a versão do compilador avr-gcc que estou usando não esteja a ideal para geração de código que caiba na flash pequena desse dispositivo.
<quote> You must use avr-gcc v3.4.6 and avr-libc v1.4.4 as part of Winavr-20060421 to compile the firmware. Please do not post to the forums asking for help on how to compile or burn the firmware. </quote>
Achei meio esquisito... Meu avr-gcc 4.5.3 é mais recente que isso (e obviamente eu não uso Windows) mas é isso que eles indicam no site.
Usando o BusPirate
http://dangerousprototypes.com/docs/Bus_Pirate
Inicialmente eu não usei o BusPirate por que eu não sabia onde ele estava. Ficou "perdido" em alguma gaveta no Garoa durante 2 anos e em 11/julho/2014 o DQ, ao ver minhas mensagens na lista de emails do Garoa, lembrou onde estava e me avisou.
O legal do BusPirate é que dá pra fazer uns scripts pra automatizar o sniffer... Além disso ele já tem uns scripts prontos para, por exemplo, escanear e detectar os endereços dos dispositivos i2c que estiverem conectados ao barramento. Isso provavelmente será útil dado que o manual de serviço do monitor não informa qual é o endereço utilizado para update de firmware.
O BusPirate também permite selecionar o clock do protocolo i2c. Dado que odatasheet do 8051 em uso nesse monitor indica que para o protocolo de carga de firmware é usado "IIC Bus clock rates up to 140KHz", a opção de i2c em 100KHz do BusPirate deve dar conta de estabelecer a comunicação.
Documentarei aqui os resultados assim que eu fizer o teste.
Usando algum outro bridge USB->I2C
Preciso comprar algum desses pra tentar.
Acabei descobrindo um projeto bacana chamado Sigrok
Teorias
Aqui eu listo algumas teorias que tenho para tentar explicar por que não está funcionando ainda a tentativa de se fazer dump do firmware via I²C.
detalhes do I²C
Talvez a frequencia do clock (SCL) gerado pela placa de vídeo Intel i915 esteja fora da faixa de frequencias de clock suportadas pela interface de atualização de firmware do 8051. <quote>"IIC Bus clock rates up to 140KHz"</quote>
Talvez haja alguma leve divergência entre as implementações do protocolo da placa VGA e do 8051. Talvez tenha a ver com bits de acknownledge ou condições de Start/Restart ou alguma nuance na temporização dos sinais. Nesses momentos eu queria muito ter em mãos um analisador lógico pra de fato ver o que se passa no barramento I²C durante a execução desses testes.
interface desabilitada ?
Talvez o fabricante tenha usado a interface de update de firmware via i2c apenas durante a etapa de desenvolvimento do produto, mas tenha a desabilitado após colocar o produto no mercado. Se for isso mesmo, a única forma de modificar o firmware seria desolvando o chip e usando um gravador convencional, como o TL866.
faltou sinalização elétrica para habilitar ?
O datasheet também fornece uma informação interessante:
<quote>Writer Mode: RESET=1 & DA9=0 & DA8=1</quote>
Isso equivalente a:
pino 7 = 1 pino 38 = 0 pino 39 = 1
Preciso verificar na PCB se existe algum jumper ou algum test-point que possa ser usado para induzir essa condição.
faltou comando i2c para habilitar ?
Um chinês com um inglês muito ruim publicou um artigo em que descreve o protocolo de programação desse chip MTV312 numa placa parecida com a desse LCD (mas não idêntica).
O protocolo dele coincide em alguns pontos com o que consta no datasheet, mas também tem algumas coisas diferentes. TODO: verificar.
Tem também uns códigos-fonte disponíveis online com rotinas que colocam o microcontrolador em modo ISP.
detecção de ISP
O datasheet menciona que o fw detecta uma requisição de ISP (In-System Programming) e coloca o 8051 em modo idle (após preparar o terreno para uma atualização de firmware).
<quote> Once the S/W detects an ISP request (by key or IIC), S/W can accept the request </quote>
Como ocorre essa detecção? O datasheet fala de detecção por meio de "key or IIC".
Isso nos faz acreditar que pode haver algum combo mágico de teclas que o usuário pode fazer que indique para o 8051 que o dispositivo irá receber uma atualização. Tentei várias combinações, mas não obtive resultado satisfatório. Apenas encontrei uma tela secreta no OSD ao ligar o monitor com todos os 4 botões pressionados. Mas mesmo assim, continuei não conseguindo nenhum resultado pela interface i2c. Além disso, o manual de serviço deveria mencionar esses combos mágicos, mas nada consta...
Quando o datasheet do 8051 fala que o sw pode detectar uma requisição de ISP por meio de IIC (i²c), talvez isso signifique que algum comando mágico enviado pela i²c habilita a interface de update de fw. Mas isso também é algo que deveria constar ou no datasheet do microcontrolador, ou no service manual. Mas se for o caso, guardaram como segredo pra ser usado só na fábrica. Ter acesso a algum software de manutenção do monitor seria útil para desvendar essas coisas.
Talvez seja só um dos comandos do protocolo que habilite a interface. Mas fiquei com medo de testar com o comando "Program" por receio de que isso pudesse acadar apagando a flash toda durante o teste... Por outro lado, talvez Command=Program seguido por Data Read resulte em leituras corretas do fw interno, sem apagá-lo no processo (supondo que só apague quando se envia um Data Write)...
E se for direto na porta VGA ?
<quote> Only one two-pin IIC bus (shared with DDC2) is needed for ISP in user/factory mode. </quote>
O fato dessa frase no datasheet mencionar que o ISP compartilha o barramento I²C do DDC2, levanta a questão. Será que a programação do firmware pode ser feita direto pelo cabo VGA, em vez de ser por meio do conector interno P303 ?
Sniff de barramento de dados
Já que estamos tendo dificuldade em ler o firmware original, podemos partir para outras frentes de investigação. Uma técnica muito útil em engenharia reversa é a "escuta" da comunicação entre dispositivos em um barramento. Utiliza-se o nome "sniffer" para os equipamentos que podem fazer esse tipo de monitoramento.
Primeira tentativa
Em 7 de setembro de 2014 eu implementei um sniffer para a interface que interliga o microcontrolador 8051 e o chip de vídeo gmZAN1. O sniffer foi implementado rapidamente utilizando um Arduino. Fiz 3 coletas de dados utilizando esse sniffer, mas tive que sair correndo pra ir pra casa por conta de alguns compromissos pessoais. No dia seguinte, escrevi alguns scripts em Python para analisar os dados coletados, na esperança de encontrar strings de texto como "CHECK CABLE" (que é o que aparecia escrito na tela no momento em que os dados foram coletados). Não tive sucesso nessa tentativa e, por isso, resolvi revisar atenciosamente o código dos scripts python e também o código do sniffer. Descobri que havia um erro de programação no meu sniffer (em vez de coletar os bits HDATA0, HDATA1, HDATA2 e HDATA3, eu estava erroneamente coletando 4 vezes o mesmo bit HDATA0 :-P
Corrigi isso e pretendo voltar a fazer o experimento assim que eu voltar ao Garoa ainda nesta semana. Entretanto, observando novamente os logs do sniffer agora sob a luz da descoberta do bug no sniffer, já percebi que o tempo de execução da função digitalRead do arduino é lento demais e que, com isso, dados deixarão de ser lidos no barramento, pois os sinais alternam mais rapidamente que as leituras do Arduino. Para solucionar isso, precisarei reimplementar o código do sniffer utilizando rotinas de baixo nível do microcontrolador AVR. Espero que fazendo acesso direto à porta D, já seja suficiente para dar conta de fazer uma leitura correta. Se não der, terei que apelar para escrever as rotinas do firmware em assembly, ou até mesmo projetar um pequeno circuito digital contendo hardware dedicado a fazer um log íntegro dos dados em um chip de memória RAM auxiliar que eu possa depois lentamente ler usando o Arduino.
Segue abaixo o código atual do sniffer (que não dá conta de ser rápido o suficiente e perde leituras):
#define hdata0 2 #define hdata1 3 #define hdata2 4 #define hdata3 5 #define hclk 6 #define hfs 7 void setup(){ Serial.begin(9600); pinMode(hdata0, INPUT); pinMode(hdata1, INPUT); pinMode(hdata2, INPUT); pinMode(hdata3, INPUT); pinMode(hclk, INPUT); pinMode(hfs, INPUT); } boolean old_hclk=HIGH, new_hclk; void loop(){ if (digitalRead(hfs)==HIGH){ new_hclk = digitalRead(hclk); if (new_hclk==HIGH && old_hclk==LOW) /*detect HCLK rising edge*/ { int value; value = digitalRead(hdata0) == HIGH ? 1 : 0; value += digitalRead(hdata1) == HIGH ? 2 : 0; value += digitalRead(hdata2) == HIGH ? 4 : 0; value += digitalRead(hdata3) == HIGH ? 8 : 0; Serial.write('a'+value); } old_hclk = new_hclk; } }
Segunda tentativa
TODO: Reimplementar o sniffer seguindo as seguintes diretrizes:
- Fazer o log em um vetor de bytes e só depois de ler N amostras, enviar tudo de uma vez só para a porta serial. A razão para essa modificação é não gastar tempo com transmissão de dados pela porta serial, pois nesse meio tempo, podemos estar perdendo amostras de sinais do barramento.
- Fazer acesso direto à porta D do AVR, que é onde estão os pinos digitais de 0 a 7 do Arduíno. Razão: a função digitalRead é muito lenta.
TODO: implementar e colar aqui o código
análise do fluxo de dados
No momento estou trabalhando com o seguinte código, para analisar os dados trocados entre o 8051 e o gmZAN1:
#This is the mapping of values for the chars (according to gmZAN1 datasheet, page #34): char_map = "0123456789ABCDEFGHIJKLMNOPQRSTUVXZabcdefghijklmnopqrstuvxzw" invert_bits=False #maybe the sniffed bus has inverted bit-values? result = "" def log_command(address, value, code): global result try: result += char_map[value & 0x7F] #restrict to 7 bits of data for font-map select (according to gmZAN1 datasheet, page #32) except IndexError: result += "." def getValue(c): #this is the data format for each 4bit sample used by the sniffer I implemented using an Arduino return int(ord(c) - ord('a')) #each frame corresponds to 6 write cycles (of 4 bits each) #as we don't have sync markers in the sniffed data stream, we'll have to try all 6 possible offsets # for interpreting the data: for offset in range(6): global result cmds = open("sniff2.txt").readline().strip() cmds = cmds[offset:] #this gets rid of a few bytes in order to start interpreting a command with an offset in the data stream print "offset=%d:" % offset result = "" while len(cmds) >=6: cmd = cmds[:6] cmds = cmds[6:] if invert_bits: new_cmd = "" for i in range(6): new_cmd += chr(ord(cmd[i]) ^ 0xFF) cmd = new_cmd value = getValue(cmd[3]) + 16*getValue(cmd[4]) + 256*getValue(cmd[5]) address = getValue(cmd[0]) + 16*getValue(cmd[1]) + 256*(getValue(cmd[2]) & 3) code = (getValue(cmd[2]) & 0xc) >> 2 log_command(address, value, code) print result