desenvolvimento de um modem plc em plataforma linux

Transcrição

desenvolvimento de um modem plc em plataforma linux
UNIVERSIDADE FEDERAL DO RIO GRANDE DO SUL
ESCOLA DE ENGENHARIA
DEPARTAMENTO DE ENGENHARIA ELÉTRICA
CURSO DE GRADUAÇÃO EM ENGENHARIA ELÉTRICA
DIEGO CABERLON SANTINI
DESENVOLVIMENTO DE UM
MODEM PLC EM PLATAFORMA
LINUX
Porto Alegre
2006
DIEGO CABERLON SANTINI
DESENVOLVIMENTO DE UM
MODEM PLC EM PLATAFORMA
LINUX
Projeto de Diplomação apresentado ao Departamento de Engenharia Elétrica da Universidade
Federal do Rio Grande do Sul como parte dos
requisitos para a obtenção do tı́tulo de Engenheiro Eletricista.
ORIENTADOR: Prof. Dr. Walter Fetter Lages
Porto Alegre
2006
DIEGO CABERLON SANTINI
DESENVOLVIMENTO DE UM
MODEM PLC EM PLATAFORMA
LINUX
Este Projeto foi julgado adequado para a obtenção dos créditos da Disciplina Projeto de Diplomação do Departamento de Engenharia Elétrica
e aprovado em sua forma final pelo Orientador
e pela Banca Examinadora.
Orientador:
Prof. Dr. Walter Fetter Lages, UFRGS
Doutor pela Instituto Tecnológico de Aeronáutica – São
José dos Campos, Brasil
Banca Examinadora:
Prof. Dr. Walter Fetter Lages, UFRGS
Doutor pelo Instituto Tecnológico de Aeronáutica – São José dos Campos,
Brasil
Prof. Dr. Renato Ventura Bayan Henriques, UFRGS
Doutor pela Universidade Federal de Minas Gerais – Belo Horizonte, Brasil
Prof. Dr. Romeu Reginatto, UFRGS
Doutor pela Universidade Federal de Santa Catarina – Florianópolis, Brasil
Chefe do DELET:
Prof. Dr. Roberto Petry Homrich
Porto Alegre, dezembro de 2006.
DEDICATÓRIA
Dedico esse trabalho para todas as pessoas que diretamente ou indiretamente
contribuı́ram para o meu crescimento intelectual ao longo da minha vida acadêmica.
AGRADECIMENTOS
Primeiramente gostaria de agradecer aos meus pais, Gilberto Santini e Maria Ines
Caberlon Santini, por toda a estrutura que me têm oferecido ao longo da minha vida
e especialmente pelo o esforço que fizeram para que eu me tornasse um Engenheiro
Eletricista.
Também agradeço ao meu irmão, Thiago Caberlon Santini, pelo incentivo que
meu deu ao longo da minha vida.
Ao meu orientador, Walter Fetter Lages, pelos três anos sobre a sua orientação
pelo qual muito devo o meu atual conhecimento.
Ao meu supervisor, Julio Klemm, pela a oportunidade me dada para consolidar
meus conhecimentos durante o estágio.
À Universidade Federal do Rio Grande do Sul e aos seus funcionários pela constante infra-estrutura oferecida, e pela a oportunidade de estudar gratuitamente em
uma das melhores universidades do Brasil.
Por fim, aos meus colegas que me apoiaram durante a faculdade e que, com
certeza, não chegaria até aqui sem eles.
RESUMO
Este trabalho trata dos passos do desenvolvimento de um modem PLC que está
sendo construindo no LASCAR, utilizando o chip ST7538. Neste trabalho consta
uma breve descrição a respeito da comunicação PLC. A seguir serão apresentadas as
caracterı́sticas do hardware que foi desenvolvido, incluindo a parte digital e analógica
do modem. Finalmente será explorado a parte de software do mesmo, com uma
introdução a sistemas operacionais e o linux, com o desenvolvimento de driver TTY
para plataforma Linux-2.6.17 com os protocolos HDLC, KISS, TCP/UDP, IP.
Palavras-chave: PLC, modem PLC, Linux, drivers TTY, ST7538.
ABSTRACT
This work deals with the steps for chip development of a PLC modem that is
being built at LASCAR, with ST7538. This work present a description of PLC communication, its advantages and disadvantages. In the sequence, the characteristics
of the hardware are described, including digital and analogical components. Finally
the software will be explored, with an introduction to operational system and Linux
and with the development of a TTY driver for Linux-2.6.17 with the HDLC, KISS,
TCP/UDP and IP protocols.
Keywords: PLC, modem PLC, Linux, drivers TTY, ST7538.
SUMÁRIO
LISTA DE ILUSTRAÇÕES . . . . . . . . . . . . . . . . . . . . . . . . . .
15
LISTA DE TABELAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
LISTA DE ABREVIATURAS . . . . . . . . . . . . . . . . . . . . . . . . .
19
1 INTRODUÇÃO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.1 Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.2 Organização do trabalho . . . . . . . . . . . . . . . . . . . . . . . . 22
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
23
23
23
24
24
26
26
26
3 HARDWARE DO MODEM PLC . . . .
3.1 Parte Analógica . . . . . . . . . . . .
3.1.1
ST7538-PowerLine FSK Transceiver
3.1.2
Componente Analógicos . . . . . . .
3.2 Parte Digital . . . . . . . . . . . . . .
3.2.1
Interface com a Porta Paralela . . . .
3.3 Mudança de Hardware . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
29
29
29
32
32
32
35
4 SOFTWARE DO MODEM PLC . .
4.1 Plataforma de Desenvolvimento
4.1.1
Sistemas Operacionais . . . . . .
4.2 Linguagem C . . . . . . . . . . .
4.2.1
Programando no Kernel do Linux
4.3 Especificações de Hardware . .
4.4 Kernel Space: Driver TTY . .
4.4.1
Funções de Acesso ao Hardware .
4.4.2
Funções de Protocolos . . . . . .
4.4.3
Funções de Driver TTY . . . . .
4.5 User Space: kissattach . . . . .
. . . . . . . . . . . . . . .
Software . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
37
37
38
40
40
42
42
43
44
49
50
2 TECNOLOGIA PLC . . . . . . . .
2.1 Introdução a Tecnologia PLC
2.1.1
X-10 Uma Primeira Idéia . . . .
2.1.2
Problemas da Tecnologia PLC .
2.1.3
Vantagens da Tecnologia PLC .
2.2 Tipos de Redes PLC . . . . . .
2.2.1
Last Mile Access . . . . . . . .
2.2.2
Last Inch Access . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
do
. .
. .
. .
. .
. .
. .
. .
. .
. .
CONCLUSÃO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
55
REFERÊNCIAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
APÊNDICE A
DIAGRAMAS ESQUEMÁTICOS DO MODEM PLC . . .
59
APÊNDICE B
ARTE FINAL DO PCB DO MODEM PLC . . . . . . . .
63
APÊNDICE C
CÓDIGO FONTE DO DRIVER DO MODEM PLC
. . .
69
APÊNDICE D HEADER DA PLACA, LPMODEM.H . . . . . . . . . . .
95
APÊNDICE E
97
5
HEADER DO ST7538, ST7538.H . . . . . . . . . . . . .
LISTA DE ILUSTRAÇÕES
Figura
Figura
Figura
Figura
Figura
2.1:
2.2:
2.3:
2.4:
2.5:
Modem X-10. . . . . . . . . . . . . . . . . . . . .
Rede Ethernet. . . . . . . . . . . . . . . . . . . .
Rede usando a linha telefônica. . . . . . . . . . .
Rede usando a rede de energia elétrica. . . . . . .
Last inch access, ou acesso dentro das residências.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
24
25
25
26
27
Figura 3.1: Diagrama de blocos do ST7538. . . . . . . . . . . . . . . . . . . . 30
Figura 3.2: Modulação FSK. . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Figura
Figura
Figura
Figura
Figura
Figura
Figura
Figura
Figura
Figura
Figura
Figura
Figura
Figura
Figura
Figura
Figura
4.1: Divisão do kernel do Linux. . . . . . . . . . . . . . . . . . . . . .
4.2: Adicionando um Driver no Kernel. . . . . . . . . . . . . . . . . .
4.3: Fluxograma da leitura e escrita de dados no ST7538. . . . . . . .
4.4: Seqüência de escrita no registrador interno do ST7538. . . . . . .
4.5: Fluxograma do software de leitura e escrita de dados no ST7538.
4.6: Leitura do registrador interno do ST7538 realizada pelo driver. . .
4.7: Fluxograma do software de leitura e escrita de dados no ST7538.
4.8: Envio de dados para o ST7538. . . . . . . . . . . . . . . . . . . .
4.9: Estrutura do Frame HDLC. . . . . . . . . . . . . . . . . . . . . .
4.10: Envio da seqüência de flags para o ST7538. . . . . . . . . . . . .
4.11: Envio da seqüência de flags para o ST7538 (zoom). . . . . . . . .
4.12: Configuração do Arquivo /etc/ax25/axports. . . . . . . . . . . . .
4.13: Utilizando o kissattach. . . . . . . . . . . . . . . . . . . . . . . .
4.14: Display do ifconfig. . . . . . . . . . . . . . . . . . . . . . . . . . .
4.15: Modem Funcionando. . . . . . . . . . . . . . . . . . . . . . . . . .
4.16: Transmissão de dados para o modem PLC. . . . . . . . . . . . . .
4.17: Recepção de dados pelo o modem PLC. . . . . . . . . . . . . . .
39
41
43
44
45
45
46
46
47
47
48
51
51
52
53
53
54
Figura 5.1: Foto do Modem PLC. . . . . . . . . . . . . . . . . . . . . . . . . 55
LISTA DE TABELAS
Tabela
Tabela
Tabela
Tabela
Tabela
3.1:
3.2:
3.3:
3.4:
3.5:
Ligação
Ligação
Ligação
Ligação
Ligação
do registrador de controle.
dos pinos de entrada. . . .
dos pinos de saı́da. . . . .
nos multiplexadores. . . .
dos pinos de saı́da. . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
33
33
34
35
35
Tabela 4.1: Tipo de frame do protocolo KISS. . . . . . . . . . . . . . . . . . . 49
LISTA DE ABREVIATURAS
ASK
Amplitude-Shift Keying
CRC
Cyclic Redundancy Check
CSMA
Carrier Sense Multiple Access
EXT2
Second Extended File System
EXT3
Third Extended File System
FAT
File Allocation Table
FEND
Frame End
FESC
Frame Escape
FR
Frame Relay
FSK
Frequency-shift keying
GPL
General Public License
HD
Hard Data
HDLC
High Level Data Link Control
IA32
Intel Architecture, 32-bits
IP
Internet Protocol
JFFS2
The Journalling Flash File System, version 2
KISS
Keep it Simple Stupid
LASCAR Laboratório de Sistemas de Controle, Automação e Robótica
NTFS
New Technology File System
OSI
Open Systems Interconection
PCB
Printed Circuit Board
PLC
PowerLine Communication
PLIP
Parallel Line Internet Protocol
PPP
Point-to-Point Protocol
PSK
Phase-Shift Keying
SLIP
Serial Line Internet Protocol
TCP
Transmission Control Protocol
TEND
Transposed Frame End
TFESC
Transposed Frame Escape
TTY
Teletype
xDSL
Digital Subscriber Line
21
1
INTRODUÇÃO
A tecnologia PowerLine Communication PLC permite transmitir dados e informações utilizando os cabos da rede elétrica como meio fı́sico. Possuindo assim como
principal vantagem sobre as outras tecnologias de transmissão o fato da utilização
de uma infra-estrutura fı́sica já existente, pois em praticamente todos os lugares
há uma rede elétrica instalada. Isso a torna uma solução prática para as seguintes
necessidades:
1. automação residencial, pois, como por necessidade, quase todo o equipamento
já está ligado à rede elétrica.
2. telemetria.
3. internet banda larga.
Visando tais aplicações, especialmente a telemetria, foi implementada no LASCAR uma placa de desenvolvimento de um modem PLC baseada no chip ST7538
(Powerline FSK Transceiver) da STMicroelectronics.
Tal aparelho foi desenvolvido para se conectar a rede de energia elétrica, utilizando modulação FSK com uma portadora 132.5kHz. Ele possui uma alimentação
independente de 10V e a troca de dados com um computador IBM-PC ocorre através
da interface da porta paralela. Tais passos serão desenvolvidos no Capı́tulo 3.
Para utilizar o modem PLC torna-se necessário o desenvolvimento de um software que controle os sinais do chip. Utilizando um computador rodando Linux,
com o kernel 2.6.17, será implementado um driver para a troca de informações do
kernel com a aplicação. Tal driver deverá ser responsável por fazer com que que
dois modems não transmitam ao mesmo tempo na rede elétrica. Para isso será implementado um controle de acesso ao meio CSMA-ppersitente. Para a camada de
enlace, o driver encapsulará os dados em HDLC e utilizará o tipo de transmissão
broadband, onde todos os modems recebem o que está sendo transmitido.
1.1
Objetivos
O objetivo deste trabalho é explorar as caracterı́sticas do chip ST7538, utilizandoo no desenvolvimento de um modem PLC, implementando o hardware e o software
adequados para a tarefa em questão. Os esquemáticos de hardware e a arte final
PCB se encontram em anexo no final desse trabalho e podem ser encontrados em
<http://www.ece.ufrgs.br/~fetter>. O software do modem terá uma licença
GPL, o qual permite ser copiado, distribuı́do e melhorado, sendo desenvolvido para
22
ser utilizado com o kernel 2.6.17, possibilitando que pessoas que necessitem utilizar
tal solução possam usufruir de um modem PLC completo para as suas finalidades.
A principal motivação deste trabalho é finalizar o desenvolvimento do modem
PLC com licença GPL, demonstrando todas as qualidades adquiridas ao longo do
curso para tal, para que todas as pessoas possam utilizar tal tecnologia como uma
opção viável para a transmissão de dados a baixas velocidades em redes residenciais
internas.
1.2
Organização do trabalho
Este trabalho está organizado em outros quatro capı́tulos. No Capı́tulo 2 são
apresentados as caracterı́sticas da comunicação PLC. O Capı́tulo 3 apresenta as
caracterı́sticas do chip ST7538 e dos componentes presentes na placa. No Capı́tulo
seguinte, desenvolve-se noções de Linux, funções para acessar ao hardware da placa
e dos protocolos que serão suportados pelo o modem e a utilização do software
kissattach e a sua com utilidade.
23
2
TECNOLOGIA PLC
Este capı́tulo tem por objetivo apresentar aspectos básicos da tecnologia PLC.
2.1
Introdução a Tecnologia PLC
A tecnologia PLC usa como meio fı́sico uma das redes mais utilizadas em todo o
mundo: a rede de energia elétrica. A idéia desta tecnologia não é nova e isto pode
ser comprovado se lembrarmos do antigo sistema ”Babá Eletrônica”que mesmo tendo
mais de 30 anos, é utilizado até hoje (IGUAÇU ENERGIA, 2006). Entretanto tal
meio possui uma grande quantidade de equipamentos conectados a ele, que interfere
na comunicação PLC. Devido a esses problemas, somente há aproximadamente quatro anos, com a evolução da tecnologia, a comunicação PLC começou a ser avaliada
e utilizada por algumas empresas. Assim, aproveitando o fenômeno da propagação
de ondas através dos condutores elétricos e desenvolvendo uma transmissão de forma
controlada (freqüências e nı́veis de propagação especı́ficos) com sinais que utilizam
as freqüências de forma inteligente e diferenciada daquelas utilizadas por outros
equipamentos e pela própria transmissão da energia elétrica, nasceram os primeiros
modems PLC.
2.1.1
X-10 Uma Primeira Idéia
Uma das primeiras formas de implementar a comunicação PLC foi através da
tecnologia X-10. Ela foi desenvolvida para ser unidirecional, porém atualmente é
possı́vel encontrar dispositivos bidirecionais. Os controladores X-10 enviam seus
sinais para receptores através da rede de energia elétrica e são utilizado basicamente
para controlar lâmpadas e outras aplicação simples. A Figura 2.1 apresenta a foto
de um modem comercial PLC utilizando a tecnologia X-10.
A tecnologia X-10 utiliza uma forma de modulação Amplitude-Shift Keying (ASK),
onde uma portadora modulada de 120kHz com um sinal de 0.5W de energia, é transmitida a cada cruzamento por zero da rede de energia elétrica. Para aumentar a
robustez do sistema, cada bit utiliza dois cruzamentos para ser transmitido. A transmissão no cruzamento por zero é justificada, pois neste instante apresenta um menor
nı́vel de ruı́do na rede (NETWORKING, 2006).
Essa forma de representação do sinal apresenta um pequena largura de banda
(60 bps) e fica severamente comprometida se o ambiente de transmissão for muito
ruidoso, por esses motivos tal tecnologia ficou limitada a aplicações simples.
24
Figura 2.1: Modem X-10.
2.1.2
Problemas da Tecnologia PLC
A tecnologia PLC seria uma solução perfeita se não fosse pelo fato de as linhas de
força (assim como a rede telefônica no passado) não serem consideradas meios ideais
para a transmissão de dados. Tanto dentro e como fora de casa, a rede elétrica está
sujeita a todo tipo de interferência e ruı́dos gerados pela diversidade de equipamentos
que estão conectados a ela, tais como fontes chaveadas, motores e dimmers. Esses
equipamentos geram durante o seu funcionamento uma série de harmônicas da rede
elétrica que pode inviabilizar a comunicação PLC.
Outro fator negativo das redes elétricas é sua oscilação: caracterı́sticas como
impedância, atenuação podem variar drasticamente de um momento para o outro,
à medida que luzes ou aparelhos conectados à rede são ligados ou desligados. Além
disso, se a intenção for transmitir informações a longas distâncias, os transformadores
de distribuição são verdadeiras barreiras para a transferência de dados. Apesar de
permitirem a passagem de corrente alternada a 50 Hz ou 60 Hz com quase 100% de
eficiência, os transformadores atenuam seriamente outros sinais de maior freqüência,
tal fato foi de fundamental importância para a realização de um modem para a rede
interna.
2.1.3
Vantagens da Tecnologia PLC
A principal vantagem da tecnologia PLC é a possibilidade de transformar toda
a infra-estrutura elétrica de uma residência ou edifı́cio em uma rede local de dados,
baseado no conceito de ”aproveitamento da rede elétrica”. A conveniência é até
mesmo mais óbvia neste caso porque enquanto nem todo cômodo tiver um telefone
para conectar, sempre existirá uma tomada perto de um computador e cada tomada
pode ser encarada como um ponto de acesso que pode ser usado de maneira simples e
descomplicada. Logo, por não requerer nenhuma instalação nova, a tecnologia PLC
é um bom método de conectar computadores em cômodos diferentes. Vale ressaltar
que uma rede doméstica PLC não acrescenta custo a conta de energia elétrica. As
Figuras 2.2, 2.3, 2.4 apresentam os exemplos de uma rede de dados para uma
casa residencial. A Figura 2.2 mostra uma rede Ethernet onde os poucos pontos
de acessos estão localizados somente pertos de computadores. A Figura 2.3 mostra
uma rede usando a linha telefônica onde existem mais pontos de acessos localizados
escassamente por toda a casa. A Figura 2.4 mostra uma rede usando a linha de
energia onde existem muitos pontos de acessos localizados por todos os cômodos da
25
casa.
Figura 2.2: Rede Ethernet.
Figura 2.3: Rede usando a linha telefônica.
Essa idéia de transmitir dados sobre rede elétrica também poderia ser aplicada
para interconectar dispositivos inteligentes dentro de uma casa. No inı́cio de 2000, a
empresa Sunbeam anunciou uma linha de eletrodomésticos inteligentes que trocavam
informações no momento em que eram ligados à tomada. Batizada de HLT (Home
Linking Technology), a iniciativa pretendia lançar produtos como despertadores,
detectores de fumaça, cafeteiras, cobertores elétricos, medidores de pressão arterial,
capazes de se comunicar. Por exemplo, o despertador poderia ser programado para
mandar uma ordem à cafeteira para começar a preparar o café um pouco antes
26
Figura 2.4: Rede usando a rede de energia elétrica.
do pessoal da casa sair da cama. Num futuro próximo, até será possı́vel colocar
um filme em DVD no PC da casa e transmitir o som e a imagem para uma TV
compatı́vel pela fiação interna (REDES, 2006).
2.2
Tipos de Redes PLC
O mercado atual envolvendo a tecnologia PLC encontra-se dividido em dois segmentos: last mile access e last inch access
2.2.1
Last Mile Access
Last mile access: seria o acesso até a residências, onde o PLC é somente uma das
diversas possibilidades tecnológicas dentre as quais figura o cable modem e diferentes
tipos de xDSL e difusão sem fio (broadband wireless). Nenhuma das tecnologias se
destaca com relação as demais; nem o PLC apresenta grande vantagens técnicas com
relação às outras técnicas, nem estas são muito superiores ao PLC (MAJUMDER;
CAFFREY, 2004). É nesse segmento do mercado que as grandes concessionárias
estão interessadas, pois a tecnologia PLC usaria as linhas de transmissão das mesmas
abrindo um novo setor de mercado para elas.
2.2.2
Last Inch Access
Last inch access: seria o acesso dentro das residências. Alguns estudos apontam
que o uso de PLC seria mais eficaz que o cabo, ou mesmo o wireless para prover o
acesso no interior das residências (MAJUMDER; CAFFREY, 2004), devido a capilaridade da sua rede. O last inch access está conduzindo gradativamente à expansão
do espectro das redes caseiras, por meio de um vasto conjunto de equipamentos
conectados no interior das habitações por uma rede interna (in-home). Tal rede
pode transformar, como dito anteriormente, todas as tomadas elétricas da casa em
conexões de difusão para computadores pessoais, telefones, e seus acessórios, bem
como para outro dispositivos eletro-eletrônicos, como ilustrado na Figura 2.5.
27
Figura 2.5: Last inch access, ou acesso dentro das residências.
28
29
3
HARDWARE DO MODEM PLC
Neste capı́tulo serão expostas as caracterı́sticas do hardware do modem PLC.
O hardware do modem é dividido em basicamente duas partes:uma parte analógica
constituı́da do chip ST7538 e uma interface dos sinais digitais para a porta paralela
de um computador IBM-PC padrão.
3.1
Parte Analógica
A parte analógica do modem é constituı́da de um chip ST7538 (PowerLine FSK
Transceiver ) e dos componentes necessários para o seu funcionamento.
3.1.1
ST7538-PowerLine FSK Transceiver
O chip ST7538 é um transceiver que foi projetado para realizar comunicações seriais half-duplex, como descrito em 3.1.1.1, utilizando a modulação FSK, como 3.1.1.2
sobre as linhas de energia elétrica. Esse chip apresenta uma série de pinos de saı́da
para indicar o seu status sob diversas condições, e quatro pinos de entrada para
realizar o controle sobre o modem. Internamente apresenta um registrador de 24
bits onde é possı́vel programar diversas opções a cerca do seu funcionamento. Possui uma alimentação analógica de 7.5V a 12.5V e uma alimentação digital de 5V, e
necessita de um clock de 16 MHz para o seu correto funcionamento. A Figura 3.1
apresenta o diagrama de blocos do ST7538.
3.1.1.1 Comunicação Half-Duplex
Uma comunicação é dita half-duplex (também chamada semi-duplex) quando
existem um dispositivo transmissor e outro receptor, sendo que ambos podem transmitir e receber dados, porém não simultaneamente, a transmissão tem sentido bidirecional. Durante uma transmissão half-duplex, em determinado instante um dispositivo A será transmissor e o outro B será receptor, em outro instante os papéis
podem se inverter. Esse caso se aplica perfeitamente para as redes de energia elétrica, pois existe apenas um meio de transmissão e o modem envia e recebe na mesma
freqüência, logo dois modems não podem transmitir ao mesmo tempo.
Caso existisse apenas um modem receptor e um transmissor, tal comunicação
seria simplex, ou ainda no caso de que fosse possı́vel que o modem recebesse e
transmitisse simultaneamente, tal comunicação seria full-duplex.
30
Figura 3.1: Diagrama de blocos do ST7538.
3.1.1.2 Modulação FSK
A modulação FSK também conhecida por modulação de chaveamento de freqüência é um técnica de modulação digital que consiste na mudança da freqüência da
onda portadora de acordo com o sinal digital modulante. O modem PLC possui uma
onda portadora de alta freqüência, que irá variar +δ para representar uma marca e
−δ para representar um espaço.
A Figura 3.2 mostra um sinal digital a ser transmitido através dessa modulação
e o seu equivalente após a modulação.
Figura 3.2: Modulação FSK.
Ainda é possı́vel citar que existem a modulação ASK, onde é variada a amplitude
da onda portadora, como no caso do X10 2.1.1 e a modulação PSK, onde ocorre uma
31
mudança na fase da portadora, esta modulação é utilizada em modems telefônicos
de pequena banda.
3.1.1.3 Registrador Interno
Como mencionado anteriormente, o ST7538 possui um registrador interno que
possibilita programar algumas opções de funcionamento do mesmo. A leitura e escrita desse registrador é sempre feita de forma sı́ncrona utilizando os pinos REG/DATA,
RX/TX, RxD, TxD, CLRT.
Para realizar uma leitura nesse registrador é necessário colocar o pino REG/DATA
e RX/TX em alto, a seguir deve-se ler o pino RxD a cada borda crescente do clock
durante os próximos 24 ciclos, a parte mais significativa do registrador é enviada
antes. Para realizar um escrita o procedimento é similar, porém deve-se deixar o
RX/TX em baixo e escrever os dados no TxD. Somente os últimos 24 bits serão
escritos ao registrador.
Através do registrador interno é possı́vel programar algumas opções do ST7538,
como a frequência de portadora entre 60kHz até 132.5kHz, velocidade da taxa de
transmissão, distância em marca e espaço, etc.
3.1.1.4 Funções Auxiliares
O ST7538 apresenta uma série de periféricos para auxiliar o controle e a operação
do chip, entre eles podemos destacar:
Detector de Banda em Uso: Detecta quando uma freqüência da configuração
atual do modem é detectada na linha, está condicionado ao detector de portadora para prevenir alarmes falsos. É diferente do detector de portadora devido
a sensibilidade dos seus filtros.
Timeout de Transmissão: Após um ou 3 segundos de transmissão contı́nua, o
ST7538 é forçado para o modo de recepção conforme o tempo programado no
seu registrador interno. Pode se utilizado para controlar o acesso ao meio.
Wacthdog de Hardware: O ST7538 possui um watchdog que previne falhas de
software assertando o pino RST causando o reset externo e interno da placa.
Pode ser programado no registrador interno.
Detector de Cruzamento por Zero: É um detector de cruzamento por zero na
linha de energia elétrica. Através do registrador interno é possı́vel sincronizar
a transmissão com o cruzamento para diminuir os pacotes perdidos.
Divisor de Clock: É possı́vel colocar um dos clocks divididos internamente no
ST7538 conforme o programado no registrador interno.
Protetor de Temperatura: Desliga uma parte do chip ao detectar que a temperatura dele passou de um determinado limite.
Regulador de Tensão: Gera uma tensão de 5V para a parte digital do circuito e
possui um comparador para verificar a qualidade dessa tensão.
32
3.1.2
Componente Analógicos
Para garantir o funcionamento do ST7538 foi construı́do uma interface analógica
para o modem. Essa interface é bem similar ao Apliccation Notes do ST7538 (CANTONE, 2003) e é composta basicamente pelos seguintes componentes:
Alimentação Digital: É constituı́do de um regulador de tensão 7805 que gera a
alimentação de 5V para a parte digital do circuito. A alimentação digital pode
ser selecionada dele ou do ST7538 segundo um jumper.
Gerador de Clock: É constituı́do de um cristal de 16MHz e seus capacitores para
gerar o clock de 16 MHz para o ST7538.
Filtros Passa Banda: São filtros sintonizados nas freqüências possı́veis de transmissão. São três filtros, um na entrada do sinal antes do transformador de
desacoplamento, um antes da recepção e outro antes da transmissão. Os filtros foram especificados para uma portadora de 132,5kHz.
Circuito de Proteção: Composto de três diodos zeners de 6V8 rápidos que protegem o circuito contra spikes da rede elétrica.
Controle de Corrente: Composto de um resistor e um capacitor, serve para controlar o valor máximo da corrente nos drivers de saı́da do ST7538.
Controle de Tensão: Composto de um divisor de tensão, serve para manter a
tensão constante no pino Vsense, esse controle é utilizado para ajustar o ganho
do sinal de saı́da do ST7538. Através de um jumper é possı́vel escolher qual
será a linha amostrada.
Detector de Cruzamento por Zero: Composto de um filtro passa baixa para
eliminar as altas freqüências, pode ser conectado ao ST7538 através de um
jumper na placa.
3.2
Parte Digital
A parte digital do hardware do modem é composta pela a interface dos sinas do
ST7538 com a porta paralela de um computador IBM-PC padrão.
3.2.1
Interface com a Porta Paralela
A porta paralela é uma interface de comunicação entre um computador e um
periférico, que nesse caso será o modem PLC. Ela é basicamente composta de três
registradores: registrador de dados, registrador de status e registrador de controle.
Cada registrador é composto de 8 bits, porém nem todos eles estão disponı́veis
externamente na porta paralela. Neste trabalho ficou definido a utilização dos pinos
de dados para enviar sinais para o modem, dos pinos de status para receber sinais
e, por fim, do registrador de controle para as demais operações.
3.2.1.1 Pinos de Entrada
Através dos seguintes pinos é possı́vel controlar o funcionamento do ST7538:
33
REG/DATA: através desse pino é possı́vel informar ao ST7538 se há o envio dados para serem modulados e transmitidos na rede ou armazenados no seu
registrador interno, alterando o seu funcionamento.
TxD: Pino de entrada digital, através desse pino passa-se o dado para o chip.
Rx/Tx: através desse pino podemos alternar entre uma seção de transmissão ou
recepção de acordo com uma comunicação do tipo half-duplex.
WD#: Pino de watchdog, esse pino é utilizado para resetar o contador interno do
ST7538 e impedir o reset no pino RESET# da placa.
Os pinos de entrada do ST7538 estão conectados ao registrador de dados da porta
paralela através de um buffer tri-state que está constantemente habilitado (tal buffer
é utilizado como uma forma de proteção) e a ligação com os pinos é feita conforme
a Tabela 3.2.
Tabela 3.1: Ligação do registrador de controle.
Pino na porta paralela Bit do registrador
AUTOF1
bit1
INIT
bit2
Tabela 3.2: Ligação dos pinos de entrada.
Pino na porta paralela Bit do registrador Pino no ST7538
Data0
bit0
REG/DAT A
Data1
bit1
T xD
Data2
bit2
Rx/T x
Data3
bit3
W D#
3.2.1.2 Pinos de Saı́da
Os pinos de saı́da apresentam diversos status do modem PLC que devem ser
constantemente lidos para garantir o controle sobre o mesmo. Os seguintes pinos
são lidos do ST7538:
RxD: Pino de saı́da digital, através desse pino é possı́vel receber os dados do
ST7538.
CLRT: Pino de clock digital, através desse pino é possı́vel amostrar o pino RxD de
forma correta.
MCLK: Pino de clock digital, esse pino possui um clock de alta freqüência que
pode ser utilizado para realizar transferências rápidas para o driver. O seu
valor depende do registrador interno do ST7538.
RESET#: Pino de reset, esse pino é utilizado para que o ST7538 resete o driver
devido a uma falha de software, caso o watchdog esteja habilitado.
34
PG: Pino de status da alimentação, esse pino avisa que a tensão de alimentação do
ST7538 está adequada através do nı́vel lógico 1.
TOUT: Pino de time-out, esse pino é assertado quando uma transmissão durar
mais que o valor programado no registrador do ST7538. Esse pino também
pode representar que o chip está numa temperatura acima do padrão e que a
placa deve ser desligada.
REG OK: Pino de status do registrador interno do ST7538, esse pino garante que
o conteúdo do registrador interno do chip é válido.
CP/PD: Pino de detecção de portadora, através desse pino o ST7538 indica a
presença de uma portadora ou preâmbulo (alternância de marcas e espaços)
na linha de energia elétrica, conforme o valor do registrador interno do ST7538.
BU: Pino de banda em uso, similar ao CP/PD porém com outra sensibilidade.
ZCOUT: Pino de detecção de cruzamento por zero na linha de energia, pode ser
utilizado para sincronizar a transmissão com a rede de energia.
Devido ao fato de que o ST7538 possui mais pinos de saı́da que o registrador de
status, eles estão conectados ao registrador compartilhando um barramento através
de três buffers tri-state, que são habilitados pelo registrador de controle da porta
paralela conforme a o bit desejado para a leitura. Os pinos desse registrador estão
conectados conforme a Tabela 3.1. A ligação com os pinos de status é feita conforme
a Tabela 3.3.
Tabela 3.3: Ligação dos pinos de saı́da.
Pino na porta paralela Bit do registrador
ERROR
ACK
SLCT
BUSY1
bit3
bit6
bit4
bit7
Pinos no ST7538
ST1
ST2
ST3
CLRT
ZCOU T
T OU T
RESET # RESET # RESET #
RxD
REG OK
BU
CD/P D
PG
M CLK
3.2.1.3 Pinos de Controle
Como dito anteriormente os pinos de status são lidos através de um barramento,
logo é necessário utilizar os pinos de controle para setar o buffer que utilizará o
barramento conforme a Tabela 3.1.
A Tabela 3.4 indica como os valores do registradores de controle afetam o barramento de status.
1
Pino invertido
35
Tabela 3.4: Ligação nos multiplexadores.
AUTOF INIT
ST Ativo
0
0
ST 3
0
1
N enhumST Ativo
1
0
ST 1
1
1
ST 2
3.3
Mudança de Hardware
Através do desenvolvimento do software do modem foi constatada a necessidade
da mudança no hardware. Para efetuar o recebimento de dados no processador do
computador seria necessário ficar realizando polling no ST7538, sendo que tal fato
iria consumir muito tempo de processamento dificultando a utilização do modem
juntamente com o kernel do Linux. Para corrigir esse defeito foi conectado o pino
de clock diretamente no pino 10 (ACK) da porta paralela e a partir deste momento
foi utilizado uma interrupção para o recebimento de dados, melhorando o processo.
A Tabela 3.5 apresenta a nova configuração dos pinos.
Tabela 3.5: Ligação dos pinos de saı́da.
Pino na porta paralela Bit do registrador
Pinos no ST7538
ST1
ST2
ST3
ERROR
bit3
RESET # ZCOU T
T OU T
ACK
bit6
CLRT
CLRT
CLRT
SLCT
bit4
RxD
REG OK
BU
1
BUSY
bit7
CD/P D
PG
M CLK
36
37
4
SOFTWARE DO MODEM PLC
Neste capı́tulo serão expostos os passos da construção do software desenvolvido
para controlar o modem PLC. Para que sejam compreendidas os passos descritos
neste relatório, é conveniente que haja uma breve explicação dos conceitos envolvidos nas atividades. Uma pequena introdução sobre sistemas operacionais, particularmente sobre o sistema Linux, será fornecida. Também, uma breve descrição da
linguagem C utilizada no desenvolvimento do software do modem PLC. A seguir
será explicado o desenvolvimento do software do modem PLC.
4.1
Plataforma de Desenvolvimento do Software
Como opções para plataforma de desenvolvimento do driver para computadores IBM-PC há como principais alternativas o Windows e o Linux. No Windows
não há acesso aos códigos fontes do sistema operacional e exitem algumas dificuldades de acessar alguns dispositivos de I/O como a porta paralela, por exemplo. Já
na plataforma Linux, os códigos fontes do mesmo são acessı́veis, o que possibilita
qualquer alteração que poderá eventualmente ser necessária, e existe acesso a todos
os dispositivos do computador. Por esse motivo o Linux é utilizado amplamente
no desenvolvimento de aplicações embarcadas, tornando-se a plataforma de desenvolvimento ideal para o driver tanto para o seu desenvolvimento como para a sua
aplicação.
Com a escolha da plataforma de desenvolvimento do driver, torna-se necessário a escolha do tipo de driver que será desenvolvido. Inicialmente pode-se pensar
em implementar um simples driver para dispositivos de caracter para Linux. A
primeira vista essa alternativa é uma boa opção pois implementação de um driver
char é simples e fácil de ser compreendida evitando maiores problemas. Drivers
char funcionam de forma similar a arquivos em linguagem C. Porém pelo fato de
que o modem implementa uma comunicação serial, a implementação de tal driver
implicaria na necessidade de desenvolvimento de complexos programas de aplicações
para a sua utilização. Para realizar comunicação serial torna-se adequando o desenvolvimento de um driver TTY para Linux pois tal driver apresenta certas funções
adequadas a essa comunicação e possuem uma série de programas já prontos para a
sua utilização. Porém ele apresenta uma implementação um pouco mais complexa
que um driver char. Assim o desenvolvimento de um driver TTY torna-se adequado
nessa situação para tornar o resultado mais útil ao usuário final.
Devido ao fato de que o modem realiza uma comunicação half-duplex torna-se
necessário implementar algum controle de acesso á linha de transmissão no driver
para evitar que dois modems tentem transmitir ao mesmo tempo, o que conseqüente-
38
mente inviabilizaria a comunicação. Nesse trabalho será implementado um controle
de fluxo CSMA-ppersitente, pois ele possui as caracterı́sticas necessárias para essa
aplicação e permite uma maior eficiência do meio de transmissão.
4.1.1
Sistemas Operacionais
Em um sistema microprocessado complexo, vários processos são executados. Em
um sistema multitarefa, o processador deve atender a todos os processos em um
intervalo reduzido de tempo, de forma que para um usuário do sistema, eles pareçam
ser executados simultaneamente. Todos os processos demandam recursos do sistema,
como memória, conexão de rede, tempo de computação, acesso a dispositivos etc.
O sistema operacional pode ser entendido como uma camada de software, colocada
entre a aplicação e o hardware, responsável por gerenciar a execução dos processos
e suas demandas.
Na maior parte do tempo, o processador executa aplicativos. O sistema operacional é chamado na ocorrência de um evento especial, que pode ser uma chamada
de sistema ou uma interrupção de periférico. A chamada de sistema merece atenção
especial, pois é a interface através da qual o aplicativo faz uma solicitação de serviço
ao sistema operacional. Uma chamada de sistema é como uma chamada de subrotina, mas que transfere a execução para o sistema operacional. Ao seu retorno, a
execução do aplicativo é retomada na instrução seguinte à chamada.
A parte do sistema operacional responsável pela implementação das chamadas
de sistema é chamada de núcleo (kernel ) do sistema. O kernel gerencia o acesso ao
processador, à memória, aos dispositivos de entrada e saı́da e ao sistema de arquivos.
4.1.1.1 Linux
O sistema operacional Linux é um sistema Unix-like, tendo muitas caracterı́sticas
comuns a outros sistemas UNIX. Uma virtude do sistema Linux é ser um sistema
operacional independente de plataforma, ou seja, não está atrelado a processadores
Intel e compatı́veis. O sistema Linux pode ser utilizado, por exemplo, em processadores ARM, Alpha, PowerPC, M68K, MIPS, SPARC, S390 e outros, que constituem
em uma alternativa à arquitetura IA32 de processadores (RUBINI; CORBET, 2001).
Em um sistema Unix-like, existem diversas tarefas sendo executadas. Cada tarefa
requisita por recursos do sistema operacional, tais como, processamento, memória,
conectividade de rede e outros recursos especı́ficos. O kernel do Linux é um grande
programa que se encarrega de distribuir tais recursos entre as tarefas. O kernel pode
ser dividido, como mostra a Figura 4.1, nas seguintes partes:
Gerenciador de Tarefas: O kernel é encarregado de destruir e criar tarefas, bem
como tratar da sua conexão com o mundo externo. A comunicação entre
diferentes tarefas é a base para o correto funcionamento do sistema operacional
e também é tratado pelo kernel. Ainda existe o escalonador (schelduler ), que
controla como as tarefas dividem o processamento da CPU nesta parte do
kernel.
Gerenciador de Memória: A memória do computador é um recurso essencial, e
a polı́tica para o seu uso é crucial para o desempenho do sistema operacional.
O kernel constrói um espaço de endereçamento virtual para si e para todas as
tarefas. Através de tal endereçamento é feita a divisão entre kernel space e user
39
Figura 4.1: Divisão do kernel do Linux.
space. O sistema operacional, que roda no kernel space, gerencia a memória
contra acessos não autorizados. Esta tarefa de proteção é feita com o auxilio do
hardware do processador. De fato, todos os processadores modernos possuem
nı́veis (ou modalidades) diferentes de operação, e algumas ações apenas são
permitidas em certos nı́veis de operação.
Sistemas de Arquivo: Unix é fortemente baseado na conceito de sistemas de arquivo. Praticamente tudo em um sistema Unix pode ser tratado como um
arquivo. O kernel constrói um sistema de arquivos estruturados em cima de
uma hardware desestruturado, e o resultado dessa abstração é usado por todo
o sistema. Ainda. o Linux suporta múltiplos sistemas de arquivo de diferentes
tipos, sendo que, cada tipo de sistema utilizada uma forma diferente de organizar os dados no meio fı́sico. Como exemplo de sistemas de arquivos existem:
EXT2, EXT3, FAT, NTFS, JFFS2 e Reiser.
Controlador de Dispositivos: Cada operação do sistema operacional, geralmente,
é direcionada para um dispositivo fı́sico diferente. Com exceção do processador, memória, e outras poucas entidades, todas as operações direcionadas a
um hardware são executadas por um código especı́fico para aquele dispositivo.
Tal código é chamado de driver. O kernel deve ter um driver especı́fico para
cada periférico presente no sistema, desde HD até o teclado. Por esse motivo
será desenvolvido um driver para tratar as funções do modem PLC. Existem
diversos tipos de drivers, porém eles não serão abordados nesse trabalho.
Sistema de Rede: As operações de rede devem ser controladas pelo sistema ope-
40
racional porque tais operações não são uma especificas a uma tarefa. Pacotes
chegando são eventos assı́ncronos que devem ser coletados, identificados e despachados antes que uma tarefa precise deles. O sistema é responsável por
entregar os pacotes á sua devida tarefa, ou á sua interface de rede, e controlar
a execução dos programas de acordo com a sua atividade na rede. Uma grande
vantagem do Linux, é que o seu kernel já possui desenvolvido controles de diversos protocolos como PPP, AX-25, FR, SLIP, PLIP, X25 e outros. Assim
tais protocolos são gerenciados pelo kernel sendo transparentes para o modem
PLC.
Neste ponto será implementado um driver TTY no kernel do Linux, controlado
pelo controlador de dispositivos, que utilizará as operações de rede para realizar a
comunicação entre dois modems.
4.2
Linguagem C
A linguagem C tem sido utilizada com sucesso nas mais diversas aplicações, tais
como sistemas operacionais, compiladores e utilitários em geral. A popularidade
da linguagem aumentou nos últimos anos, tornando C uma linguagem de propósito
geral. O código C é bastante portável. Isso significa que softwares escritos para
um determinado processador podem ser facilmente transmitidos para um outro de
arquitetura diferente.
O sistema operacional UNIX, do qual o Linux é derivado, é escrito em C. Na
verdade, a linguagem C foi desenvolvida para que se implementasse o sistema UNIX.
Portanto, é natural que C seja a linguagem utilizada para o desenvolvimento de
aplicativos para o sistema Linux.
A principal diferença entre a linguagem C e as outras linguagens de programação
reside no emprego de ponteiros, ou seja, no endereçamento indireto. Os programas
são estruturados através da definição e chamada de funções. Dados podem ser
armazenados em arranjos (arrays) ou estruturas.
Na comparação com outras linguagens, C é considerado de nı́vel médio, pois
oferece um bom grau de abstração ao programador (caracterı́stica de linguagens de
alto nı́vel), ao mesmo tempo em que permite acesso eficiente ao hardware (como em
linguagens de baixo nı́vel) (SCHILDT, 1988).
Distribuições Linux oferecem um compilador C, o GCC, que foi o compilador
utilizado para gerar o driver.
4.2.1
Programando no Kernel do Linux
Esta parte tem como objetivo introduzir alguns conceitos de programação para o
kernel do linux. Todas as funções utilizadas no drivers podem ser achadas no código
fonte do kernel -2.6.17 localizados nos headers do mesmo.
Inicialmente como o kernel roda por si mesmo, ele não pode utilizar funções da
bibliotecas padrões do C e sim as suas próprias. A função printf() é substituı́da
pela printk() que está definida no kernel de modo bastante similar ao printf().
O driver pode utilizar printk() pois, após ele ser linkado com o kernel, ele tem
acesso a todas os sı́mbolos públicos do kernel (funções e variáveis).
Indo adiante, existem outras diferenças entre o driver do kernel e uma aplicação. Onde uma aplicação faz uma simples tarefa do inı́cio ao fim, um driver
41
deve-se registrar para prover futuras chamados do kernel ao mesmo. A função
init_module() e as outras chamadas por ela são responsáveis pela inicialização do
driver e por informar ao kernel a sua existência e as suas funcionalidades. A função
cleanup_module() faz o contrário.
É possı́vel adicionar um driver ao kernel já carregado utilizando os programas
insmod e rmmod para adicionar e retirar, respectivamente, drivers e módulos no
kernel. Deve-se executar tais programas como superusuário para que a operação
seja feita com sucesso. A Figura 4.2 mostra como as funções e os ponteiros são
utilizados para adicionar uma nova funcionalidade ao kernel rodando.
Figura 4.2: Adicionando um Driver no Kernel.
Como mencionado anteriormente, a divisão do espaço do usuário e do kernel é
implementada pelo controlador de memória, e assim deve-se usar funções especı́ficas
para tratar ponteiros recebidos do programa de aplicação. A função put_user() é
utilizada para colocar dados no espaço do usuário e get_user() recebe dados do
mesmo.
Somente funções que são realmente parte do kernel podem ser usada no driver.
Qualquer header pode ser encontrado em include/linux e include/asm dentro da
árvore do kernel.
Como até mesmo o menor driver é linkado a todo o kernel do Linux, deve-se
tomar o cuidado no uso de variáveis globais. Caso existam duas variáveis globais
com o mesmo nome, podem ocorrer os mais variados tipos de falhas, que dependerão
da configuração do computador usado e dos módulos carregados no kernel. Para
evitar tais problemas recomenda-se a utilização da declaração static para as suas
variáveis.
42
Por fim, uma última diferença em a programação no kernel e na aplicação é a
respeito de erros no programa. Enquanto um uma falha de segmentação na aplicação
é simplesmente um aviso e um debbuger pode ser utilizado para rastrear o problema,
no kernel, tal problema é fatal não só para a aplicação que está sendo executada
e sim para todo o sistema. Métodos de depuração para o kernel existem, porém
eles não serão tratados neste trabalho. No desenvolvimento do driver foi utilizado
o programa oops para depurar tais problemas.
4.3
Especificações de Hardware
Após apresentar alguns conceitos a respeito de Linux é possı́vel tratar do desenvolvimento do driver em si. O hardware construı́do influencia fortemente o funcionamento do driver, pois algumas opções do registrador interno do ST7538 não
poderão ser alteradas pelos usuários do modem. As opções fixas são as seguintes:
Freqüência da portadora: Os filtros localizados no hardware da placa foram sintonizados para um freqüência de portadora de 132.5KHz. Logo essa opção fica
fixa.
Watchdog: Não será implementada uma função de watchdog nesta versão de software e portanto ele deve permanecer desabilitado.
Timeout: O timeout de transmissão será implementado em software nas funções
de protocolo, logo o de hardware deve ser desabilidato.
Detecção de portadora: Como será utilizado o pino de clock para interromper o
processador, o clock só deverá ser passado para o processador quando houver
algum dado disponı́vel tornando essa opção fixa.
Interface Sı́ncrona: Não será implementada um software que suporte a interface
assı́ncrona, logo essa opção será fixa.
Modo Pacote: Essa função foi retirada na primeira revisão do datasheet do ST7538
e não será desenvolvido suporte para ela.
As demais opções como taxa de transmissão e outras ficam como opções de
funcionamento para o usuário.
4.4
Kernel Space: Driver TTY
Como citado em 4.1.1.1, foi implementado um driver TTY no kernel do Linux
para realizar a parte de baixo nı́vel do modem PLC. Ele deverá se comunicar com
o usuário com um programa de aplicação conforme os passos descrito em 4.5. Ele
possui basicamente as seguintes funções:
1. funções de acesso ao hardware,
2. funções de protocolos,
3. funções de drivers TTY.
O código fonte do driver se encontra em anexo no final desse trabalho.
43
4.4.1
Funções de Acesso ao Hardware
As funções dessa seção serão responsáveis por realizar o acesso em mais baixo
nı́vel ao hardware do modem PLC. Elas deverão dar suporte as funções das camadas
superiores para o gerenciamento do driver.
4.4.1.1 Função de Leitura dos Pinos do ST7538
Para realizar a leitura e a escrita nos pinos de dados e de controles do ST7538
basta realizar a leitura dos pinos, modificar os pinos desejados e então escrever o
valor no respectivo registrador.
Para realizar a leitura dos pinos de status do ST7538 será criada a função
readMSR(), que deve seguir o seguir o fluxograma da Figura 4.3.
Figura 4.3: Fluxograma da leitura e escrita de dados no ST7538.
Primeiramente é necessário setar um dos buffers do barramento, realizar a leitura
dos seus pinos de status, armazenar em um registrador. Então se deve setar outro
buffer e fazer todos os passos novamente. Assim é realizada a leitura dos pinos de
status do ST7538.
44
4.4.1.2 Função de Leitura e Escrita do Registrador Interno do ST7538
Essas funções devem ser responsáveis por ler e escrever a configuração desejada
no registrador interno do ST7538. Isso é feito através da Figura 4.4
Figura 4.4: Seqüência de escrita no registrador interno do ST7538.
Devido ao fato de que a configuração do modem não é realizada durante o funcionamento do driver, ou não frequentemente, implementamos funções que realizam polling no pino de clock do ST7538. As funções readRegister() e writeRegister()
implementam os passos do fluxograma da Figura 4.5 para fazer sua função.
Inicialmente é necessário setar o pino REG/DATA do ST7538, a seguir deve-se
ler e escrever os próximos 24 bits de configuração do modem PLC.
A Figura 4.6 mostra a visualização de uma leitura do registrador interno do
ST7538 realizada pelo driver. Nota-se o clock variando rapidamente em baixo e o
bits no registrador sendo escritos em cima.
4.4.1.3 Função de leitura e escrita dados do ST7538.
Essas funções devem ser responsáveis por enviar e receber dados do ST7538. Os
passos para o recebimento dos dados são similares à escrita e a leitura do registrador
interno do ST7538, porém ela é realizada de uma forma totalmente diferente das anteriores. O recebimento/escrita de dados é realizada em um contexto de interrupção
do processador e seguem os passos do fluxograma representado na Figura 4.7.
A cada ciclo de clock recebido, um dado é lido/escrito para o ST7538. A cada
oito bits que isso ocorre um novo dado é carregado para repetir o processo ou um
dado é enviado para a camada superior do driver.
A Figura 4.8 mostra um frame sendo enviado para o hardware do modem PLC.
Inicialmente um preâmbulo é enviado para que o ST7538 possa detectar a portadora
na recepção e assim garantir que nenhum bit será perdido..
4.4.2
Funções de Protocolos
Para aumentar a robustez do sistema serão implementados dois nı́veis de protocolos: HLDC e CSMA-ppersistente. Por uma questão de facilidade para o usuário
será implementado também o protocolo KISS como explicado em 4.5.
4.4.2.1 Funções de HDLC
HDLC é um dos protocolos mais utilizados da camada dois do modelo OSI (TANENBAUM, 2003), sendo estruturado em frames. A Figura 4.9 mostra a estrutura
45
Figura 4.5: Fluxograma do software de leitura e escrita de dados no ST7538.
Figura 4.6: Leitura do registrador interno do ST7538 realizada pelo driver.
46
Figura 4.7: Fluxograma do software de leitura e escrita de dados no ST7538.
Figura 4.8: Envio de dados para o ST7538.
47
de um frame HDLC.
Figura 4.9: Estrutura do Frame HDLC.
HDLC utiliza inserção/remoção de zeros (bit-stuffing) para garantir que no pacote de dados não apareça a flag (7E), que por sua vez serve para delimitar o inı́cio
e o final de cada frame. É possı́vel visualizar a transmissão dos flags para o modem
na Figura 4.10 e na Figura 4.11. Apesar de o frame original HDLC possuir um
campo de endereço, esse não será utilizado neste projeto em questão. Isso se deve
ao fato de que o campo de endereço só é realmente útil quando existe um hardware
que permite a decodificação do endereço, o que não se aplica a esse caso. Por isso
a transmissão será broadcast, ou seja, todos os modems escutam todos os modems
e uma camada de cima deverá ser responsável por descartar as mensagens que não
são para aquele modem.
Figura 4.10: Envio da seqüência de flags para o ST7538.
Após acabar o pacote o software deverá adicionar 16 bits de CRC para garantir
a integridade dos dados durante a transmissão. Para as funções de CRC foram
incluı́dos trechos de códigos prontos. Se durante a mensagem ocorre a presença de
sete bits ”um”consecutivos o frame é abortado.
A função hdlc_tx_byte() é responsável pela a máquina de estados da transmissão do frame HDLC, isto é, pela inserção de flags, do CRC e do bit-stuffing da
transmissão. A função hdlc_rx_byte() é responsável pela a recepção do frame, e
pela verificação do bit-stuffing durante o frame, os flags são tratados pela a função
48
Figura 4.11: Envio da seqüência de flags para o ST7538 (zoom).
hdlc_rx_add_bytes() e o CRC pela a função hdlc_rx_flag(). Essa ultima função
também coloca o dado em um pacote KISS como será explicado em 4.4.2.3.
4.4.2.2 Funções de CSMA-ppersistente
O CSMA, é um protocolo de telecomunicação que organiza a forma como os
modems compartilham um determinado meio de transmissão. Este protocolo está
baseado na detecção da portadora para controlar quando o meio está sendo utilizado.
O CSMA-ppersistente é uma variação do CSMA na qual ao detectar que o meio está
disponı́vel para a transmissão, cada modem tem uma probabilidade p de ganhar o
acesso ao meio. Utilizando tal técnica é possı́vel diminuir o número de colisões de
acordo com uma rede especifica em questão.
Como o modem PLC utiliza uma comunicação half-duplex é necessário realizar
um controle de acesso ao meio fı́sico, ou seja, a linha de energia elétrica. O acesso
ao meio será realizado através de timeslots, assim cada modem PLC terá um determinado tempo para verificar o estado do meio. Caso o meio esteja livre, ele irá
realizar um sorteio para tentar ganhar o meio.
Ao final de cada transmissão do frame o meio será liberado para o sorteio entre
os modems escutando a linha. Cada modem também deverá enviar uma certa quantidade de flags após o final e antes do inı́cio da mensagem. Todos esses parâmetros
são passados para o driver na sua inserção junto ao kernel de Linux.
Para realizar o controle de timeslots será inicializado no kernel um timer que
periodicamente irá verificar o estado da linha e caso ela esteja livre, cada modem que
está disputando o meio terá uma probabilidade P de conseguir pegar o meio para
si. Caso dois ou mais modem peguem a linha ao mesmo tempo, ocorrerá colisão
e assim sendo que nenhum dos modems conseguirá transmitir corretamente o seu
49
pacote. Como não é possı́vel detectar colisões com o hardware do modem PLC, não
será feito nenhum controle de transmissão nesse nı́vel do driver. O nı́vel de cima
deverá ser responsável por controlar se o frame foi entregue ou se ocorreu colisão.
A função arb_sched() é responsável por verificar a disponibilidade do meio ao
final de cada timeslot e caso necessário chamar a função tx_arbitrate() onde
ocorre o sorteio do acesso ao meio. O sorteio é feito através da função rand()
implementada de (METAWARE, 1994).
4.4.2.3 Funções de KISS
O protocolo KISS foi proposto em 1986 por Phil Karn (KARN, 1986) para o seu
software TCP-IP. Esse protocolo também é baseando em frames, sendo que o inı́cio
e o final de cada frame é seguido de um caractere especial chamado FEND similar
ao flag do HDLC, porém nenhum controle de erro como CRC é implementado.
Se o caractere FEND aparece durante os dados, ele é convertido em dois caracteres especiais em seqüência, FESC TEND. Assim como se o FESC aparece nos dados,
ele é convertido em FESC TFESC. O caractere FEND não é enviado entre os dados
para garantir a integridade dos frames. Esse protocolo é bastante similar ao SLIP.
Para distinguir comandos de dados, o primeiro byte de cada frame indica o tipo
de frame que será. A Tabela 4.1 indica o tipo de frame em função do comando.
Tabela 4.1: Tipo de frame do protocolo KISS.
Valor do bit Tipo de Frame
0x0
DATA FRAME
0x1
0x2
0x3
0x4
TXDELAY
P
TIMESLOT
TXTAIL
Função
o resto do frame contém dados
a ser passados para o próximo nı́vel
o próximo byte contém o txdelay em 10ms
o próximo byte contém o parâmetro ppersistente
o próximo byte contém o timeslot em 10ms
o próximo byte contém o txtail em 10ms
Esses parâmetros foram descritos anteriormente em 4.4.2.2 e podem ser alterados
com o driver já carregado através do protocolo KISS. A função lpmodem_put_char()
é responsável por realizar a decodifição dos caracteres especiais bem como os macros ADD_CHAR() e ADD_KISSCHAR() por codificar os mesmos. A função lpmodem_put
_fend() deverá decodificar o comando do protocolo KISS fazendo a alteração do parâmetro quando necessário. Por fim as funções store_kiss_packet(), ack_packet(),
get_packet() e store_packet() fazem a interface com o buffer circular em que os
dados vindo desse protocolo são colocados para serem encapsulados em HDLC. A
questão do uso do protocolo KISS será explicada no decorrer desse trabalho em 4.5.
4.4.3
Funções de Driver TTY
Como comentado anteriormente a interface do modem PLC será implementada
em plataforma Linux através de um driver TTY. Basicamente o driver TTY deve
possuir as seguintes funções:
Funções de Inicialização e Finalização: Tais funções são chamadas na inicialização/finalização do driver, elas devem alocar memórias para buffers, realizar a
50
configuração default do modem, requisitar a porta paralela, registrar o driver,
etc.
Função de Ioctl: Essa é a forma básica do usuário modificar a configuração do
modem. Através dele deverá ser possı́vel escrever e ler a configuração do
registrador interno do ST7538, configurar os parâmetros do protocolo KISS,
etc.
Funções de Escrita: O driver deve receber dados através dessas funções. Devido à
forma que ele está implementado é necessário que esse dado esteja encapsulado
com o protocolo KISS.
Funções de Leitura: Essa é a forma que o driver envia dados para o usuário. Os
dados serão enviados utilizando protocolo KISS.
Funções de Buffers: Essas funções devem responder ao usuário informações sobre
os buffers, tais como quantos caracteres estão neles ou quanto espaço há livre
para aceitar novos caracteres.
4.5
User Space: kissattach
A escolha de um driver TTY para o kernel do Linux foi feita especialmente
pensando nesta parte do trabalho. Devido a grande utilidade de tais drivers para a
comunicação serial, já foram desenvolvidos programas de aplicação que se conectam
a eles, implementado assim um modem completo.
O kissattach faz parte do pacote ax25-util utilizado por rádios amadores, desenvolvido para funcionar com o kernel do Linux. AX.25 é um protocolo para redes
de rádio amador, que nesse caso é utilizado para um rede PLC, ele deve ter o seu
suporte compilado juntamente com o as operações de redes ,citadas em 4.1.1.1. do
kernel para que o programa de aplicação seja suportado. No caso de um radio amador a comunicação também é half-duplex, onde o ar é o meio fı́sico e somente um
radio pode falar de cada vez no meio, por essa similaridade com a comunicação PLC
tal protocolo também será utilizado no modem PLC.
Através desse programa é possı́vel emular um ponto IP, utilizando controle de
fluxo TCP, no computador. Esse programa se conecta a um driver TTY do Linux
utilizando o protocolo KISS. e assim, justifica-se a utilização de um driver TTY
juntamente com o protocolo KISS na implementação do modem. Devido ao fato de
que ele utiliza o protocolo TCP-IP, não precisamos nos preocupar com a questão de
endereçamento ou de controle de colisão pois tal protocolo irá descartar pacotes que
não sejam para si e também irá se comprometer a retransmitir pacotes perdidos.
Assim será possı́vel emular uma placa de rede através do modem PLC, porém vale
ressaltar que a velocidade será baixa.
Como dito anteriormente, para utilizar o kissattach é necessário ter compilado
o kernel com suporte ao protocolo AX.25, ter instalado a biblioteca ax25 e possuir
o pacote de utilidade ax25. Tais pacotes podem ser encontrados em (BAECHLE;
OSTERRIED, 2006). Inicialmente deve-se configurar o arquivo /etc/ax25/axports
conforme a Figura 4.12. Tal arquivo contem informações sobre configuração do meio
fı́sico como:
name é um identificar para o meio,
51
Figura 4.12: Configuração do Arquivo /etc/ax25/axports.
callsign contém uma identificação para radio amador e não precisa ser alterada,
speed é a velocidade do meio e deve ser setada de acordo com o que o modem
suporta,
paclen é o tamanho máximo do pacote ax25 a ser transmitido,
windows, é o tamanho da janela do protocolo ax25, ou seja, quantos pacotes ele irá
enviar anter de receber uma confirmação,
description bem é a descrição.
Como o arquivo /etc/ax25/axports configurado é possı́vel executar o programa
da aplicação. A Figura 4.13 demonstra um exemplo de como executar o programa.
Os argumentos são os seguintes:
Figura 4.13: Utilizando o kissattach.
/dev/ttyPLC é o device associado ao major do driver TTY,
52
1 identificador que está no arquivo /etc/ax25/axports,
192.168.0.1 endereço IP.
Após esses passos, pode-se confirmar a configuração e o endereço IP utilizando o
comando ifconfig conforme a Figura 4.14. Para verificar o estado do link utilizamos
o comando ping conforme a Figura 4.15 Assim podemos afirmar que o kissattach
está utilizando as funções do kernel para empacotar os dados com TCP/IP sobre
AX.25 e após sobre KISS. A seguir o driver do Linux é chamado juntamente com as
funções de protocolo kiss para se comunicar com as funções de protocolo HDLC. Essas últimas funções serão chamadas quando as funções de CSMA permitirem o acesso
ao meio. Por fim, após os dados serem encapsulados em HDLC as funções de acesso
ao hardware são chamadas passando a informação para o modem e completando o
ciclo.
Figura 4.14: Display do ifconfig.
A Figura 4.16 mostra a transmissão para o hardware dos dados encapsulados
com o protocolo HDLC oriundos do comando ping direcionado para a rede criada
com o programa kissattach. Pode-se perceber a colocação de flags do HDLC antes
dos dados passados para o driver. A Figura 4.17 apresenta a recepção do frame pelo
o modem receptor, de forma semalhente a transmissão como deveria ocorrer.
53
Figura 4.15: Modem Funcionando.
Figura 4.16: Transmissão de dados para o modem PLC.
54
Figura 4.17: Recepção de dados pelo o modem PLC.
55
5
CONCLUSÃO
Os conhecimentos adquiridos durante os três anos de trabalho como bolsista
de iniciação cientifica foram fundamentais para a consolidação do aprendizado da
faculdade e foram responsáveis pelo desenvolvimento do esquemático e da placa PCB
do modem PLC. A figura 5.1 mostra o modem PLC desenvolvido nesse trabalho
Figura 5.1: Foto do Modem PLC.
No desenvolvimento do software a escolha dos protocolos para a comunicação
PLC foi adequada, tendo em vista que mesmo com a grande quantidade de ruı́do na
rede elétrica a comunicação ocorre sem maiores problemas. O protocolo HDLC, com
o conceito de frames, bit-stuffing, e CRC permite a eliminação de pacotes errados e o
protocolo TCP/IP é responsável por retransmitir tais pacotes, bem como cuidar do
endereçamento. O controle de acesso ao meio CSMA-ppersistente se mostrou uma
boa alternativa para uma rede PLC.
56
Uma questão que poderia ser repensada seria o fato de ter utilizado um microprocessador menor no controle do modem PLC, tal modificação evitaria o ocorrências
de interrupções no computador e aumentaria o desempenho do mesmo. Porém tal
caminho não foi adotado devido ao fato de diminuir a complicação em hardware e
resolver em software os problemas, o fato do driver esta funcionando também indica
que a escolha tomada foi viável. Uma futura alteração que pode ser proposta neste
trabalho é a implementação de um driver de rede para Linux.
Tendo em vista que o objetivo era aprender a usar o ST7538, os resultados foram
satisfatórios. Por fim, vale ressaltar que o modem PLC desenvolvido neste relatório é
apenas um protótipo inicial, e como tal está sujeito a uma série de BUGs que só serão
descobertos e possivelmente corrigidos no decorrer da utilização do mesmo. Neste
trabalho foi construı́da uma rede PLC composta de dois modems e conseguimos
comunicar um com o outro através do comando ping. Esse trabalho fica disponı́vel
a todas as pessoas que quiserem formar um rede PLC com as ressalvas acima.
57
REFERÊNCIAS
BAECHLE, R.; OSTERRIED, T. AX.25 Utilities for Hamradio. Disponı́vel em:
<http://ax25.sourceforge.net/>. Acesso em: 04 dez. 2006.
CANTONE, G. ST7538 FSK Power Line Transceiver Demo-Kit Description. STMicroeletronics, 2003. Application Note.
IGUAÇU ENERGIA, L. Projeto PLC. Disponı́vel em: <http://www.ienergia.
com.br>. Acesso em: 04 dez. 2006.
KARN, P. Proposed Raw TNC Functional Spec. Santa Cruz, Califórnia, EUA,
1986.
MAJUMDER, A.; CAFFREY, J. Power Line Communications: an overview.
IEEE Potentials, vol.23, 2004.
METAWARE. Metaware High C Reference Manual. Santa Cruz, Califórnia,
EUA, 1994.
NETWORKING, P. Introduction to Powerline Communications. Disponı́vel em: <http://plc.qcslink.com/IntroPLC/PLCmain.htm>. Acesso em: 04 dez.
2006.
REDES, P. de. Redes PLC. Disponı́vel em: <http://www.projetoderedes.com.
br/tutoriais/tutorial_redes_plc_01.php>. Acesso em: 04 dez. 2006.
RUBINI, A.; CORBET, J. Linux Devices Driver. 2th.ed. Sebastopol, 2001.
SCHILDT, H. Turbo C, Guia do Usuário. 2th.ed. São Paulo, 1988.
TANENBAUM, A. Redes de Computadorees. 3th.ed. 2003.
58
59
APÊNDICE A
DEM PLC
DIAGRAMAS ESQUEMÁTICOS DO MO-
60
61
62
63
APÊNDICE B
ARTE FINAL DO PCB DO MODEM PLC
64
65
66
67
68
69
APÊNDICE C
DEM PLC
CÓDIGO FONTE DO DRIVER DO MO-
/*******************************************************************************
lpmodem Driver
Walter Fetter Lages <[email protected]>
Diego Caberlon Santini <[email protected]>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
Command line options (insmod command line)
major
major number the driver should use; default 124
parport port? [,port? [,port? [,port? ] ] ]
parport number to use
defbaud baud? [,baud? [,baud? [,baud? ] ] ]
default baud rate; default 2400
tx_delay delay? [,delay? [,delay? [,delay? ] ] ]
transmitter keyup delay in 10 ms; default 20
tx_tail tail? [,tail? [,tail? [,tail? ] ] ]
transmitter tail time in 10 ms; default 2
slottime time? [,time? [,time? [,time? ] ] ]
transmitter slot time in 10 ms; default 10
ppersist persist? [,persist? [,persist? [,persist? ] ] ]
tranmitter p persistence scaled to 0..255 range; default 63
fulldup duplex? [,duplex? [,duplex? [,duplex? ] ] ]
full duplex; default 0
History:
2006.06.19
Start development based on BIM 3.0.0
*******************************************************************************/
#include <linux/module.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
<linux/delay.h>
<linux/errno.h>
<linux/fs.h>
<linux/interrupt.h>
<linux/ioport.h>
<linux/mm.h>
<linux/sched.h>
<linux/workqueue.h>
<linux/tty.h>
<linux/tty_flip.h>
<linux/parport.h>
#include <st7538.h>
#include <lpmodem.h>
//lpmodem defines
70
#define PLC_DEBUG 2
#define PLC_IRQ 0x07
#define PLC_IRQ_ENABLE 0x10
#define writeMCR(handle,mcr) parport_write_data(handle->dev->port,mcr)
#define readMCR(handle,mcr) mcr = parport_read_data(handle->dev->port)
#define plc_timeout(ti) if(jiffies >= (ti+5)) return -EIO;
//no parport
#ifdef NOPAR
#undef parport_read_control
#undef parport_write_control
#define parport_read_control(Q) inb(0x37a)
#define parport_write_control(Q, data) outb(data,0x37a)
#undef parport_read_data
#undef parport_write_data
#define parport_read_data(Q) inb(0x378)
#define parport_write_data(Q, data) outb(data,0x378)
#undef parport_read_status
#define parport_read_status(Q) inb(0x379)
#endif
//Debug stuff
#if PLC_DEBUG > 0
#define DEBUGPLCF(arg,arg1) printk(arg,arg1)
#else
#define DEBUGPLCF(arg,arg1)
#endif
#if PLC_DEBUG > 0
#define DEBUGPLC1 printk
#if PLC_DEBUG > 1
#define DEBUGPLC2 printk
#else
#define DEBUGPLC2
#endif
#else
#define DEBUGPLC1
#endif
#ifndef KISS_VERBOSE
#define KISS_VERBOSE
#endif
#define LPMODEM_TYPE_NORMAL 0
#define TTY_DRIVER_TYPE_LPMODEM 6
#define BUFLEN_RX 8192
#define BUFLEN_TX 8192
#define NR_PORTS 4
#define LPMODEM_MAGIC 0x4c504d31
/**lpmodem Modem Status Register Address
*/
enum MSR_REGS
{
/* OE378#=0=>D5=0, IRQ7EN=0=>D4=0, SLCTIN#=1=>D3=0, STROBE#=1=>D0=0 */
MSR0
=0x02, /* A1=AUTOF#=0=>D1=1, A0=INIT#=0=>D2=0 */
MSR1
=0x06, /* A1=AUTOF#=0=>D1=1, A0=INIT#=1=>D2=1 */
MSR2
=0x00, /* A1=AUTOF#=1=>D1=0, A0=INIT#=0=>D2=0 */
NOMSR
=0x04, /* A1=AUTOF#=1=>D1=0, A0=INIT#=1=>D2=1 */
MSRMASK =0x06 /* MASK for A1 e A0*/
};
#define LPMODEM_EXTENT 3
/* Modem state flags */
#define ARB_TIMER_ON
0x01
/* default baud rate */
#define DFLT_BAUD
2400
static int major=124;
static
static
static
static
static
static
static
int
int
int
int
int
int
int
parport[4]={0xffffffff,0xffffffff,0xffffffff,0xffffffff};
defbaud[4]={DFLT_BAUD,DFLT_BAUD,DFLT_BAUD,DFLT_BAUD};
tx_delay[4]={20,20,20,20};
tx_tail[4]={2,2,2,2};
slottime[4]={10,10,10,10};
ppersist[4]={63,63,63,63};
fulldup[4]={0,0,0,0};
MODULE_AUTHOR("Diego Caberlon Santini <[email protected]>");
MODULE_DESCRIPTION("lpmodem Driver");
MODULE_SUPPORTED_DEVICE("ttyPLC");
module_param(major,int, 0);
MODULE_PARM_DESC(major,"major number the driver should use; default 124");
module_param_array(parport,int,NULL,0);
MODULE_PARM_DESC(parport,"port? [,port? [,port? [,port? ] ] ] parport to use; 0=parport0, 1=parport1, 2=parport2, 0xffffffff=none");
module_param_array(defbaud,int,NULL,0);
MODULE_PARM_DESC(defbaud,"baud? [,baud? [,baud? [,baud? ] ] ] default baud rate; default 2400");
module_param_array(tx_delay,int,NULL,0);
71
MODULE_PARM_DESC(tx_delay,"delay? [,delay? [,delay? [,delay? ] ] ] transmitter keyup delay in 10 ms; default 20");
module_param_array(tx_tail,int,NULL,0);
MODULE_PARM_DESC(tx_tail,"tail? [,tail? [,tail? [,tail? ] ] ] transmitter tail time in 10 ms; default 2");
module_param_array(slottime,int,NULL,0);
MODULE_PARM_DESC(slottime,"time? [,time? [,time? [,time? ] ] ] transmitter slot time in 10 ms; default 1000");
module_param_array(ppersist,int,NULL,0);
MODULE_PARM_DESC(ppersist,"persist? [,persist? [,persist? [,persist? ] ] ] tranmitter p persistence scaled to 0..255 range; default 63");
module_param_array(fulldup,int,NULL,0);
MODULE_PARM_DESC(fulldup,"duplex? [,duplex? [,duplex? [,duplex? ] ] ] full duplex; default 0");
enum BH_SCHED_BITS
{
BH_SCHED_RX=
BH_SCHED_TX=
};
struct access_params
{
int tx_delay; /*
int tx_tail;
/*
int slottime; /*
int ppersist; /*
int fulldup;
/*
0x01,
0x02
the transmitter keyup delay in 10ms units */
the transmitter keyoff delay in 10ms units */
the slottime in 10ms; usually 10 = 100ms */
the p-persistence 0..255 */
the driver does not support full duplex, setting
this just makes the driver send even if DCD is on */
};
#define BH_RX_BUFLEN
#define BH_TX_BUFLEN
256
16
struct hdlc_state_rx
{
int rx_state;
unsigned int bitstream;
unsigned int bitbuf;
int numbits;
/* bottom half RX buffer */
/* bottom half TX buffer */
/* 0 = sync hunt, != 0 receiving */
/* keep track of bitstream to catch stuffed bit */
/* hdlc input bit buffer */
/* number of bits at bitbuf */
unsigned char bh_buffer[BH_RX_BUFLEN];
/* bottom-half buffer */
int bh_wr;
/* bottom-half buffer write point */
int bh_rd;
/* bottom-half buffer read point */
int bh_buflen;
/* bottom-half buffer lenght; redundant but simpler */
int len;
/* buffer lenght in use */
unsigned char *bp;
/* buffer pointer */
unsigned char buffer[LPMODEM_MAXFLEN+2];
/* make room for CRC */
};
struct hdlc_state_tx
{
int tx_state;
/* 0=send flags,1=send txtail(flags),2=send packet */
int numflags;
/* number of flags to send */
unsigned int bitstream;
/* keep track of bit stream to insert stuff bit */
unsigned char ptt;
/* push to talk state */
unsigned char holdptt;
/* flag to release ptt after transmit */
unsigned int bitbuf;
int numbits;
/* buffer for hdlc output bits */
/* number of bits available at bitbuf */
unsigned char bh_buffer[BH_TX_BUFLEN];
/* bottom-half buffer */
int bh_wr;
/* bottom-half buffer write point */
int bh_rd;
/* bottom-half buffer read point */
int bh_buflen;
/* bottom-half buffer lenght; redundant but simpler */
int len;
/* frame buffer lenght in use */
unsigned char *bp;
/* frame buffer pointer */
unsigned char buffer[LPMODEM_MAXFLEN+2];
/* make room for CRC */
};
struct modem_state
{
unsigned char dcd;
unsigned int flags;
};
struct packet_buffer
{
unsigned int rd;
unsigned int wr;
unsigned int buflen;
unsigned char *buffer;
};
struct packet_hdr
{
unsigned int next;
unsigned int len;
/* packet follows */
};
#ifdef LPMODEM_DEBUG
struct bit_buffer
72
{
unsigned int rd;
unsigned int wr;
unsigned char buffer[64];
};
struct debug_vals
{
unsigned long last_jiffies;
unsigned cur_intcnt;
unsigned last_intcnt;
};
#endif /* LPMODEM_DEBUG */
struct kiss_decode
{
unsigned char dec_state; /* 0 = hunt FEND */
unsigned char escaped;
unsigned char pkt_buf[LPMODEM_MAXFLEN+1];
unsigned int wr;
};
struct lpmodem_state
{
int magic;
unsigned int parport;
int dflt_baud;
int opened;
struct tty_struct *tty;
struct pardevice *dev;
#ifdef LPMODEM_USE_BH
struct work_struct tq_receiver;
struct work_struct tq_transmitter;
#endif /* LPMODEM_USE_BH */
struct packet_buffer rx_buf;
struct packet_buffer tx_buf;
struct access_params ch_params;
struct hdlc_state_rx hdlc_rx;
struct hdlc_state_tx hdlc_tx;
struct modem_state modem;
struct timer_list arb_timer;
#ifdef LPMODEM_DEBUG
struct bit_buffer bitbuf_hdlc;
struct debug_vals debug_vals;
#endif /* LPMODEM_DEBUG */
struct kiss_decode kiss_decode;
struct lpmodem_statistics stat;
int preamble;
};
static struct
{
int parport;
int baud;
struct access_params dflt_ch_params;
} lpmodem_ports[NR_PORTS];
static struct tty_struct *lpmodem_ttys[NR_PORTS];
static struct termios *lpmodem_termios[NR_PORTS];
static struct termios *lpmodem_termios_locked[NR_PORTS];
static int lpmodem_refcount;
static struct tty_driver lpmodem_driver;
static struct lpmodem_state lpmodem_state[NR_PORTS];
static
static
static
static
static
static
static
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
unsigned
char rx_bit_buff;
char tx_bit_buff;
char rxcont;
char txcont;
char plc_fixme;
char plc_fixme2;
int aaa=0;
static void load_txbitbuff(struct lpmodem_state *lpmodem);
/*******************************************************************************
The CRC routines are stolen from WAMPES by Dieter Deyke
*******************************************************************************/
73
static const
{
0x0000,
0x8c48,
0x1081,
0x9cc9,
0x2102,
0xad4a,
0x3183,
0xbdcb,
0x4204,
0xce4c,
0x5285,
0xdecd,
0x6306,
0xef4e,
0x7387,
0xffcf,
0x8408,
0x0840,
0x9489,
0x18c1,
0xa50a,
0x2942,
0xb58b,
0x39c3,
0xc60c,
0x4a44,
0xd68d,
0x5ac5,
0xe70e,
0x6b46,
0xf78f,
0x7bc7,
};
unsigned short crc_ccitt_table[]=
0x1189,
0x9dc1,
0x0108,
0x8d40,
0x308b,
0xbcc3,
0x200a,
0xac42,
0x538d,
0xdfc5,
0x430c,
0xcf44,
0x728f,
0xfec7,
0x620e,
0xee46,
0x9581,
0x19c9,
0x8500,
0x0948,
0xb483,
0x38cb,
0xa402,
0x284a,
0xd785,
0x5bcd,
0xc704,
0x4b4c,
0xf687,
0x7acf,
0xe606,
0x6a4e,
0x2312,
0xaf5a,
0x3393,
0xbfdb,
0x0210,
0x8e58,
0x1291,
0x9ed9,
0x6116,
0xed5e,
0x7197,
0xfddf,
0x4014,
0xcc5c,
0x5095,
0xdcdd,
0xa71a,
0x2b52,
0xb79b,
0x3bd3,
0x8618,
0x0a50,
0x9699,
0x1ad1,
0xe51e,
0x6956,
0xf59f,
0x79d7,
0xc41c,
0x4854,
0xd49d,
0x58d5,
0x329b,
0xbed3,
0x221a,
0xae52,
0x1399,
0x9fd1,
0x0318,
0x8f50,
0x709f,
0xfcd7,
0x601e,
0xec56,
0x519d,
0xddd5,
0x411c,
0xcd54,
0xb693,
0x3adb,
0xa612,
0x2a5a,
0x9791,
0x1bd9,
0x8710,
0x0b58,
0xf497,
0x78df,
0xe416,
0x685e,
0xd595,
0x59dd,
0xc514,
0x495c,
0x4624,
0xca6c,
0x56a5,
0xdaed,
0x6726,
0xeb6e,
0x77a7,
0xfbef,
0x0420,
0x8868,
0x14a1,
0x98e9,
0x2522,
0xa96a,
0x35a3,
0xb9eb,
0xc22c,
0x4e64,
0xd2ad,
0x5ee5,
0xe32e,
0x6f66,
0xf3af,
0x7fe7,
0x8028,
0x0c60,
0x90a9,
0x1ce1,
0xa12a,
0x2d62,
0xb1ab,
0x3de3,
0x57ad,
0xdbe5,
0x472c,
0xcb64,
0x76af,
0xfae7,
0x662e,
0xea66,
0x15a9,
0x99e1,
0x0528,
0x8960,
0x34ab,
0xb8e3,
0x242a,
0xa862,
0xd3a5,
0x5fed,
0xc324,
0x4f6c,
0xf2a7,
0x7eef,
0xe226,
0x6e6e,
0x91a1,
0x1de9,
0x8120,
0x0d68,
0xb0a3,
0x3ceb,
0xa022,
0x2c6a,
0x6536,
0xe97e,
0x75b7,
0xf9ff,
0x4434,
0xc87c,
0x54b5,
0xd8fd,
0x2732,
0xab7a,
0x37b3,
0xbbfb,
0x0630,
0x8a78,
0x16b1,
0x9af9,
0xe13e,
0x6d76,
0xf1bf,
0x7df7,
0xc03c,
0x4c74,
0xd0bd,
0x5cf5,
0xa33a,
0x2f72,
0xb3bb,
0x3ff3,
0x8238,
0x0e70,
0x92b9,
0x1ef1,
0x74bf,
0xf8f7,
0x643e,
0xe876,
0x55bd,
0xd9f5,
0x453c,
0xc974,
0x36bb,
0xbaf3,
0x263a,
0xaa72,
0x17b9,
0x9bf1,
0x0738,
0x8b70,
0xf0b7,
0x7cff,
0xe036,
0x6c7e,
0xd1b5,
0x5dfd,
0xc134,
0x4d7c,
0xb2b3,
0x3efb,
0xa232,
0x2e7a,
0x93b1,
0x1ff9,
0x8330,
0x0f78
static void append_crc_ccitt(unsigned char *buffer,int len)
{
unsigned short crc=0xffff;
DEBUGPLCF("%s\n",__FUNCTION__);
for(;len > 0;len--) crc=(crc >> 8) ^ crc_ccitt_table[(crc ^ *buffer++) & 0xff];
crc^= 0xffff;
*buffer++=crc;
*buffer++=crc >> 8;
}
static int check_crc_ccitt(const unsigned char *buf,int cnt)
{
unsigned short crc=0xffff;
DEBUGPLCF("%s\n",__FUNCTION__);
for(; cnt > 0; cnt--) crc=(crc >> 8) ^ crc_ccitt_table[(crc ^ *buf++) & 0xff];
return (crc & 0xffff) == 0xf0b8;
}
static int baud_table[]=
{
0,50,75,110,134,150,200,300,600,1200,1800,2400,4800,
9600,19200,38400,57600,115200
};
static int baud(struct lpmodem_state *lpmodem)
{
unsigned int i=lpmodem->tty->termios->c_cflag & CBAUD;
DEBUGPLCF("%s\n",__FUNCTION__);
if(i & CBAUDEX)
{
i&=~CBAUDEX;
i+=15;
}
return baud_table[i];
}
static inline unsigned int tenms_to_flags(struct lpmodem_state *lpmodem,unsigned int tenms)
{
return (tenms*baud(lpmodem)+999)/1000;
}
/*******************************************************************************
KISS decoder functions
*******************************************************************************/
static int store_packet(struct packet_buffer *buf,unsigned char *data,char from_user,unsigned int len)
{
struct packet_hdr *hdr;
unsigned int needed=sizeof(struct packet_hdr)+len;
74
int err;
unsigned int free=buf->rd-buf->wr;
DEBUGPLCF("%s\n",__FUNCTION__);
if(buf->rd <= buf->wr)
{
free=buf->buflen-buf->wr;
if((free < needed) && (buf->rd >= needed))
{
hdr=(struct packet_hdr *)(buf->buffer+buf->wr);
hdr->next=0;
hdr->len=0;
buf->wr=0;
free=buf->rd;
}
}
if(free < needed) return 0;
/* buffer overrun */
hdr=(struct packet_hdr *)(buf->buffer+buf->wr);
if(from_user)
{
if((err=copy_from_user(hdr+1,data,len))) {
return err;}
}
else memcpy(hdr+1,data,len);
hdr->len=len;
hdr->next=buf->wr+needed;
if(hdr->next+sizeof(struct packet_hdr) >= buf->buflen) hdr->next=0;
buf->wr=hdr->next;
return 1;
}
static void get_packet(struct packet_buffer *buf,unsigned char **data,unsigned int *len)
{
struct packet_hdr *hdr;
DEBUGPLCF("%s\n",__FUNCTION__);
*data=NULL;
*len=0;
if(buf->rd == buf->wr) return;
hdr=(struct packet_hdr *)(buf->buffer+buf->rd);
while(!(hdr->len))
{
buf->rd=hdr->next;
if (buf->rd == buf->wr)
return;
hdr=(struct packet_hdr *)(buf->buffer+buf->rd);
}
*data=(unsigned char *)(hdr+1);
*len=hdr->len;
}
static void ack_packet(struct packet_buffer *buf)
{
struct packet_hdr *hdr;
DEBUGPLCF("%s\n",__FUNCTION__);
if (buf->rd == buf->wr)
return;
hdr=(struct packet_hdr *)(buf->buffer+buf->rd);
buf->rd=hdr->next;
}
static int store_kiss_packet(struct packet_buffer *buf,unsigned char *data,unsigned int len)
{
unsigned char *bp=data;
int ln=len;
/* variables of buf */
unsigned
unsigned
unsigned
unsigned
int rd;
int wr;
int buflen;
char *buffer;
DEBUGPLCF("%s\n",__FUNCTION__);
if(!len || !data || !buf) return 0;
buflen=buf->buflen;
rd=buf->rd;
wr=buf->wr;
buffer=buf->buffer;
#define ADD_CHAR(c) {\
buffer[wr++] = c;\
if (wr >= buflen) wr = 0;\
if (wr == rd) return 0;\
}
#define ADD_KISSCHAR(c) {\
if (((c) & 0xff) == KISS_FEND) {\
ADD_CHAR(KISS_FESC);\
ADD_CHAR(KISS_TFEND);\
} else if (((c) & 0xff) == KISS_FESC) {\
ADD_CHAR(KISS_FESC);\
ADD_CHAR(KISS_TFESC);\
} else {\
ADD_CHAR(c);\
}\
75
}
ADD_CHAR(KISS_FEND);
ADD_KISSCHAR(KISS_CMD_DATA);
for(;ln > 0;ln--,bp++) ADD_KISSCHAR(*bp);
ADD_CHAR(KISS_FEND);
buf->wr=wr;
#undef ADD_CHAR
#undef ADD_KISSCHAR
return 1;
}
static void lpmodem_put_fend(struct lpmodem_state *lpmodem)
{
DEBUGPLCF("%s\n",__FUNCTION__);
if(lpmodem->kiss_decode.wr <= 0 || (lpmodem->kiss_decode.pkt_buf[0] & 0xf0) != 0) return;
switch (lpmodem->kiss_decode.pkt_buf[0] & 0xf)
{
case KISS_CMD_DATA:
{
DEBUGPLC2("store packet\n");
if(lpmodem->kiss_decode.wr <= 8) break;
if(!store_packet(&lpmodem->tx_buf,lpmodem->kiss_decode.pkt_buf+1,0,lpmodem->kiss_decode.wr-1)) lpmodem->stat.tx_bufferoverrun++;
break;
}
case KISS_CMD_TXDELAY:
{
if(lpmodem->kiss_decode.wr < 2) break;
lpmodem->ch_params.tx_delay=lpmodem->kiss_decode.pkt_buf[1];
#ifdef KISS_VERBOSE
printk(KERN_INFO "lpmodem: TX delay = %ums\n",lpmodem->ch_params.tx_delay * 10);
#endif /* KISS_VERBOSE */
break;
}
case KISS_CMD_PPERSIST:
{
if(lpmodem->kiss_decode.wr < 2) break;
lpmodem->ch_params.ppersist=lpmodem->kiss_decode.pkt_buf[1];
#ifdef KISS_VERBOSE
printk(KERN_INFO "lpmodem: p-persistence = %u\n",lpmodem->ch_params.ppersist);
#endif /* KISS_VERBOSE */
break;
}
case KISS_CMD_SLOTTIME:
{
if(lpmodem->kiss_decode.wr < 2) break;
lpmodem->ch_params.slottime=lpmodem->kiss_decode.pkt_buf[1];
#ifdef KISS_VERBOSE
printk(KERN_INFO "lpmodem: slottime = %ums\n",lpmodem->ch_params.slottime*10);
#endif /* KISS_VERBOSE */
break;
}
case KISS_CMD_TXTAIL:
{
if(lpmodem->kiss_decode.wr < 2) break;
lpmodem->ch_params.tx_tail=lpmodem->kiss_decode.pkt_buf[1];
#ifdef KISS_VERBOSE
printk(KERN_INFO "lpmodem: TX tail = %ums\n",lpmodem->ch_params.tx_tail*10);
#endif /* KISS_VERBOSE */
break;
}
case KISS_CMD_FULLDUP:
{
if(lpmodem->kiss_decode.wr < 2) break;
lpmodem->ch_params.fulldup=lpmodem->kiss_decode.pkt_buf[1];
#ifdef KISS_VERBOSE
printk(KERN_INFO "lpmodem: %s duplex\n",lpmodem->ch_params.fulldup ? "full" : "half");
#endif /* KISS_VERBOSE */
break;
}
default:
{
#ifdef KISS_VERBOSE
printk(KERN_INFO "lpmodem: unhandled KISS packet code %u\n",lpmodem->kiss_decode.pkt_buf[0] & 0xf);
#endif /* KISS_VERBOSE */
break;
}
76
}
}
/******************************************************************************
lpmodem hardware access function
*******************************************************************************/
static int writeTX(struct lpmodem_state *handle, unsigned char data)
{
DEBUGPLCF("%s\n",__FUNCTION__);
if(handle == NULL) return -EINVAL;
writeMCR(handle, (data & MCR_TXD));
return 0;
}
static int readRX(struct lpmodem_state *handle)
{
unsigned char status;
unsigned char control;
DEBUGPLCF("%s\n",__FUNCTION__);
if(handle==NULL) return -EINVAL; /* Invalid argument */
control = parport_read_control(handle->dev->port);
control &= ~MSRMASK;
parport_write_control(handle->dev->port,MSR0 | control);
status=parport_read_status(handle->dev->port);
parport_write_control(handle->dev->port,NOMSR | control);
return ((status & PLC_MSR_RXD) >> 4);
}
static int readMSR(struct lpmodem_state *handle,plcMSR *msr)
{
unsigned char status;
unsigned char control;
DEBUGPLCF("%s\n",__FUNCTION__);
if(handle==NULL) return -EINVAL; /* Invalid argument */
control = parport_read_control(handle->dev->port);
control &= ~MSRMASK;
parport_write_control(handle->dev->port,MSR0 | control);
status=parport_read_status(handle->dev->port);
/* BUSY signal in conector is inverted with respect to the
#BUSY bit (bit 7) in status register */
*msr=(plcMSR)(status ^ 0x80);
parport_write_control(handle->dev->port,MSR1 | control);
status=parport_read_status(handle->dev->port);
/* BUSY signal in conector is inverted with respect to the
#BUSY bit (bit 7) in status register*/
*msr|=(PLC_MSR_PG | PLC_MSR_REGOK_ | PLC_MSR_TOUT | PLC_MSR_RESET_) & (((plcMSR)(status ^ 0x80))<<8);
parport_write_control(handle->dev->port,MSR2 | control);
status=parport_read_status(handle->dev->port);
/* BUSY signal in conector is inverted with respect to the
#BUSY bit (bit 7) in status register*/
*msr|=(PLC_MSR_CD_PD | PLC_MSR_BU | PLC_MSR_ZCOUT | PLC_MSR_RESET_)
parport_write_control(handle->dev->port,NOMSR | control);
return 0;
}
static int readRegister(struct lpmodem_state *handle,st7538reg *reg)
{
plcMSR status=0;
int n, count;
unsigned long ti;
st7538reg regtmp=0;
int error;
DEBUGPLCF("%s\n",__FUNCTION__);
if(handle==NULL) return -EINVAL; /* Invalid argument */
*reg=1;
disable_irq(PLC_IRQ);
/* Read now */
writeMCR(handle,MCR_RXTX | MCR_REG_DATA | MCR_WD_);
DEBUGPLC1("Lendo Registrador interno:");
& (((plcMSR)(status ^ 0x80))<<16);
77
for(n=23;n >= 0;n--)
{
ti=jiffies;
count=0;
/* RXD data is available on the rising edge of CLRT. Wait for it */
do
{
if((error=readMSR(handle,&status))) return error;
plc_timeout(ti)/* I/O error */
count++;
}
while( !(status & PLC_MSR_CLRT) );
/* Too fast, this isnt a data*/
if(count == 1)
n++;
if (n !=24){
if((error=readMSR(handle,&status))) return error;
regtmp=(status & PLC_MSR_RXD) >> 4; /* RXD is bit 4 */
DEBUGPLC2("%ld", (status & PLC_MSR_RXD) >> 4);
regtmp<<=n;
*reg|=regtmp;
}
/* RXD data is available on the rising edge of CLRT.
Wait for the high state to prepare for the read of the next bit */
ti=jiffies;
do
{
if((error=readMSR(handle,&status))) return error;
if(jiffies >=(ti+2) ) return -EIO; /* I/O error */
}
while(status & PLC_MSR_CLRT);
}
DEBUGPLC1(":0x%lx\n",*(unsigned long int *)reg);
/* Back to rx mode */
writeMCR(handle,MCR_RXTX | MCR_WD_);
enable_irq(PLC_IRQ);
return 0;
}
static int writeRegister(struct lpmodem_state *handle,st7538reg *reg)
{
st7538reg buffer;
unsigned long ti;
int n;
plcMSR status;
int error;
DEBUGPLCF("%s\n",__FUNCTION__);
if(handle==NULL) return EINVAL;
/* Invalid argument */
DEBUGPLC1("Escrevendo no registrador:");
DEBUGPLC1("0x%lx:\n", *reg);
disable_irq(PLC_IRQ);
/* Set what we need */;
writeMCR(handle,MCR_REG_DATA | MCR_WD_);
/* Give it a time..., the critical point is the end, not the begin*/
msleep(100);
for(n=23;n >= 0;n--)
{
ti=jiffies;
/* TXD is loaded on the rising edge of CLRT. Wait for the low state */
do
{
if((error=readMSR(handle,&status))) return error;
plc_timeout(ti) /* I/O error */
}
while( (status & PLC_MSR_CLRT) );
buffer=*reg >> n;
buffer<<=1;
/* shift the bit to transmit to bit 0 */
/* TXD is bit 1 */
buffer=(buffer & MCR_TXD) | MCR_REG_DATA | MCR_WD_;
writeMCR(handle,buffer);
DEBUGPLC2("%d",(unsigned)((buffer & MCR_TXD) >> 1));
ti=jiffies;
/* TXD is loaded on the rising edge of CLRT. Wait for it. */
do
{
if((error=readMSR(handle,&status))) return error;
plc_timeout(ti) /* I/O error */
}
while(!(status & PLC_MSR_CLRT));
}
78
/* This is important because the register is write now */
writeMCR(handle,MCR_RXTX);
msleep(100);
writeMCR(handle,MCR_RXTX | MCR_WD_);
DEBUGPLC1("\n");
enable_irq(PLC_IRQ);
return 0;
}
/*******************************************************************************
Tx Arbitration functions
*******************************************************************************/
/*
* ANSI sample implementation borrowed from Metaware High C Reference Manual
* ANSI uses 1 as seed. Here seed is get from jiffies to be more, well, random.
*/
static unsigned long int rand_seed;
static inline int rand(void)
{
rand_seed=1103515245UL*rand_seed+12345;
return (unsigned int) (rand_seed/65536) % 32768;
}
inline unsigned char preamble_tx(struct lpmodem_state *lpmodem)
{
if(lpmodem->preamble < 0) return 0;
return (lpmodem->preamble--)? 0xaa:0xff;
}
static inline void ptt(struct lpmodem_state *lpmodem,int value)
{
//
unsigned char ier;
//
unsigned int mcr=inb(MCR(lpmodem->iobase));
if(value)
{
//
outb(mcr | RTS,MCR(lpmodem->iobase));
/* set RTS */
lpmodem->hdlc_tx.holdptt=1;
lpmodem->hdlc_tx.ptt=1;
lpmodem->stat.ptt_keyed++;
lpmodem->preamble=(3*baud(lpmodem)+9999)/10000;
/* enable TX interrupt */
DEBUGPLC2("Enable TX\n");
load_txbitbuff(lpmodem);
txcont++;
plc_fixme = 0;
writeMCR(lpmodem, (((tx_bit_buff >> (8 - txcont)) << 1) & MCR_TXD));
}
else
{
lpmodem->hdlc_tx.ptt=0;
/* disable tx interrupt */
DEBUGPLC2("Diseble TX\n");
writeMCR(lpmodem,MCR_RXTX);
lpmodem->hdlc_tx.holdptt=0;
}
}
static inline void tx_arbitrate(struct lpmodem_state *lpmodem)
{
unsigned char *bp;
unsigned int len;
if(!lpmodem || lpmodem->hdlc_tx.ptt || lpmodem->modem.dcd) {
return;}
get_packet(&lpmodem->tx_buf,&bp,&len);
if(!bp || !len) return;
if(!lpmodem->ch_params.fulldup)
{
if((rand() % 256) > lpmodem->ch_params.ppersist) return;
}
lpmodem->hdlc_tx.tx_state=0;
if(!(lpmodem->hdlc_tx.numflags=tenms_to_flags(lpmodem,lpmodem->ch_params.tx_delay))) lpmodem->hdlc_tx.numflags=1;
lpmodem->hdlc_tx.numbits=lpmodem->hdlc_tx.bitbuf=lpmodem->hdlc_tx.bitstream=0;
ptt(lpmodem,1);
}
static inline void tx_release(struct lpmodem_state *lpmodem)
{
lpmodem->hdlc_tx.tx_state=1;
if(!(lpmodem->hdlc_tx.numflags=tenms_to_flags(lpmodem,lpmodem->ch_params.tx_tail))) lpmodem->hdlc_tx.numflags=1;
if(lpmodem->hdlc_tx.numbits % 8) lpmodem->hdlc_tx.numflags++;
}
static inline void rx_arbitrate(struct lpmodem_state *lpmodem)
{
79
if(!lpmodem || !lpmodem->hdlc_tx.ptt) return;
if(!lpmodem->ch_params.fulldup)
{
if((rand() % 256) > lpmodem->ch_params.ppersist) return;
tx_release(lpmodem);
}
}
static void arb_sched(unsigned long data)
{
struct lpmodem_state *lpmodem=(struct lpmodem_state *)data;
unsigned long ms;
static unsigned char rxcont_bak;
plcMSR msr;
DEBUGPLCF("%s\n",__FUNCTION__);
if(!lpmodem || lpmodem->magic != LPMODEM_MAGIC) return;
ms=lpmodem->ch_params.slottime*10;
del_timer(&lpmodem->arb_timer);
lpmodem->arb_timer.expires=jiffies+(ms*HZ+999)/1000+1;
add_timer(&lpmodem->arb_timer);
/* if transmitting don’t even test DCD */
if(lpmodem->hdlc_tx.ptt) return;
readMSR(lpmodem, &msr);
lpmodem->modem.dcd = (msr & PLC_MSR_CD_PD);
if( ~(lpmodem->modem.dcd & PLC_MSR_CD_PD) & (rxcont == rxcont_bak)){
plc_fixme2=1;
}
rxcont_bak = rxcont;
tx_arbitrate(lpmodem);
}
/*******************************************************************************
HDLC functions
********************************************************************************/
#ifdef LPMODEM_DEBUG
inline static void add_bitbuffer_byte(struct bit_buffer *buf,unsigned char byte)
{
buf->buffer[buf->wr]=byte;
buf->wr=(buf->wr+1)%sizeof(buf->buffer);
}
#endif /* LPMODEM_DEBUG */
/*
* This routine moves received bytes from rx buffer to flip buffer.
* The flip buffer is located in the tty structure, and is used as a high
* speed interface between the tty driver and the tty line discipline.
*/
static inline void rx_chars_to_flip(struct lpmodem_state *lpmodem)
{
unsigned int cnt;
unsigned int new_rd;
int i;
if(lpmodem->rx_buf.rd <= lpmodem->rx_buf.wr) cnt=lpmodem->rx_buf.wr-lpmodem->rx_buf.rd;
else cnt=lpmodem->rx_buf.buflen-lpmodem->rx_buf.rd;
//
for(i=0;i<cnt;i++){
printk("recebi %x\t",((unsigned char*)(lpmodem->rx_buf.buffer+lpmodem->rx_buf.rd))[i]);
tty_insert_flip_char(lpmodem->tty, ((unsigned char*)(lpmodem->rx_buf.buffer+lpmodem->rx_buf.rd))[i], TTY_NORMAL);
}
tty_schedule_flip(lpmodem->tty);
/* updates rx buffer pointer */
new_rd=lpmodem->rx_buf.rd+cnt;
if(new_rd >= lpmodem->rx_buf.buflen) new_rd -= lpmodem->rx_buf.buflen;
lpmodem->rx_buf.rd=new_rd;
}
static inline int hdlc_rx_add_bytes(struct lpmodem_state *lpmodem,unsigned int bits,int num)
{
int added=0;
while(lpmodem->hdlc_rx.rx_state && num >= 8)
{
if((unsigned int)lpmodem->hdlc_rx.len >= sizeof(lpmodem->hdlc_rx.buffer))
{
/* buffer overflow */
lpmodem->hdlc_rx.rx_state=0;
return 0;
}
*lpmodem->hdlc_rx.bp++=bits >> (16-num);
lpmodem->hdlc_rx.len++;
num-=8;
added+=8;
}
80
return added;
}
static inline void hdlc_rx_flag(struct lpmodem_state *lpmodem)
{
if(lpmodem->hdlc_rx.len < 4) return;
if(!check_crc_ccitt(lpmodem->hdlc_rx.buffer,lpmodem->hdlc_rx.len)) return;
lpmodem->stat.rx_packets++;
if(!store_kiss_packet(&lpmodem->rx_buf,lpmodem->hdlc_rx.buffer,lpmodem->hdlc_rx.len-2)) lpmodem->stat.rx_bufferoverrun++;
rx_chars_to_flip(lpmodem);
/* send packet to flip buffer */
}
static void hdlc_rx_byte(struct lpmodem_state *lpmodem,unsigned int byte)
{
int i;
unsigned int mask1;
unsigned int mask2;
unsigned int mask3;
unsigned int mask4;
unsigned int mask5;
unsigned int mask6;
DEBUGPLCF("%s\n",__FUNCTION__);
if(!lpmodem) return;
DEBUGPLC2("RECEBI RX: %x\n",byte);
byte&=0xff;
/* mask unused bits */
#ifdef LPMODEM_DEBUG
add_bitbuffer_byte(&lpmodem->bitbuf_hdlc,byte);
#endif /* LPMODEM_DEBUG */
lpmodem->hdlc_rx.bitstream >>= 8;
lpmodem->hdlc_rx.bitstream |= byte << 8;
lpmodem->hdlc_rx.bitbuf >>= 8;
lpmodem->hdlc_rx.bitbuf |= byte << 8;
lpmodem->hdlc_rx.numbits+=8;
/* check stuffed bit */
for(i=7,mask1=0x1fc,mask2=0x1fe,mask3=0x0fc,
mask4=0x1f8,mask5=0xf8,mask6=0xff;
i >= 0;
i--,mask1 <<= 1,mask2 <<= 1,mask3 <<= 1,mask4 <<= 1,
mask5 <<= 1,mask6 = (mask6 << 1) | 1)
{
if((lpmodem->hdlc_rx.bitstream & mask1) == mask1) lpmodem->hdlc_rx.rx_state=0; /* abort received */
else if((lpmodem->hdlc_rx.bitstream & mask2) == mask3)
{
/* flag received */
if(lpmodem->hdlc_rx.rx_state)
{
/* terminate frame */
hdlc_rx_add_bytes(lpmodem,lpmodem->hdlc_rx.bitbuf << (8 + i),lpmodem->hdlc_rx.numbits-8-i);
hdlc_rx_flag(lpmodem);
}
/* start new frame */
lpmodem->hdlc_rx.len=0;
lpmodem->hdlc_rx.bp=lpmodem->hdlc_rx.buffer;
lpmodem->hdlc_rx.rx_state=1;
lpmodem->hdlc_rx.numbits=i;
}
else if((lpmodem->hdlc_rx.bitstream & mask4) == mask5)
{
/* stuffed bit */
lpmodem->hdlc_rx.numbits--;
lpmodem->hdlc_rx.bitbuf=(lpmodem->hdlc_rx.bitbuf & (~mask6)) |
((lpmodem->hdlc_rx.bitbuf & mask6) << 1);
}
}
lpmodem->hdlc_rx.numbits-=hdlc_rx_add_bytes(lpmodem,lpmodem->hdlc_rx.bitbuf,lpmodem->hdlc_rx.numbits);
}
static unsigned char hdlc_tx_byte(struct lpmodem_state *lpmodem)
{
unsigned char ret;
unsigned int mask1;
unsigned int mask2;
unsigned int mask3;
int i;
DEBUGPLCF("%s\n",__FUNCTION__);
if(!lpmodem || !lpmodem->hdlc_tx.ptt) return 0;
for(;;)
{
/* if there are bits availabe at bitbuf return them */
if(lpmodem->hdlc_tx.numbits >= 8)
{
ret=lpmodem->hdlc_tx.bitbuf & 0xff;
lpmodem->hdlc_tx.bitbuf>>=8;
lpmodem->hdlc_tx.numbits-=8;
return ret;
}
switch(lpmodem->hdlc_tx.tx_state)
{
81
default:
/* reset */
{
ptt(lpmodem,0);
lpmodem->hdlc_tx.tx_state=0;
return 0;
}
case 0:
/* send flags */
case 1:
/* send txtail(flags) */
{
if(lpmodem->hdlc_tx.numflags)
{
/* insert flags into bitbuf */
lpmodem->hdlc_tx.numflags--;
lpmodem->hdlc_tx.bitbuf|=0x7e << lpmodem->hdlc_tx.numbits;
lpmodem->hdlc_tx.numbits+=8;
break;
}
if(lpmodem->hdlc_tx.tx_state == 1)
{
/* no more flags, reset ptt */
lpmodem->hdlc_tx.holdptt=0;
return 0;
}
/* get packet pointer and lenght */
get_packet(&lpmodem->tx_buf,&lpmodem->hdlc_tx.bp,(unsigned int *)&lpmodem->hdlc_tx.len);
if(!lpmodem->hdlc_tx.bp || !lpmodem->hdlc_tx.len)
{
/* no more packets */
tx_release(lpmodem);
break;
}
if(lpmodem->hdlc_tx.len >= LPMODEM_MAXFLEN)
{
/* packet too big, discard */
lpmodem->hdlc_tx.tx_state=0;
lpmodem->hdlc_tx.numflags=1;
ack_packet(&lpmodem->tx_buf);
/* chance to revert channel */
rx_arbitrate(lpmodem);
break;
}
/* copy packet to buffer */
memcpy(lpmodem->hdlc_tx.buffer,lpmodem->hdlc_tx.bp,lpmodem->hdlc_tx.len);
ack_packet(&lpmodem->tx_buf);
lpmodem->hdlc_tx.bp=lpmodem->hdlc_tx.buffer;
append_crc_ccitt(lpmodem->hdlc_tx.buffer,lpmodem->hdlc_tx.len);
/* the appended CRC */
lpmodem->hdlc_tx.len+=2;
lpmodem->hdlc_tx.tx_state=2;
/* send packet */
lpmodem->hdlc_tx.bitstream=0;
lpmodem->stat.tx_packets++;
break;
}
case 2:
/* send packet */
{
if(!lpmodem->hdlc_tx.len)
{
/* packet end */
lpmodem->hdlc_tx.tx_state=0;
lpmodem->hdlc_tx.numflags=1;
/* chance to revert channel */
rx_arbitrate(lpmodem);
break;
}
lpmodem->hdlc_tx.len--;
lpmodem->hdlc_tx.bitbuf |= *lpmodem->hdlc_tx.bp << lpmodem->hdlc_tx.numbits;
lpmodem->hdlc_tx.bitstream >>= 8;
lpmodem->hdlc_tx.bitstream |= (*lpmodem->hdlc_tx.bp++) << 16;
/* test if stuff bit is needed */
mask1=0x1f000;
mask2=0x10000;
mask3=0xffffffff >> (31-lpmodem->hdlc_tx.numbits);
lpmodem->hdlc_tx.numbits+=8;
for(i=0;i < 8;i++,mask1 <<= 1,mask2 <<= 1,mask3 = (mask3 << 1) | 1)
{
if((lpmodem->hdlc_tx.bitstream & mask1) != mask1) continue;
/* insert stuff bit */
lpmodem->hdlc_tx.bitstream&=~mask2;
lpmodem->hdlc_tx.bitbuf=(lpmodem->hdlc_tx.bitbuf & mask3) | ((lpmodem->hdlc_tx.bitbuf & (~mask3)) << 1);
lpmodem->hdlc_tx.numbits++;
mask3=(mask3 << 1) | 1;
}
break;
}
}
}
}
/*******************************************************************************
Interrupt & Bottom-half functions
*******************************************************************************/
#ifdef LPMODEM_DEBUG
82
/*
* This routine measures how many interrupts are received per second.
*/
inline static void lpmodem_int_freq(struct lpmodem_state *lpmodem)
{
unsigned long cur_jiffies=jiffies;
/* measure the interrupt frequency */
lpmodem->debug_vals.cur_intcnt++;
if((cur_jiffies-lpmodem->debug_vals.last_jiffies) >= HZ)
{
lpmodem->debug_vals.last_jiffies=cur_jiffies;
lpmodem->debug_vals.last_intcnt=lpmodem->debug_vals.cur_intcnt;
lpmodem->debug_vals.cur_intcnt=0;
}
}
#endif /* LPMODEM_DEBUG */
static void load_txbitbuff(struct lpmodem_state *lpmodem)
{
#ifdef LPMODEM_USE_BH
int bh_sched = 0;
#endif /* LPMODEM_USE_BH */
DEBUGPLCF("%s\n",__FUNCTION__);
tx_bit_buff=preamble_tx(lpmodem);
if(!tx_bit_buff)
{
/* no more preamble */
#ifdef LPMODEM_USE_BH
/* check end of transmition */
if(!lpmodem->hdlc_tx.bh_buflen){
ptt(lpmodem,0);
}
tx_bit_buff=lpmodem->hdlc_tx.bh_buffer[lpmodem->hdlc_tx.bh_rd];
lpmodem->hdlc_tx.bh_rd=(lpmodem->hdlc_tx.bh_rd+1)%BH_TX_BUFLEN;
lpmodem->hdlc_tx.bh_buflen--;
bh_sched|=BH_SCHED_TX;
#else /* LPMODEM_USE_BH */
if(!lpmodem->hdlc_tx.holdptt){
ptt(lpmodem,0);
}
tx_bit_buff = hdlc_tx_byte(lpmodem);
#endif /* LPMODEM_USE_BH */
}
#ifdef LPMODEM_USE_BH
if(bh_sched & BH_SCHED_TX) schedule_work(&lpmodem->tq_transmitter);
#endif /* LPMODEM_USE_BH */
return;
}
irqreturn_t lpmodem_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
register struct lpmodem_state *lpmodem=(struct lpmodem_state *)dev_id;
//
DEBUGPLC1("Interrupt\n");
#ifdef LPMODEM_USE_BH
int bh_sched;
#endif /* LPMODEM_USE_BH */
DEBUGPLCF("%s\n",__FUNCTION__);
if(lpmodem == NULL || lpmodem->magic != LPMODEM_MAGIC) return IRQ_NONE;
#ifdef LPMODEM_DEBUG
lpmodem_int_freq(lpmodem);
#endif /* LPMODEM_DEBUG */
#ifdef LPMODEM_USE_BH
bh_sched=0;
#endif /* LPMODEM_USE_BH */
if(!lpmodem->hdlc_tx.ptt)
{
//
if(aaa != jiffies){
rx_bit_buff |= (readRX(lpmodem) << (7 - rxcont));
rxcont++;
if((plc_fixme2 == 1) && (rxcont == 8))
{
if(rx_bit_buff != 0xff)
{
rx_bit_buff = (rx_bit_buff << 1);
rxcont--;
}
else
{
plc_fixme2 = 0;
}
DEBUGPLC2("RECEBI RX: %x\n",rx_bit_buff);
83
}
if(rxcont == 8)
{
rxcont =0;
#ifdef LPMODEM_USE_BH
if(lpmodem->hdlc_rx.bh_buflen >= BH_RX_BUFLEN)
{
/* bh buffer overflow */
#ifdef LPMODEM_DEBUG
printk("lpmodem: RX bottom-half buffer overflow\n");
#endif
/* LPMODEM_DEBUG */
return -1;
}
lpmodem->hdlc_rx.bh_buffer[lpmodem->hdlc_rx.bh_wr]=rx_bit_buff;
lpmodem->hdlc_rx.bh_wr=(lpmodem->hdlc_rx.bh_wr+1)%BH_RX_BUFLEN;
lpmodem->hdlc_rx.bh_buflen++;
bh_sched|=BH_SCHED_RX;
#else /* LPMODEM_USE_BH */
hdlc_rx_byte(lpmodem,rx_bit_buff);
#endif /* LPMODEM_USE_BH */
//
DEBUGPLC2("RECEBI RX: %x\n",rx_bit_buff);
rx_bit_buff= 0;
}
}}
else
{
if ( (plc_fixme == 0) || plc_fixme ==1){
plc_fixme++;
return IRQ_HANDLED;
}
else
{
if(aaa != jiffies){
//
printk("%d",(inb(0x378) & MCR_TXD) >> 1);
txcont++;
writeTX(lpmodem,((tx_bit_buff >> (8 - txcont)) << 1));
if (txcont == 8)
{
load_txbitbuff(lpmodem);
txcont =0;
}
}
}
}
#ifdef LPMODEM_USE_BH
if(bh_sched & BH_SCHED_RX) schedule_work(&lpmodem->tq_receiver);
#endif /* LPMODEM_USE_BH */
aaa=jiffies;
return IRQ_HANDLED;
}
/* Bottom half */
#ifdef LPMODEM_USE_BH
static void bh_receiver(void *priv)
{
struct lpmodem_state *lpmodem=(struct lpmodem_state *)priv;
unsigned long flags;
spinlock_t lpmodem_lock=SPIN_LOCK_UNLOCKED;
unsigned char data;
DEBUGPLCF("%s\n",__FUNCTION__);
if(!lpmodem || lpmodem->magic != LPMODEM_MAGIC) return;
while(lpmodem->hdlc_rx.bh_buflen)
{
spin_lock_irqsave(&lpmodem_lock,flags);
data=lpmodem->hdlc_rx.bh_buffer[lpmodem->hdlc_rx.bh_rd];
lpmodem->hdlc_rx.bh_rd=(lpmodem->hdlc_rx.bh_rd+1)%BH_RX_BUFLEN;
lpmodem->hdlc_rx.bh_buflen--;
spin_unlock_irqrestore(&lpmodem_lock,flags);
hdlc_rx_byte(lpmodem,data);
}
}
static void bh_transmitter(void *priv)
{
struct lpmodem_state *lpmodem=(struct lpmodem_state *)priv;
unsigned char data;
unsigned long flags;
spinlock_t lpmodem_lock=SPIN_LOCK_UNLOCKED;
DEBUGPLCF("%s\n",__FUNCTION__);
if(!lpmodem || lpmodem->magic != LPMODEM_MAGIC) return;
while(lpmodem->hdlc_tx.bh_buflen < BH_TX_BUFLEN)
{
data=hdlc_tx_byte(lpmodem);
if(!lpmodem->hdlc_tx.holdptt) break;
84
spin_lock_irqsave(&lpmodem_lock,flags);
lpmodem->hdlc_tx.bh_buffer[lpmodem->hdlc_tx.bh_wr]=data;
lpmodem->hdlc_tx.bh_wr=(lpmodem->hdlc_tx.bh_wr+1)%BH_TX_BUFLEN;
lpmodem->hdlc_tx.bh_buflen++;
spin_unlock_irqrestore(&lpmodem_lock,flags);
}
}
#endif /* LPMODEM_USE_BH */
/*******************************************************************************
Hardware initialization & cleanup functions
*******************************************************************************/
static void lpmodem_set_baud(struct lpmodem_state *lpmodem)
{
unsigned int baud=0;
unsigned int cflag;
unsigned int i;
st7538reg bbits=0;
st7538reg reg;
DEBUGPLCF("%s\n",__FUNCTION__);
if(lpmodem->parport==0xffffffff) return;
if(lpmodem->opened)
{
cflag=lpmodem->tty->termios->c_cflag;
/* can change only baud rate */
lpmodem->tty->termios->c_cflag=CS8 | CREAD | CLOCAL | (cflag & CBAUD);
i=cflag & CBAUD;
/* do not suport baud rates above 4800 */
if(i & CBAUDEX) lpmodem->tty->termios->c_cflag &=~CBAUDEX;
switch(i)
{
case B0:
baud=0;
break;
case B50:
case B75:
case B110:
case B134:
case B150:
case B200:
case B300:
case B600:
baud=600;
bbits=ST7538_BAUD_600;
break;
case B1200:
baud=1200;
bbits=ST7538_BAUD_1200;
break;
case B1800:
case B2400:
baud=2400;
bbits=ST7538_BAUD_2400;
break;
case B4800:
default:
baud=4800;
bbits=ST7538_BAUD_4800;
break;
}
readRegister(lpmodem,&reg);
reg &= ~ST7538_BAUD;
reg |= bbits;
writeRegister(lpmodem,&reg);
}
else baud=lpmodem->dflt_baud;
#ifdef LPMODEM_DEBUG
printk(KERN_INFO "lpmodem: baud rate: %d\n",baud);
#endif
}
static void lpmodem_deallocate_resources(struct lpmodem_state *lpmodem)
{
DEBUGPLCF("%s\n",__FUNCTION__);
if(lpmodem == NULL) return;
/* remove arbitration timer */
85
if(lpmodem->modem.flags & ARB_TIMER_ON) del_timer(&lpmodem->arb_timer);
lpmodem->modem.flags&=~ARB_TIMER_ON;
}
static int lpmodem_on_open(struct lpmodem_state *lpmodem)
{
int error;
DEBUGPLCF("%s\n",__FUNCTION__);
if(lpmodem==NULL) return -ENXIO;
DEBUGPLC1("Hardware Start");
#ifndef NOPART
/* For now the parport is my */
if((error=parport_claim_or_block(lpmodem->dev))) return error;
#endif
/* Start Rx mode */
writeMCR(lpmodem,MCR_WD_ | MCR_RXTX);
/* For now default */
lpmodem_set_baud(lpmodem);
/* start arbitration */
if(!(lpmodem->modem.flags & ARB_TIMER_ON))
{
unsigned long ms=lpmodem->ch_params.slottime*10;
lpmodem->modem.flags|=ARB_TIMER_ON;
lpmodem->arb_timer.expires=jiffies+(ms*HZ+999)/1000+1;
add_timer(&lpmodem->arb_timer);
}
return 0;
}
static void lpmodem_on_close(struct lpmodem_state *lpmodem)
{
DEBUGPLCF("%s\n",__FUNCTION__);
if(lpmodem == NULL) return;
/* remove arbitration timer */
if(lpmodem->modem.flags & ARB_TIMER_ON) del_timer(&lpmodem->arb_timer);
lpmodem->modem.flags&=~ARB_TIMER_ON;
writeMCR(lpmodem,MCR_WD_ | MCR_RXTX);
#ifndef NOPART
parport_release(lpmodem->dev);
#endif
}
static int lpmodem_set_hardware(struct lpmodem_state *lpmodem,struct parport *port,int baud)
{
int i;
DEBUGPLCF("%s\n",__FUNCTION__);
if(lpmodem == NULL) return -EINVAL;
lpmodem_deallocate_resources(lpmodem);
lpmodem->parport=port->number;
lpmodem->dflt_baud=baud;
i=0;
if(lpmodem->opened > 0) i=lpmodem_on_open(lpmodem);
return i;
}
/*******************************************************************************
TTY Driver functions
*******************************************************************************/
static inline int lpmodem_paranoia_check(struct lpmodem_state *lpmodem,const char *routine)
{
DEBUGPLCF("%s\n",__FUNCTION__);
if(!lpmodem || lpmodem->magic != LPMODEM_MAGIC)
{
printk(KERN_ERR "lpmodem: bad magic number for lpmodem struct in routine %s\n",routine);
return 1;
}
return 0;
}
/*
*
This routine is called when a particular tty device is opened.
*
This routine is mandatory; if this routine is not filled in,
*
the attempted open will fail with ENODEV.
*/
int lpmodem_open(struct tty_struct *tty,struct file *filp)
86
{
int line;
struct lpmodem_state *lpmodem;
int i;
st7538reg reg;
unsigned char data;
DEBUGPLCF("%s\n",__FUNCTION__);
DEBUGPLC1("Driver lpmodem is open\n");
if(!tty) return -ENODEV;
line=tty->index - tty->driver->minor_start;
if(line < 0 || line >= NR_PORTS) return -ENODEV;
lpmodem=lpmodem_state+line;
if(lpmodem->opened > 0)
{
lpmodem->opened++;
return 0;
}
/* allocate the buffer space */
if(lpmodem->rx_buf.buffer) kfree(lpmodem->rx_buf.buffer);
if(lpmodem->tx_buf.buffer) kfree(lpmodem->tx_buf.buffer);
lpmodem->rx_buf.buflen=BUFLEN_RX;
lpmodem->tx_buf.buflen=BUFLEN_TX;
lpmodem->rx_buf.rd=lpmodem->rx_buf.wr=0;
lpmodem->tx_buf.rd=lpmodem->tx_buf.wr=0;
lpmodem->rx_buf.buffer=(unsigned char *)kmalloc(lpmodem->rx_buf.buflen,GFP_KERNEL);
lpmodem->tx_buf.buffer=(unsigned char *)kmalloc(lpmodem->tx_buf.buflen,GFP_KERNEL);
if(!lpmodem->rx_buf.buffer || !lpmodem->tx_buf.buffer)
{
if(lpmodem->rx_buf.buffer) kfree(lpmodem->rx_buf.buffer);
if(lpmodem->tx_buf.buffer) kfree(lpmodem->tx_buf.buffer);
lpmodem->rx_buf.buffer=lpmodem->tx_buf.buffer=NULL;
lpmodem->rx_buf.buflen=lpmodem->tx_buf.buflen=0;
return -ENOMEM;
}
i=0;
/* enable to open a unitialized port */
i=lpmodem_on_open(lpmodem);
/* initialize port */
if((i=readRegister(lpmodem, &reg))) return i;
reg &= ~( ST7538_DET | ST7538_WATCHDOG | ST7538_ASYNC | ST7538_BAUD_4800);
reg |= ST7538_DET_CDC | ST7538_BAUD_4800;
if((i=writeRegister(lpmodem, &reg))) return i;
if(i) return i;
lpmodem->opened++;
tty->driver_data=lpmodem;
lpmodem->tty=tty;
if( tty == NULL){
return -1;
}
//
enable_irq(PLC_IRQ);
DEBUGPLC2("ENABLE PLC_IRQ\n");
data = parport_read_control(lpmodem->dev->port);
data |= PLC_IRQ_ENABLE;
parport_write_control(lpmodem->dev->port,data);
return 0;
}
/*
*
This routine is called when a particular tty device is closed.
*/
static void lpmodem_close(struct tty_struct *tty,struct file * filp)
{
struct lpmodem_state *lpmodem;
plcMCR data;
DEBUGPLCF("%s\n",__FUNCTION__);
if(!tty) return;
if(lpmodem_paranoia_check(lpmodem=(struct lpmodem_state *)tty->driver_data,"close")) return;
lpmodem->opened--;
if(lpmodem->opened <= 0)
{
lpmodem_on_close(lpmodem);
/* clear port */
87
tty->driver_data=NULL;
lpmodem->tty=NULL;
lpmodem->opened=0;
/* release buffers */
lpmodem->rx_buf.rd=lpmodem->rx_buf.wr=0;
lpmodem->tx_buf.rd=lpmodem->tx_buf.wr=0;
if(lpmodem->rx_buf.buffer) kfree(lpmodem->rx_buf.buffer);
if(lpmodem->tx_buf.buffer) kfree(lpmodem->tx_buf.buffer);
lpmodem->rx_buf.buffer=lpmodem->tx_buf.buffer=NULL;
lpmodem->rx_buf.buflen=lpmodem->tx_buf.buflen=0;
//
disable_irq(PLC_IRQ);
DEBUGPLC2("DISABLE PLC_IRQ\n");
data = parport_read_control(lpmodem->dev->port);
data &= ~PLC_IRQ_ENABLE;
parport_write_control(lpmodem->dev->port,data);
}
}
/*
*
This routine is called by the kernel to write a single
*
character to the tty device. If the kernel uses this routine,
*
it must call the flush_chars() routine (if defined) when it is
*
done stuffing characters into the driver. If there is no room
*
in the queue, the character is ignored.
*/
static void lpmodem_put_char(struct tty_struct *tty,unsigned char ch)
{
struct lpmodem_state *lpmodem;
DEBUGPLCF("%s\n",__FUNCTION__);
if(!tty) return;
if(lpmodem_paranoia_check(lpmodem=(struct lpmodem_state *)tty->driver_data,"put_char")) return;
DEBUGPLC2("put char\n");
if(ch==KISS_FEND)
{
lpmodem_put_fend(lpmodem);
lpmodem->kiss_decode.wr=0;
lpmodem->kiss_decode.escaped=0;
lpmodem->kiss_decode.dec_state=1;
return;
}
if(!lpmodem->kiss_decode.dec_state) return;
if(ch==KISS_FESC)
{
lpmodem->kiss_decode.escaped=1;
return;
}
if(lpmodem->kiss_decode.wr >= sizeof(lpmodem->kiss_decode.pkt_buf))
{
lpmodem->kiss_decode.wr=0;
lpmodem->kiss_decode.dec_state=0;
return;
}
if(lpmodem->kiss_decode.escaped)
{
if(ch==KISS_TFEND) lpmodem->kiss_decode.pkt_buf[lpmodem->kiss_decode.wr++]=KISS_FEND;
else if(ch==KISS_TFESC) lpmodem->kiss_decode.pkt_buf[lpmodem->kiss_decode.wr++]=KISS_FESC;
else
{
lpmodem->kiss_decode.wr=0;
lpmodem->kiss_decode.dec_state=0;
}
lpmodem->kiss_decode.escaped=0;
return;
}
lpmodem->kiss_decode.pkt_buf[lpmodem->kiss_decode.wr++]=ch;
}
/*
*
This routine is called by the kernel to write a series of
*
characters to the tty device. This routine will return the
*
number of characters actually accepted for writing. This
*
routine is mandatory.
*/
static int lpmodem_write(struct tty_struct *tty,const unsigned char *buf,int count)
{
struct lpmodem_state *lpmodem;
const unsigned char *bp;
int c;
DEBUGPLCF("%s\n",__FUNCTION__);
if(!tty || !buf || count <= 0) return count;
if(lpmodem_paranoia_check(lpmodem=(struct lpmodem_state *)tty->driver_data,"write")) return count;
for(c=count,bp=buf;c > 0;c--,bp++) lpmodem_put_char(tty,*bp);
if((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
wake_up_interruptible(&tty->write_wait);
tty->ldisc.write_wakeup) (tty->ldisc.write_wakeup)(tty);
88
return count;
}
/*
*
This routine returns the numbers of characters the tty driver
*
will accept for queuing to be written. This number is subject
*
to change as output buffers get emptied, or if the output flow
*
control is acted.
*/
static int lpmodem_write_room(struct tty_struct *tty)
{
struct lpmodem_state *lpmodem;
int free;
DEBUGPLCF("%s\n",__FUNCTION__);
if(!tty) return 0;
if(lpmodem_paranoia_check(lpmodem=(struct lpmodem_state *)tty->driver_data,"write_room")) return 0;
free=lpmodem->tx_buf.rd-lpmodem->tx_buf.wr;
if(free <= 0)
{
free=lpmodem->tx_buf.buflen-lpmodem->tx_buf.wr;
if((unsigned int)free < lpmodem->tx_buf.rd) free=lpmodem->tx_buf.rd;
}
/* we may fold */
return free / 2; /* a rather pessimistic estimate */
}
/*
*
This routine returns the numbers of characters the tty driver
*
has in queue to be written to tty device. This number is subject
*
to change as output buffers get emptied.
*/
static int lpmodem_chars_in_buffer(struct tty_struct *tty)
{
struct lpmodem_state *lpmodem;
int cnt;
DEBUGPLCF("%s\n",__FUNCTION__);
if(!tty) return 0;
if(lpmodem_paranoia_check(lpmodem=(struct lpmodem_state *)tty->driver_data,"chars_in_buffer")) return 0;
cnt=lpmodem->tx_buf.wr-lpmodem->tx_buf.rd;
if(cnt < 0) cnt+=lpmodem->tx_buf.buflen;
return cnt;
}
/*
*
This routine is called by the kernel after it has written a
*
series of characters to the tty device using write().
*/
static void lpmodem_flush_buffer(struct tty_struct *tty)
{
struct lpmodem_state *lpmodem;
DEBUGPLCF("%s\n",__FUNCTION__);
if(!tty) return;
if(lpmodem_paranoia_check(lpmodem=(struct lpmodem_state *)tty->driver_data,"flush_buffer")) return;
wake_up_interruptible(&tty->write_wait);
if((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
tty->ldisc.write_wakeup) (tty->ldisc.write_wakeup)(tty);
}
/*
*
This routine allows the tty driver to implement
*
device-specific ioctl’s. If the ioctl number passed in cmd
*
is not recognized by the driver, it should return ENOIOCTLCMD.
*/
static int lpmodem_ioctl(struct tty_struct *tty,struct file * file,unsigned int cmd,unsigned long arg)
{
struct lpmodem_state *lpmodem;
struct lpmodem_params par;
int i;
int baud;
int err;
DEBUGPLCF("%s\n",__FUNCTION__);
if (!tty) return -EINVAL;
//
if(lpmodem_paranoia_check(lpmodem=(struct lpmodem_state *)tty->driver_data,"ioctl")) return -EINVAL;
printk("%x", cmd);
switch(cmd)
{
default:
{
return -ENOIOCTLCMD;
}
case TIOCMGET:
{
i=access_ok(VERIFY_WRITE,(void *)arg,sizeof(int));
if(!i) return -EFAULT;
i=(lpmodem->modem.dcd ? TIOCM_CAR : 0) | (lpmodem->hdlc_tx.ptt ? TIOCM_RTS : 0);
put_user(i,(int *)arg);
return 0;
}
89
case LPMODEM_GET_DCD:
{
int i=access_ok(VERIFY_WRITE,(void *)arg,sizeof(unsigned char));
if(!i) return -EFAULT;
put_user(lpmodem->modem.dcd,(unsigned char *) arg);
return 0;
}
case LPMODEM_GET_PTT:
{
i=access_ok(VERIFY_WRITE,(void *)arg,sizeof(unsigned char));
if(!i) return -EFAULT;
put_user(lpmodem->hdlc_tx.ptt,(unsigned char *)arg);
return 0;
}
case LPMODEM_SET_TXDELAY:
{
if(arg > 255) return -EINVAL;
lpmodem->ch_params.tx_delay=arg;
return 0;
}
case LPMODEM_SET_PPERSIST:
{
if(arg > 255) return -EINVAL;
lpmodem->ch_params.ppersist=arg;
return 0;
}
case LPMODEM_SET_SLOTTIME:
{
if (arg > 255) return -EINVAL;
lpmodem->ch_params.slottime=arg;
return 0;
}
case LPMODEM_SET_TXTAIL:
{
if(arg > 255) return -EINVAL;
lpmodem->ch_params.tx_tail=arg;
return 0;
}
case LPMODEM_SET_FULLDUP:
{
lpmodem->ch_params.fulldup=arg ? 1 : 0;
return 0;
}
case LPMODEM_GET_PARAMS:
{
i = access_ok(VERIFY_WRITE,(void *) arg,sizeof(par));
if(!i) return -EFAULT;
par.parport=lpmodem->parport;
par.baud=lpmodem->dflt_baud;
par.tx_delay=lpmodem->ch_params.tx_delay;
par.tx_tail=lpmodem->ch_params.tx_tail;
par.slottime=lpmodem->ch_params.slottime;
par.ppersist=lpmodem->ch_params.ppersist;
par.fulldup=lpmodem->ch_params.fulldup;
if((err=copy_to_user((void *)arg,&par,sizeof(par)))) return err;
return 0;
}
case LPMODEM_SET_PARAMS:
{
if(!capable(CAP_SYS_ADMIN)) return -EPERM;
i=access_ok(VERIFY_READ,(void *) arg,sizeof(par));
if(!i) return -EFAULT;
if((err=copy_from_user(&par,(void *)arg,sizeof(par)))) return err;
i=lpmodem_set_hardware(lpmodem,lpmodem->dev->port,par.baud);
if(i) return i;
/* set termios baud flag to default baud */
baud=0;
while((baud_table[baud] > 0 || baud == 0) && (baud_table[baud] != lpmodem->dflt_baud)) baud++;
if(baud > 15) baud |= CBAUDEX;
tty->termios->c_cflag&=~CBAUD;
tty->termios->c_cflag|=baud;
lpmodem->ch_params.tx_delay=par.tx_delay;
lpmodem->ch_params.tx_tail=par.tx_tail;
lpmodem->ch_params.slottime=par.slottime;
lpmodem->ch_params.ppersist=par.ppersist;
lpmodem->ch_params.fulldup=par.fulldup;
return 0;
}
case LPMODEM_GET_STAT:
{
i=access_ok(VERIFY_WRITE,(void *) arg,sizeof(struct lpmodem_statistics));
if(!i) return -EFAULT;
if((err=copy_to_user((void *)arg,&lpmodem->stat,sizeof(struct lpmodem_statistics)))) return err;
return 0;
90
}
#ifdef LPMODEM_DEBUG
case LPMODEM_GET_BITS:
{
if(lpmodem->bitbuf_hdlc.rd == lpmodem->bitbuf_hdlc.wr) return -EAGAIN;
i=access_ok(VERIFY_WRITE,(void *) arg,sizeof(unsigned char));
if(!i) return -EFAULT;
put_user(lpmodem->bitbuf_hdlc.buffer[lpmodem->bitbuf_hdlc.rd],(unsigned char *) arg);
lpmodem->bitbuf_hdlc.rd=(lpmodem->bitbuf_hdlc.rd+1) % sizeof(lpmodem->bitbuf_hdlc.buffer);
return 0;
}
case LPMODEM_DEBUG1:
{
i=access_ok(VERIFY_WRITE,(void *) arg,sizeof(unsigned long));
if(!i) return -EFAULT;
put_user((lpmodem->rx_buf.wr-lpmodem->rx_buf.rd) % lpmodem->rx_buf.buflen,(unsigned long *)arg);
return 0;
}
case LPMODEM_DEBUG2:
{
i=access_ok(VERIFY_WRITE,(void *) arg,sizeof(unsigned long));
if(!i) return -EFAULT;
put_user(lpmodem->debug_vals.last_intcnt,(unsigned long *)arg);
return 0;
}
case LPMODEM_GET_MSR:
{
plcMSR msr;
i=access_ok(VERIFY_WRITE,(void *) arg,sizeof(plcMSR));
if(!i) return -EFAULT;
if((i=readMSR(lpmodem,&msr))) return i;
put_user(msr,(plcMSR *)arg);
return 0;
}
case LPMODEM_GET_REG:
{
st7538reg reg, reg1;
i=access_ok(VERIFY_WRITE,(void *) arg,sizeof(st7538reg));
if(!i) return -EFAULT;
do {
schedule();
schedule();
if((i=readRegister(lpmodem,&reg))) return i;
/* This modem is lazy, if 0x0 give it a time */
msleep(100);
schedule();
schedule();
if((i=readRegister(lpmodem,&reg1))) return i;
msleep(100);
/* Ok, is really this? */
}while(reg != reg1);
put_user(reg,(st7538reg *)arg);
return 0;
}
case LPMODEM_SET_REG:
{
st7538reg reg=arg;
if((i=writeRegister(lpmodem, &reg))) return i;
return 0;
}
#endif /* LPMODEM_DEBUG */
}
return 0;
}
/*
*
This routine allows the tty driver to be notified when
*
device’s termios settings have changed. Note that a
*
well-designed tty driver should be prepared to accept the case
*
where old == NULL, and try to do something rational.
*/
static void lpmodem_set_termios(struct tty_struct *tty,struct termios *old)
{
struct lpmodem_state *lpmodem;
DEBUGPLCF("%s\n",__FUNCTION__);
if(!tty) return;
if(lpmodem_paranoia_check(lpmodem=(struct lpmodem_state *)tty->driver_data,"set_termios")) return;
91
if(tty->termios->c_cflag == old->c_cflag) return;
lpmodem_set_baud(lpmodem);
}
/*******************************************************************************
Driver initialization & cleanup functions
*******************************************************************************/
static void init_channel(struct lpmodem_state *lpmodem,int port)
{
DEBUGPLCF("%s\n",__FUNCTION__);
if(!lpmodem) return;
/* HDLC initialization */
rx_bit_buff=0;
tx_bit_buff=0;
rxcont=0;
txcont=0;
plc_fixme2 = 1;
lpmodem->hdlc_rx.rx_state=0;
lpmodem->hdlc_tx.tx_state=0;
lpmodem->hdlc_tx.numflags=0;
lpmodem->hdlc_tx.bitstream=0;
lpmodem->hdlc_tx.ptt=0;
lpmodem->hdlc_tx.holdptt=0;
lpmodem->hdlc_rx.bh_rd=0;
lpmodem->hdlc_rx.bh_wr=0;
lpmodem->hdlc_rx.bh_buflen=0;
lpmodem->hdlc_tx.bh_rd=0;
lpmodem->hdlc_tx.bh_wr=0;
lpmodem->hdlc_tx.bh_buflen=0;
#ifdef LPMODEM_DEBUG
lpmodem->bitbuf_hdlc.rd=0;
lpmodem->bitbuf_hdlc.wr=0;
#endif /* LPMODEM_DEBUG */
/* KISS initialization */
lpmodem->kiss_decode.dec_state=0;
lpmodem->kiss_decode.escaped=0;
lpmodem->kiss_decode.wr=0;
lpmodem->ch_params=lpmodem_ports[port].dflt_ch_params;
/* Arbitration initialization */
lpmodem->modem.dcd=0;
init_timer(&lpmodem->arb_timer);
lpmodem->arb_timer.function=arb_sched;
lpmodem->arb_timer.data=(unsigned long) lpmodem;
lpmodem->modem.flags=0;
/* Bottom-half initialization */
#ifdef LPMODEM_USE_BH
INIT_WORK(&(lpmodem->tq_receiver),bh_receiver,lpmodem);
INIT_WORK(&(lpmodem->tq_transmitter),bh_transmitter,lpmodem);
#endif /* LPMODEM_USE_BH */
}
static void init_datastructs(void)
{
int i;
DEBUGPLCF("%s\n",__FUNCTION__);
for(i=0;i < NR_PORTS;i++)
{
struct lpmodem_state *lpmodem=lpmodem_state+i;
lpmodem->magic=LPMODEM_MAGIC;
lpmodem->parport=0xffffffff;
lpmodem->opened=0;
lpmodem->tty=NULL;
lpmodem->rx_buf.rd=0;
lpmodem->rx_buf.wr=0;
lpmodem->rx_buf.buflen=0;
lpmodem->rx_buf.buffer=NULL;
lpmodem->tx_buf.rd=0;
lpmodem->tx_buf.wr=0;
lpmodem->tx_buf.buflen=0;
lpmodem->tx_buf.buffer=NULL;
memset(&lpmodem->stat,0,sizeof(lpmodem->stat));
92
init_channel(lpmodem,i);
}
}
#ifndef NOPART
static void lpmodem_attach(struct parport *port)
{
unsigned int i;
for(i=0;i < NR_PORTS;i++)
{
struct lpmodem_state *lpmodem=lpmodem_state+i;
if (port->number == lpmodem_ports[i].parport)
{
lpmodem->dev = parport_register_device(port,"lpmodem",NULL,NULL,NULL,0,(void *) lpmodem);
printk(/*KERN_INFO */"ttyPLC%d: using %s.\n",port->number,port->name);
lpmodem_set_hardware(lpmodem,port,lpmodem_ports[i].baud);
}
}
}
static void lpmodem_detach(struct parport *port)
{
}
static struct parport_driver pp_driver=
{
.name = "lpmodem",
.attach = lpmodem_attach,
.detach = lpmodem_detach,
};
#endif
static int lpmodem_init(void)
{
int baud;
int i;
struct lpmodem_state *lpmodem=lpmodem_state;
DEBUGPLCF("%s\n",__FUNCTION__);
/* initialize the data structures */
init_datastructs();
/* register the driver as tty driver */
memset(&lpmodem_driver,0, sizeof(struct tty_driver));
lpmodem_driver.magic=TTY_DRIVER_MAGIC;
lpmodem_driver.name="lpmodem";
lpmodem_driver.major=major;
lpmodem_driver.minor_start=0;
lpmodem_driver.num=NR_PORTS;
lpmodem_driver.type=TTY_DRIVER_TYPE_LPMODEM;
lpmodem_driver.subtype=LPMODEM_TYPE_NORMAL;
lpmodem_driver.init_termios.c_iflag=0;
lpmodem_driver.init_termios.c_oflag=0;
baud=0;
while((baud_table[baud] > 0 || baud == 0) && (baud_table[baud] != DFLT_BAUD)) baud++;
if(baud > 15) baud |= CBAUDEX;
lpmodem_driver.init_termios.c_cflag=CS8 | CREAD | CLOCAL | baud;
lpmodem_driver.init_termios.c_lflag=0;
lpmodem_driver.flags=TTY_DRIVER_REAL_RAW;
lpmodem_driver.refcount=lpmodem_refcount;
lpmodem_driver.ttys=lpmodem_ttys;
lpmodem_driver.termios=lpmodem_termios;
lpmodem_driver.termios_locked=lpmodem_termios_locked;
/* the functions */
lpmodem_driver.open=lpmodem_open;
lpmodem_driver.close=lpmodem_close;
lpmodem_driver.write= lpmodem_write;
lpmodem_driver.put_char=lpmodem_put_char;
lpmodem_driver.flush_chars=NULL;
lpmodem_driver.write_room=lpmodem_write_room;
lpmodem_driver.chars_in_buffer=lpmodem_chars_in_buffer;
lpmodem_driver.flush_buffer=lpmodem_flush_buffer;
lpmodem_driver.ioctl=lpmodem_ioctl;
/* cannot throttle the transmitter on this layer */
lpmodem_driver.throttle=NULL;
lpmodem_driver.unthrottle=NULL;
/* no special actions on termio changes */
lpmodem_driver.set_termios=lpmodem_set_termios;
/* no XON/XOFF and no hangup on the radio port */
lpmodem_driver.stop=NULL;
lpmodem_driver.start=NULL;
lpmodem_driver.hangup=NULL;
lpmodem_driver.set_ldisc=NULL;
if(tty_register_driver(&lpmodem_driver))
{
printk(KERN_ERR "lpmodem: tty_register_driver() failed.\n");
return -EIO;
93
}
#ifndef NOPART
if(parport_register_driver(&pp_driver))
{
printk (KERN_ERR "lpmodem: unable to register with parport.\n");
if(tty_unregister_driver(&lpmodem_driver)) printk(KERN_ERR "lpmodem: failed to unregister tty driver.\n");
return -EIO;
}
#endif
if ((i = request_irq(PLC_IRQ, lpmodem_interrupt, 0, "lpmodem", lpmodem))) return i;
return 0;
}
int init_module(void)
{
int i;
printk("lpmodem: v2.0.0 Copyright (c) 2005 \tWalter Fetter Lages <[email protected]>.\n"\
"\t\t\t\t\tDiego Caberlon Santini <[email protected]>\n");
printk(KERN_INFO "lpmodem: init_module() called.\n");
for(i=0; i < NR_PORTS; i++)
{
lpmodem_ports[i].parport=parport[i];
lpmodem_ports[i].baud=defbaud[i];
lpmodem_ports[i].dflt_ch_params.tx_delay=tx_delay[i];
lpmodem_ports[i].dflt_ch_params.tx_tail=tx_tail[i];
lpmodem_ports[i].dflt_ch_params.slottime=slottime[i];
lpmodem_ports[i].dflt_ch_params.ppersist=ppersist[i];
lpmodem_ports[i].dflt_ch_params.fulldup=fulldup[i];
}
/*
* ANSI sample implementation borrowed form Metaware High C Reference Manual
* ANSI uses 1 as seed. Here seed is get from jiffies to be more, well, random.
*/
rand_seed=(unsigned short) jiffies;
/* random seed */
return lpmodem_init();
}
void cleanup_module(void)
{
int i;
struct lpmodem_state *lpmodem;
printk(KERN_INFO "lpmodem: cleanup_module() called.\n");
lpmodem=lpmodem_state;
free_irq(PLC_IRQ, lpmodem);
#ifndef NOPART
if(tty_unregister_driver(&lpmodem_driver)) printk(KERN_WARNING "lpmodem: failed to unregister tty driver.\n");
#endif
for(i=0;i < NR_PORTS;i++)
{
lpmodem=lpmodem_state+i;
if(lpmodem->magic != LPMODEM_MAGIC) printk(KERN_ERR "lpmodem: invalid magic in cleanup_module.\n");
else
{
if(lpmodem->dev != NULL) parport_unregister_device(lpmodem->dev);
parport_unregister_driver(&pp_driver);
lpmodem_deallocate_resources(lpmodem);
/* free the buffers */
lpmodem->rx_buf.rd=0;
lpmodem->rx_buf.wr=0;
lpmodem->tx_buf.rd=0;
lpmodem->tx_buf.wr=0;
if(lpmodem->rx_buf.buffer) kfree(lpmodem->rx_buf.buffer);
if(lpmodem->tx_buf.buffer) kfree(lpmodem->tx_buf.buffer);
lpmodem->rx_buf.buffer=NULL;
lpmodem->tx_buf.buffer=NULL;
lpmodem->rx_buf.buflen=0;
lpmodem->tx_buf.buflen=0;
}
}
}
94
95
APÊNDICE D
HEADER DA PLACA, LPMODEM.H
/*****************************************************************************
lpmodem PLC Modem Driver
2005 Walter Fetter Lages <[email protected]>
Diego Caberlon Santini <[email protected]>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
Please note that the GPL allows you to use the driver, NOT the radio.
In order to use the radio, you need a license from the communications
authority of your country.
History:
2006.06.19
Start development based on BIM 3.0.0
*****************************************************************************/
#ifndef _LPMODEM_H
#define _LPMODEM_H
/** @file lpmodem.h lpmodem driver header.
*/
#include <linux/ioctl.h>
#include <st7538.h>
/** Plcmodem Modem Status Register bits
*/
enum PLC_MSR_BITS
{
PLC_MSR_CLRT
=0x404048L,/**<CLRT */
PLC_MSR_RXD
=0x000010L,/**<RXD */
PLC_MSR_RESET_
=0x000000L,/**<RESET# */
PLC_MSR_MCLK
=0x000080L,/**<MCLK */
PLC_MSR_TOUT
=0x000800L,/**<TOUT */
PLC_MSR_REGOK_
=0x001000L,/**<REG_OK# */
PLC_MSR_PG
=0x008000L,/**<PG */
PLC_MSR_ZCOUT
=0x080000L,/**<ZCOUT */
PLC_MSR_BU
=0x100000L,/**<BU */
PLC_MSR_CD_PD
=0x800000L /**<CD/PD */
};
/** Plcmodem Modem Control Register bits
*/
enum MCR_BITS
{
MCR_REG_DATA
=0x01,/**<REG/DATA */
MCR_TXD
=0x02,/**<TXD */
MCR_RXTX
=0x04,/**<RxTx */
MCR_WD_
=0x08 /**<WD# */
};
typedef
typedef
typedef
typedef
int plcError;
void *plcHandle;
unsigned long plcMSR;
unsigned char plcMCR;
/** lpmodem driver statistics.
*/
struct lpmodem_statistics
{
unsigned long rx_packets;
/**<
/**<
/**<
/**<
Error code */
Handle for a PLC device */
plcmodem MSR */
plcmodem MCR */
/**< Received packets. */
96
unsigned
unsigned
unsigned
unsigned
long
long
long
long
tx_packets;
ptt_keyed;
rx_bufferoverrun;
tx_bufferoverrun;
/**<
/**<
/**<
/**<
Transmitted packets. */
Number of times the ptt was keyed */
Number of RX buffer overruns */
Number of TX buffer overruns */
};
/** lpmodem KISS parameters.
*/
struct lpmodem_params
{
int parport;
/**<
int baud;
/**<
int tx_delay;
/**<
int tx_tail;
/**<
int slottime;
/**<
int ppersist;
/**<
int fulldup;
/**<
};
Parport number to use. */
Default baud rate */
Transmitter keyup delay in 10ms units. */
Transmitter keyoff delay in 10ms units. */
Slottime in 10ms; usually 10 = 100ms. */
p-persistence 0..255. */
Driver does not support full duplex, setting this just makes the driver send even if DCD is on. */
/** Maximum packet length, excluding CRC.
*/
#define LPMODEM_MAXFLEN
400
/** KISS protocol
*/
enum KISS_CHARS
{
KISS_FEND
KISS_FESC
KISS_TFEND
KISS_TFESC
};
special characters.
=0xc0,
=0xdb,
=0xdc,
=0xdd
/** KISS protocol commands.
*/
enum KISS_CMD
{
KISS_CMD_DATA
KISS_CMD_TXDELAY
KISS_CMD_PPERSIST
KISS_CMD_SLOTTIME
KISS_CMD_TXTAIL
KISS_CMD_FULLDUP
KISS_CMD_SETHW
};
/** lpmodem ioctl commands.
*/
enum LPMODEM_CTL
{
LPMODEM_GET_DCD
LPMODEM_GET_PTT
LPMODEM_SET_TXDELAY
LPMODEM_SET_PPERSIST
LPMODEM_SET_SLOTTIME
LPMODEM_SET_TXTAIL
LPMODEM_SET_FULLDUP
LPMODEM_GET_STAT
LPMODEM_GET_PARAMS
LPMODEM_SET_PARAMS
LPMODEM_DEBUG_TEST
#ifdef LPMODEM_DEBUG
LPMODEM_GET_BITS
LPMODEM_DEBUG1
LPMODEM_DEBUG2
#endif /* LPMODEM_DEBUG */
LPMODEM_GET_MSR
LPMODEM_GET_REG
LPMODEM_SET_REG
};
#endif /* _LPMODEM_H */
/**<
/**<
/**<
/**<
=0,
=1,
=2,
=3,
=4,
=5,
=6,
Frame End. */
Frame Escape. */
Transposed Frame End. */
Transpoded Frame Escape. */
/**<
/**<
/**<
/**<
/**<
/**<
/**<
The rest
The next
The next
The next
The next
The next
Specific
of the frame is data to be sent on the HDLC channel. */
byte is the transmitter keyup delay in 10 ms units. */
byte is the persistence parameter, p, scaled to the range 0 - 255. */
byte is the slot interval in 10 ms units. */
byte is the time to hold up the TX after the FCS has been sent, in 10 ms units. */
byte is 0 for half duplex, nonzero for full duplex. */
for each TNC. */
=_IOR(’L’,0,unsigned char), /**< Get the DCD state. */
=_IOR(’L’,1,unsigned char), /**< Get the ptt state. */
=_IOW(’L’,2,unsigned char), /**< Set the transmitter keyup delay in 10 ms units. */
=_IOW(’L’,3,unsigned char), /**< Set the p-persistence scaled to the range 0-255. */
=_IOW(’L’,4,unsigned char), /**< Set the slot interval in 10 ms units. */
=_IOW(’L’,5,unsigned char), /**< Set the time to hold up the TX after the FCS has been sent, in 10 ms units. */
=_IOW(’L’,6,unsigned char), /**< Set to zero for half duplex, nonzero for full duplex. */
=_IOR(’L’,7,struct lpmodem_statistics), /**< Get lpmodem statistics. */
=_IOR(’L’,8,struct lpmodem_params), /**< Set KISS lpmodem parameters. */
=_IOR(’L’,9,struct lpmodem_params), /**< Get KISS lpmodem parameters. */
=_IOR(’L’,31,st7538reg), /**< Set the transmitter keyup delay in 10 ms units. */
=_IOR(’L’,17,unsigned char), /**< Get raw HDLC frame. */
=_IOR(’L’,18,unsigned long), /**< Get the free space in RX buffer */
=_IOR(’L’,19,unsigned long), /**< Get the frenquency of interrupts */
=_IOR(’L’,20,plcMSR), /**< Get MSR register. */
=_IOR(’L’,21,st7538reg), /**< Get ST7538 Register register. */
=_IOR(’L’,22,st7538reg), /**< Set ST7538 Register register. */
97
APÊNDICE E
HEADER DO ST7538, ST7538.H
#ifndef _ST7538_H
#define _ST7538_H
/** @file st7538.h ST7538 definitions header.
*/
/** ST7538 Control Register Bits.
*/
enum ST7538_CTRL_BITS
{
ST7538_FREQ
=0x000007L,
ST7538_BAUD
=0x000018L,
ST7538_DEV
=0x000020L,
ST7538_WATCHDOG =0x000040L,
ST7538_TIMEOUT
=0x000180L,
ST7538_DET_TIME =0x000600L,
ST7538_ZEROSYNC =0x000800L,
ST7538_DET
=0x003000L,
ST7538_ASYNC
=0x004000L,
ST7538_CLK
=0x018000L,
ST7538_PACKBAUD =0x060000L,
ST7538_PACKLEN
=0x180000L,
ST7538_PACK
=0x200000L,
ST7538_HISENSE
=0x400000L,
ST7538_FILTER
=0x800000L
};
/** ST7538 Channel Frequencies.
*/
enum ST7538_FREQ_BITS
{
ST7538_FREQ_60 =0x000000L,
ST7538_FREQ_66 =0x000001L,
ST7538_FREQ_72 =0x000002L,
ST7538_FREQ_76 =0x000003L,
ST7538_FREQ_82 =0x000004L,
ST7538_FREQ_86 =0x000005L,
ST7538_FREQ_110 =0x000006L,
ST7538_FREQ_132 =0x000007L
};
/** ST7538 Baud Rates.
*/
enum ST7538_BAUD_BITS
{
ST7538_BAUD_600 =0x000000L,
ST7538_BAUD_1200=0x000008L,
ST7538_BAUD_2400=0x000010L,
ST7538_BAUD_4800=0x000018L
};
/** ST7538 Time Outs.
*/
enum ST7538_TIMEOUT_BITS
{
ST7538_NO_TIMEOUT
ST7538_TIMEOUT_1
ST7538_TIMEOUT_3
};
/**<channel frequency mask */
/**<baud rate mask */
/**<select deviation of 1 */
/**<enable watchdog */
/**<time out mask */
/**<frequency detection time mask */
/**<enable zero crossing synchronization */
/**<detection method mask */
/**<select asynchronous interfacing mode*/
/**<output clock mask */
/**<packet mode baud rate mask */
/**<packet lenght mask */
/**<enable packet mode */
/**<enable high sensitivity mode */
/**<enable input filter */
/**<select
/**<select
/**<select
/**<select
/**<select
/**<select
/**<select
/**<select
60kHz channel frequency */
60kHz channel frequency */
72kHz channel frequency */
76kHz channel frequency */
82.05kHz channel frequency */
86kHz channel frequency */
110kHz channel frequency */
132.5kHz channel frequency */
/**<select
/**<select
/**<select
/**<select
600bps rate
1200bps rate
2400bps rate
4800bps rate
*/
*/
*/
*/
=0x000000L, /**<no time out */
=0x000080L, /**<1s time out */
=0x000100L /**<3s time out */
/** ST7538 Frequency Detection Times.
*/
enum ST7538_DET_TIME_BITS
{
ST7538_DET_TIME_500
=0x000000L,
ST7538_DET_TIME_1
=0x000200L,
ST7538_DET_TIME_3
=0x000400L,
ST7538_DET_TIME_5
=0x000600L
};
/**<select
/**<select
/**<select
/**<select
500us frequency detection time */
1ms frequency detection time */
3ms frequency detection time */
5ms frequency detection time */
/** ST7538 Detection Methods.
*/
enum ST7538_DET_BITS
{
ST7538_DET_CD
=0x000000L, /**<select carrier detection without conditioning */
98
ST7538_DET_CDC
ST7538_DET_PD
ST7538_DET_PDC
=0x001000L, /**<select carrier detection with conditioning */
=0x002000L, /**<select preamble detection without conditioning
=0x003000L /**<select preamble detection with conditioning */
};
/** ST7538 Output Clocks.
*/
enum ST7538_CLK_BITS
{
ST7538_CLK_16
=0x000000L, /**<select 16MHz output clock */
ST7538_CLK_8
=0x008000L, /**<select 8MHz output clock */
ST7538_CLK_4
=0x010000L /**<select 4MHz output clock */
};
/** ST7538 Packet Mode Baud Rates.
*/
enum ST7538_PACKBAUD_BITS
{
ST7538_PACKBAUD_32
=0x000000L,
ST7538_PACKBAUD_64
=0x020000L,
ST7538_PACKBAUD_128
=0x040000L,
ST7538_PACKBAUD_256
=0x060000L
};
/** ST7538 Packet Lenghts.
*/
enum ST7538_PACKLEN_BITS
{
ST7538_PACKLEN_8
ST7538_PACKLEN_9
ST7538_PACKLEN_14
ST7538_PACKLEN_16
};
=0x000000L,
=0x080000L,
=0x100000L,
=0x180000L
/**<select
/**<select
/**<select
/**<select
/**<select
/**<select
/**<select
/**<select
Mclk/32 packet mode baud rate */
Mclk/64 packet mode baud rate */
Mclk/128 packet mode baud rate */
Mclk/256 packet mode baud rate */
8 bits packet lenght */
9 bits packet lenght */
14 bits packet lenght */
16 bits packet lenght */
#ifdef __MSDOS__
/* In MS-DOS compilers enum is usually 16 bits */
#define
ST7538_PACKLEN_9
0x080000L
#define
ST7538_PACKLEN_14
0x100000L
#define
ST7538_PACKLEN_16
0x180000L
#endif
typedef unsigned long st7538reg;
#endif
/**< ST7538 register */
*/

Documentos relacionados