Monday, March 30, 2009

Engenharia Reversa IB-200 (receptor de tv digital usb)

If you need an english translation of this technical report or additional info on my reverse engineering efforts, please email me at felipe.sanches@gmail.com and I will gladly help people working on further developments towards support of this device under GNU/Linux.

Comprei um receptor de tv digital usb OneSeg da IBAYO, modelo IB-200 e comecei a fazer engenharia reversa nele para tentar fazer um driver para Linux. Este post tem o objetivo de documentar minhas tentativas e convidar mais hackers para ajudar nesta tarefa que está dando nós na minha cabeça, mas que ao mesmo tempo está servindo para eu aprender um monte de coisas novas. Se você é um programador curioso, não tenha medo dos nomes feios que vão aparecer neste post. Eu também me assustei da primeira vez que vi isso, semana passada :-)

Só pra constar aqui (e pra facilitar as buscas no google de gente procurando informações sobre esse dispositivo) ele é reconhecido pelo comando lsusb como ID 5a57:4210 Zinwell

Usei um sniffer USB para Windows chamado usbsnoop. Com ele fiz alguns logs de operação do dispositivo no Windows usando o software proprietário que vem no CD de instalação. Em um desses logs eu cheguei a abrir o programa e assistir um pouco de vídeo para ter certeza de que no log constam todos os comandos necessários para inicializar o dispositivo, sintonizar um canal e exibir o vídeo recebido. No Linux, eu utilizei um script em perl chamado usbsnoop2libusb. Esse script lê um log do usbsnoop e gera automaticamente um código fonte em C utilizando a biblioteca libusb. Esse código, quando executado, repete as operações que constam no log. Obviamente fazer um driver não é tão fácil. Mas o código gerado automaticamente é um primeiro passo. É um esqueleto de código em cima do qual podemos trabalhar.

Rodei o programa gerado automaticamente a partir do log que continha vídeo e não funcionou, mas o led azul do dispositivo chegou a acender. Sendo assim, resolvi olhar com mais calma para o conteúdo do log. Conversei com um amigo meu, o Lucas Villa Real (desenvolvedor da distro GoboLinux), que trabalhou comigo no Laboratório de TV Digital na USP. Ele me contou que eu deveria procurar informações que começassem com o byte 0x47 e que tivessem comprimento de 188 ou 204 bytes. Segundo o Lucas, essas são as características dos pacotes que compõem o transport stream da tv digital brasileira (variante do padrão ISDB-T japonês). Fiz isso visualmente e constatei que de fato haviam alguns pacotes de dados sendo enviados do dispositivo para o PC e que tinham essas características (com 188 bytes de comprimento). Fiz um script em python que filtra o log pegando só esses pacotes e salvando eles todos concatenados em um arquivo de saída binário (no log constam esses dados em ascii usando notação hexadecimal). Depois compilei e instalei o DemuxFS, que é um software livre desenvolvido pelo Lucas, que serve para auxiliar no debugging de sistemas de tv digital ISDB, especialmente no SBTVD (sistema brasileiro de tv digital).

O DemuxFS é um filesystem virtual que exibe informações de um stream de tv digital na forma de entradas em um sistema de arquivos. Ele pode ser usado com um backend chamado filesrc ("file source") que lê o transport stream a partir de um arquivo (como o arquivo que eu gerei usando o script python) para ajudar no debugging, ou pode ser usado com um backend que leia o transport stream a partir de um dispositivo de recepção de tv digital (para ser usado na prática). A minha idéia é inicialmente escrever um backend para o demuxfs ler o transport stream a partir do IBAYO IB-200. Mas no momento isso ainda não foi feito.

Usando o DemuxFS eu consegui com sucesso montar no Linux o arquivo de transport stream que foi extraído do log de operação no Windows do IB-200. Isso é um ótimo sinal!
Observei que os pacotes que compõem o transport stream são enviados por meio de transferências isoch (isócronas). Eu nunca tinha ouvido falar disso antes, mas depois de um pouco de estudo, descobri que se trata de um dos modos de transferência disponíveis no protocolo USB. Nesse modo, a banda máxima de transmissão de dados é menor, mas há a garantia de que os dados serão enviados dentro de um determinado limite de tempo. Especialistas, corrijam-se se eu estiver falando besteira.

