The original Actiontec Dual PC Modem firmware is shipped with a binary
Linux kernel module called cnxtserial_buildin. This module drives the
internal V.92 modem which can be talked to via /dev/ttyS2. At the very
beginning of the actionhack project it was clear that it's not
some kind of annoying "winmodem" driver where the signal processing is
done by the main CPU. Instead the module only provides the interface to
the modem chip. It was unclear however what the module is doing
exactly. Maybe it sets up the modem by poking around in its registers?
Maybe it loads the modem firmware?
If I would have been able to load that binary module with later kernels
(the original kernel is as old as 2.4.6) I would never have wasted my
time by looking at it. However, the module didn't load with 2.4.29uc0
because of unresolved symbols, so I took a closer look at it - maybe I
could hack it up so that it loads. But while looking at it I noticed
that many function names correspond to the ones in cnxtserial.c (which is
part of the uClinux kernel). After looking even closer it got clear to
me that most of the function names match. My first conclusion was
that the interface to the internal modem is the same as the interface
defined in cnxtserial.c and that the cnxtserial_buildin module only
initializes the modem in some way, maybe by telling it to "talk serial",
as it had been discussed on the mailing list before.
So I decided to copy cnxtserial.c to cnxtserial_buildin.c, compile it
and compare its disassembly to the disassembly of the original
cnxtserial_buildin.o file.
There were quite some differences in the assembler code. The first
things I noticed were two functions named "get_modem_info" and
"set_modem_info" which were in the original module but not in my rebuilt
module. "set_modem_info" sounded like some modem initialization stuff,
so I focused on it. My idea was to find out where the
set_modem_info function gets called, then copy the (compiled) function
from the original module and then call it in the rebuilt module, too.
With some luck, this initialization is the only thing we need to get
the modem work. Unfortunately there was a big problem: the branch
instructions in a Linux kernel module don't "point" to the correct
position. This is because at compile time it's unknown to what memory
address a module gets loaded. So the branch target addresses have to be
filled in ("relocated") at module load time. This work is usually done
by the userspace insmod tool (busybox in embedded devices). I asked on
the uClinux mailing list if there is an easy way to get the relocations
done within the file without loading it, but I didn't get a positive
response. So I had to do some more work to actually know where the
set_modem_function is called. I
hacked up the busybox insmod applet so that it prints out information about
every relocation that it does. I then wrote
a C program for my x86
workstation that takes the busybox/insmod output and does the
relocations in the cnxtserial_buildin.o file. After that the disassembly
of the kernel module indeed showed the branch instructions pointing to
the correct branch targets (I did know where the branch instructions
must point to because they had to be similar to the function calls in
the cnxtserial.c source). And oh well, I got disappointed again: The
only branches to set_modem_info (and get_modem_info) were within
rs_ioctl, and rs_ioctl itself was not called anywhere in the module.
Additionally, there couldn't be any userspace program that makes a ioctl
call which branches to set_modem_info, simply because the modem works
by loading the module without calling any of Actiontec's userland
programs. So finally it turned out that I had to go the hardest way of
all: analyze every single disassembled function which differs between
cnxtserial and cnxtserial_buildin and then try to understand the
assembler instructions so that I can do the same thing in the C source.
I had a hard time doing this because I never worked with assembler
(especially not ARM assembler) before. But after looking at it again
and again I at last understood the most basic instructions (ldr, str,
cmp, tst, b*, etc.) and was able to rebuild the C source code. Finally
it turns out that the main difference between cnxtserial and
cnxtserial_buildin is some kind of handshake between CPU and modem chip.
The most obvious example for this is the wait_EOT function.
cnxtserial.c wait_EOT():
static void wait_EOT(struct uart_regs *uart)
{
volatile struct uart_regs* puart;
unsigned long timeout = 10000;
puart = (volatile struct uart_regs*)uart;
while(1){
if(timeout-- == 0) {
printk("eot timeout\n");
break;
}
if(puart->line_status_reg & LSR_Tx_Fifo_Empty)
break;
}
}
cnxtserial_buildin.c wait_EOT():
static void wait_EOT(struct uart_regs *uart)
{
volatile struct uart_regs* puart;
unsigned long timeout = 10000;
unsigned short modem_ctrl;
puart = (volatile struct uart_regs*)uart;
while(1){
if(timeout-- == 0) {
printk("eot timeout\n");
line_status(uart);
break;
}
if(puart->line_status_reg & LSR_Tx_Fifo_Empty) {
break;
} else {
modem_ctrl=puart->modem_ctrl;
if(!(puart->msr & 0x80)) {
return;
}
}
}
}
If you want more details about the differences, just make a context diff
between cnxtserial.c in the uClinux source tree and cnxtserial_buildin.c
in the downloadable archive below.
One last thing to note is that Actiontec and/or Conexant violates the
GPL here again. cnxtserial.c is heavily based on serial.c, e.g. the
serial_paranoia_check function has been copied literally. Moreover,
other functions are identical/similar (e.g. do_serial_bh, do_softint)
and the overall driver structure is the same. Thus, cnxtserial.c is not
exclusively property of Actiontec/Conexant, it also contains GPL code,
so it also must be put under the GPL (if it would contain
Actiontec/Conexant code only, they could put it under GPL the and under a
proprietary license at the same time). Consequently, the
cnxtserial_buildin module contains GPL code and not giving out its
source code violates the GPL.
Enough written, here is the cnxtserial_buildin archive, look at the
included CONTENTS file for a description of the single files:
cnxtserial_buildin.tar.gz
As I wrote already I compared the assembler code of all functions in the
original module and the rebuilt module. For several reasons, they do not
match 100%:
In addition to these very minor differences in the machine code (which
make absolutetly no differnce in functionality), two things are missing
from the original cnxtserial_buildin module:
- The ioctl-functions implemented by set_modem_info and
get_modem_info. But don't worry, these functions seem to be some kind of
debug functions and are probably not used by the original firmware anyway.
- The receive_chars function (which is inlined into the
rs_interrupt_buildin function) in the original cnxtserial_buildin module
outputs "no tty: ...some-string..." via printk if something is received
from the modem but /dev/ttyS2 has been closed by the userland process.
The "...some-string..." is the string which has been received by the
modem and was then discarded. The rebuilt module only outputs "no tty",
which does not really matter.
|