ltphone development notes


Last Updated: Friday, June 29 2001

So I was having lunch with Rich Bodo, and we ended up heading over to a Starbucks after the Mexican place we went to closed. He mentioned something about using winmodems as cheap telephony cards, and, well...

THE LTPHONE PROJECT

Dramatis Personae

Richard J.M. Close (userland, ltmodem project founder)
Pavel Machek (driver code)
Jamie Lokier (driver code)
Martin Mares (pciutils)
Rich Bodo (ltphone idea)
Jason Spence (idiot monkey)

Locations

Pavel's page: http://www.suse.cz/development/ltmodem

Jamie's page: http://www.tantalophile.demon.co.uk/linmodem/
also see the subdirectories /ref/ and /jamie/ of the above site, particularly /ref/LTNOTES/ which is a gold nugget.

Richard's page: http://www.close.u-net.com/ltmodem.html

Here are some links to stuff about the chips:

http://www.lucent.com/micro/K56flex/PN96053.html This is weird; this page says "the DSP hardware performs MIPS-intensive operations, such as K56flex and V.34 and V.32 modulation, while the host performs other less MIPS-intensive functions, such as V.42bis." Turns out that our dinky winmodems have some processing capability after all.

http://www.semiconductor.com/reports/asp/ShowSummary.asp?KeyColumn=862

http://www.semiconductor.com/reports/asp/ShowSummary.asp?KeyColumn=73

http://www.lucent.com/micro/K56flex/docs/OT00114.pdf

Code

Hmm, ixj.c in drivers/telephony is almost 10,000 lines. Emacs refuses to color syntax highlight it, even.

PCI devices have four identification registers:

The vendor info for the MARS-2 chipset (Lucent's codename) is:

Vendor ID: 11c1 (Lucent Microelectronics) (now Agere)
Product ID: 0441 (56k modem)
Subvendor ID: 158d (Point Multimedia Systems, according to the Linux kernel)
Subproduct ID: 5517 (?)

The ixj.c driver seems to have the following initialization path:

6538: module_init(ixj_init);

6506: int __init ixj_init(void) 
6454: #if defined(CONFIG_PCI)

6521:	if (pci_present()) {
6522:		if ((probe = ixj_probe_pci(&cnt)) < 0) {
6523:			return probe;
6524:		}
6525:   }

6455: int __init ixj_probe_pci(int *cnt)

This I found to be a little disturbing:

3255:	// NOTE:
3256:	//      The DAA can only go to SLEEP, RINGING or PULSEDIALING modes
3257:	//      if the PSTN line is on-hook.  Failure to have the PSTN line
3258:	//      in the on-hook state WILL CAUSE A HARDWARE FAILURE OF THE
3259:	//      ALIS-A part.
3260:	//

The module_init(x) stuff is obvious; it marks a function to be run when the module is inserted. Less obvious is that the kernel uses the __init declaration to mark a function to be run at boot time if the module is compiled into the kernel (<*> instead of <M> in menuconfig terms). The relevant bits are in include/linux/init.h and arch/X/vmlinux.lds, where X is the architecture. That last one is a ld script to munge the (boy, and I thought *I* was abusing macros) symbols defined with the __init macro so they run during kernel initialization. I haven't found the mechanism which triggers the __initcall assembly symbol hack depending on the compliation status of the module.

drivers/telephony/phonedev.c defines phone_register_device, which you call with a pointer to your struct phone_device and a unit parameter. I'm not sure what the unit parameter does, but I see ixj.c use PHONE_DEV_ANY (defined in linux/phonedev.h), so I guess we should use that too. There's a comment in drivers/telephony/phonedev.c which implies that the function can be called with something besides PHONE_DEV_ANY as the unit parameter, but there aren't any other drivers that do so.

struct phone_device is defined as:

struct phone_device {
	struct phone_device *next;
	struct file_operations *f_op;
	int (*open) (struct phone_device *, struct file *);
	int board;		/* Device private index */
	int minor;		/* Assigned by phone_register_device */
};

struct file_operations is defined as:

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, struct dentry *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
	ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};

The pci_enable_device function does:

  1. Enables power to the device (sets power state D0)
  2. calls pcibios_enable_device, which calls
    1. pcibios_enable_resources.
    2. pcibios_enable_irq and
It can return an error, so you need to check for it (return will be < 0).

There's a sample kernel module at http://www.cc.gatech.edu/classes/AY2001/cs3210_fall/labs/compile-part2.html. Check it out.

You compile a kernel module with something like:

cc -Wall -DMODULE -D__KERNEL__ -DLINUX -c -o ltphone.o ltphone.c

Wednesday June 27 2001

So the ltmodem people initialize a global variable, io_address, when the pci utils starts up and detects the modem and stuff. This ends up setting BaseAddress when io_init is called, and all the port io stuff like dp_regread and dp_in_port and friends use the BaseAddress as their offset to do port IO. We can use the pci_dev structure to do so instead.

Here's what I get when I use the new detection code:

Found memory range: cfffaf00-cfffafff
Found IO port range Lucent Microelectronics 56k WinModem: d000-d007
Found IO port range Lucent Microelectronics 56k WinModem: cc00-ccff
Found ltmodem Lucent Microelectronics 56k WinModem at irq 5

I'm not sure which range I'm supposed to be poking and stuff from.

It looks like the first thing to try is to read the DSP version by creating and calling dp_modem_command from portIO.c with args 16,0,0. It looks like it puts stuff into offset + 0x37, which means we're going to be talking to the larger port range (cc00-ccff from above).

Friday, June 29 2001

So the ltmodem stuff calls find_modem to query the PCI registers and find the location of the IO addresses. They're copied in the order they are found, which is important for later.

port_io_init() defines two global variables, BaseAddress and BaseAddress2. port_io_init() is called from io_init() in ltmodem.c, with io_address[1] and io_address[2] as arguments, and assigns io_address[2] to BaseAddress, which means that if they're detecting the IO ranges in the same order that we are (why do they count from 1?), BaseAddress and friends are all pointing to IO ports in the larger second 256 port wide window.

Monday August 6 2001 Ah, back after Defcon craziness. Here we go...

dp_modem_command() calls
dp_dsp_regread() and
dp_regread() and
dp_regwrite() and
dp_bamil_rd7() if the command is 0x0c or 0x0e.
Otherwise, calls dp_regwrite() and wait_for_core_read().

dp_regwrite() calls
dp_out_port().

dp_out_port does a straight outb to BaseValue, which is a global set
in dp_regwrite.  The output port from dp_modem_command is 0x35, 0x36,
and 0x37.  I doubt that they mean port 0x35, 0x36, and 0x37.  More
likely they're offsets from BaseValue.

 - Set in ltphone_probe_pci() -
BaseAddress (0x00)
BaseAddressData (0x01)
BaseAddress2 (0x02)

 - Set in dp_regwrite() -
BaseValue (set to port)
BaseAddressIndex (set to BaseAddress)

Ah, the dp_byte_x variables need to be explicitly updated in the absence of an interrupt handler. Fixed. Checksum code breaks things, though.


Wednesday March 29 2006

Talked to Gary Fleury 408 546 1550 about getting datasheets for the 1646T00.


Home | Site Index | Email me