Observei com atenção o log e inclusive modifiquei um pouco o script usbsnoop2libusb para que o programa gerado automaticamente exibisse na tela não apenas os dados retornados pelas chamadas feitas, mas também os dados que tinha sido retornados pelas respectivas chamadas originais quando executadas no Windows. Dessa forma eu pude não apenas repetir as chamadas que constam no log, mas também comparar as respostas que o dispositivo nos retorna no Linux pra ver se em algum lugar as reações do dispositivo divergem das respostas esperadas (as respostas que ele deu originalmente no Windows). No geral, as respostas foram bem parecidas com as originais. Houveram pouquíssimas respostas diferentes, mas acredito que elas não sejam um problema, pois eram mensagens onde apenas 1 ou 2 bytes eram diferentes da resposta esperada e isso até faz sentido, pois há certas informações que variam entre uma execução e outra, por exemplo o nível de qualidade do sinal de recepção de um determinado canal de tv. A cada execução, é de se esperar que o nível de qualidade seja diferente e, eventualmente esse dado será passado para o PC, o que pode caracterizar uma dessas pequenas divergências observadas.

Em um determinado ponto surgiu uma divergência grande. No ponto onde deveriam começar a ser recebidos pacotes das transferências de dados isócronas contendo o transport stream, absolutamente nada está sendo retornado. Todas as transferências parecem estar dando timeout por algum motivo. É importante dizer aqui que houve ainda um outro problema. O script usbsnoop2libusb estava reclamando que o tamanho de algumas das tranferencias isoch era maior que o limite de 32kbytes. Achei isso muito estranho, pois no log constam transferências isoch de mais que 32kbytes e foi justamente dessas transferências que eu extraí corretamente o transport stream. Portanto, não é um erro no log e de fato no windows foi possível fazer transferências maiores que 32kb. Tentei aumentar esse limite para 64kb e recebi uma mensagem de erro da biblioteca libusb quando tentei executar o código gerado a partir dessa nova versão do script. Procurei na internet pelo código fonte da biblioteca e lá existe uma condição "retorne mensagem de erro caso usuário tente fazer uma transferência maior que 32kb" sem nenhuma explicação do motivo para a limitação.

Pesquisei mais na internet e descobri que existe um projeto chamado libusb-1.0 que concorre com a libusb mais famosa (que tem pacote no ubuntu, por exemplo) que é a libusb-0.1. Apesar de terem o mesmo nome, aparentemente a 1.0 é uma versão reescrita da 0.1 e segundo consta no website do projeto ela tem várias melhorias quanto a performance e a determinados bugs da 0.1. Sendo assim, resolvi experimentar a tal libusb-1.0. Baixei a versão de desenvolvimento, compilei e instalei. Depois portei o script usbsnoop2libusb para gerar código que use a biblioteca 1.0 (as chamadas de função dessa outra biblioteca são obviamente diferentes). Esta nova versão do script está disponível no meu svn do GoogleCode. Rodando a minha versão do script consegui gerar código com transferências isoch maiores que 32kb e a libusb1.0 não tem esta limitação. Entretanto, isso ainda não resolveu o problema do receptor de tv digital. Mas ouso dizer que provavelmente este é um problema que precisaria ser resolvido de qualquer forma antes de conseguir fazer o dispositivo funcionar, então considero isso como mais um passo dado rumo ao suporte do IB-200 no Linux.

No momento, eu estou lutando com mais uma deficiência do script usbsnoop2libusb. Existe uma operação que consta no log e que o script ainda não implementou a respectiva conversão para código C. A operação é um USB_FUNCTION_ABORT_PIPE. Mais uma vez, fiquei naquela situação "WTF!?!?". Googlei um pouco e li a respeito do tal do "abort pipe". No protocolo USB, pipes são abstrações de canais de comunicação entre o host (driver no PC) e um endpoint (dispositivo). Por algum motivo que ainda não é muito claro pra mim, existe a possibilidade do host pedir para um determinado pipe ser abortado, ou reiniciado. Imagino que isso seja feito quando um pipe não vai mais ser usado, ou então quando um pipe será reconfigurado. Mais uma vez, peço o palpite dos especialistas :-)

Enfim... uma dessas chamadas de "abort pipe" acontece bem no finalzinho do log que corresponde ao momento em que fechei o programa no Windows e isso faz todo sentido, pois ao fechar o programa, é de se esperar que o pipe usado para o fluxo de dados seja fechado. Mas a outra ocorrência desse comando acontece um pouco antes do início das transferências isoch problemáticas. Sendo assim, acho que não ter o "abort pipe" alí pode ser uma das razões para as transferências isoch não estarem funcionando pra mim no momento no Linux.

A minha estratégia agora é tentar descobrir como se faz pra implementar suporte a "abort pipe" no script usbsnoop2libusb e ver se isso resolve a zica. O problema é que eu não tenho a menor idéia de como se faz para enviar um comando "abort pipe" usando a libusb. Assim que eu tiver mais avanços eu aviso. Por enquanto, aguardo comentários dos hackers de plantão :-)

If you need an english translation of this technical report or additional info, please email me at felipe.sanches@gmail.com and I will gladly help people working on further developments towards support of this device under GNU/Linux.

8 comments:

  1. Viajei total Juquinha!! hahaha... Mas muito louco, espero que você tenha sucesso na sua empreitada, e que consiga trazer mais essa melhoria para a comunidade do SL! =)

    BTW (by the way), sugiro que coloque aquele parágrafo em inglês no começo do post.... =) Acho que no final talvez tenham pessoas não cheguem a vê-lo.

    Abs!

    ReplyDelete
  2. Coloca o endereço do seu googlecode aí também. Vamos hackear isso quinta.

    ReplyDelete
  3. Cheguei aqui pesquisando sobre recepitores de TV digital p/ linux, tenho conhecimento em C/C++ mais fucei em dev de drivers, mas com a parte de renderização creio que posso ajudar.

    e força na iniciativa.

    boa sorte
    newbie_x11@yahoo.com.br

    ReplyDelete
  4. E aí Juca,

    A função da libusb-1.0 que envia o USB_FUNCTION_ABORT_PIPE é a libusb_cancel_transfer(). Comecei a fazer o esqueleto do driver agora a noite. Só tô esperando teus logs :)

    ReplyDelete
  5. Vamos lá, o USB tem todo um modelo com várias camadas (creio que 3, mas podem ser 4, ou até mais) que permitem uma certa abstração semelhante ao modelo OSI.
    Todo dispositivo usb precisa escutar num canal específico, que é o 0, e à partir dele o controlador assinala ao dispositivo um endereço no qual ele vai escutar dali em diante.
    USB tem vários tipos de transferência: INTERRUPT, que é o tipo do pacote mais emergencial, típico de quando você precisa passar uma informação rápido e sem usar muita banda (como mouse, teclado..), BULK que é a transferência de muita banda e baixa prioridade (pen drives vão aqui) e ISOC(hronous) que é para aplicações que dependem de banda garantida, como placas de som, tv e etc. (ACHO que havia um quarto modo, mas não lembro... A especificação usb fala tudo a respeito disso e é um documento free) No caso de transferências isoc, o dispositivo vai ter chance de transmitir informações 1000 vezes por segundo, portanto um buffer se faz necessário.

    Uma vantagem interessante desse modelo de camadas usb é que você pode ter software num Windows rodando numa máquina virtual debaixo de um host Linux que não suporta tal dispositivo.

    Existem vários motivos pelo qual o dispositivo pode não funcionar replicando a conversa dele inúmeras vezes, eu sugeriria você repetir a captura várias vezes com o mesmo canal sintonizado e tentar traçar um paradigma desses valores que mudam, depois tentar alterar os valores e ver no que isso implica, é o básico da engenharia reversa que você já deve conhecer bem....

    Sorry pelo comentário longo, qquer coisa me chama no gtalk!

    ReplyDelete
  6. Que bom que deu o passo inicial, comprei um c3tech TV-U200 Digital e analogica, vou tentar fazer um drive também partindo das sua pesquisa!

    ReplyDelete
  7. Aproveitando, nós (Juca e eu) continuamos a fazer engenharia reversa desse driver. Nós temos código e informações disponíveis no http://groups.fsf.org/wiki/LinuxLibre:ISDB_USB_ZINWELL

    Entre as pendências, ainda não estão claros os comandos enviados ao demodulador (o Juca está tentando usar um sniffer i2c que ele comprou recentemente pra isso) e se os 56 bytes que identificamos realmente correspondem ao firmware. Temos alguns problemas também com a leitura do transport stream, já que a callback da transferência isócrona não está sendo chamada ou, quando é chamada, retorna com pacotes de zero bytes.

    ReplyDelete
  8. Fala brother blz, bom boa sorte ai nessa tentativa ai, mas na verdade gostaria que você me desse uma força, não consigo baixar o drive desse receptor para usar no win7, encontrei 2 links porém em nenhum dos 2 ele conclui o download e sempre fico com o arquivo incompleto sem ter como utilizar o receptor, se poder me ajudar ai eu agradeço, foram esses links que eu achei:
    http://www.zinwell.com.br/isdb/ZT-UB10-v114.zip
    http://www.zinwell.com.br/isdb/ZT-UB10-v114.exe

    Obg Atenciosamente
    Delmo (delmo.fernandes@hotmail.com)

    ReplyDelete