1#!/usr/bin/env python
2#
3# Copyright (c) 2014 Marcel Moolenaar
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9#
10# 1. Redistributions of source code must retain the above copyright
11#    notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright
13#    notice, this list of conditions and the following disclaimer in the
14#    documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26#
27#
28
29'''
30Simple diagnostics program fo the AMD Am89c900 series ILACC.
31This ethernet controller is emulated by VMware Fusion among
32possibly other virtualization platforms.
33
34The datasheet can be found here:
35    http://support.amd.com/TechDocs/18219.pdf
36
37This example program sends a single DHCP discovery packet,
38waits 2 seconds and then iterates over the receive ring for
39a targeted packet.
40
41For this program to function, connect the network interface
42to a network with a DHCP server. In VMware Fusion this can
43best be done by configuring the interface as a NAT interface
44using the "Share with my Mac" setting.
45'''
46
47import ctypes
48import logging
49import os
50import sys
51import time
52
53sys.path.append('/usr/lib')
54
55import bus
56from bus import dma as busdma
57
58
59# ILACC initialization block definition
60class initblock(ctypes.LittleEndianStructure):
61    _fields_ = [('mode', ctypes.c_uint32),
62                ('hwaddr', ctypes.c_uint8 * 6),
63                ('_pad1_', ctypes.c_uint16),
64                ('filter', ctypes.c_uint16 * 4),
65                ('rxdesc', ctypes.c_uint32),
66                ('txdesc', ctypes.c_uint32),
67                ('_pad2_', ctypes.c_uint32)]
68
69
70# ILACC ring buffer descriptor
71class bufdesc(ctypes.LittleEndianStructure):
72    _fields_ = [('buffer', ctypes.c_uint32),
73                ('flags', ctypes.c_uint32),
74                ('length', ctypes.c_uint32),
75                ('_pad_', ctypes.c_uint32)]
76
77
78# The DHCP packet definition (incl. all headers)
79class packet(ctypes.BigEndianStructure):
80    _pack_ = 1
81    _fields_ = [('eth_dest', ctypes.c_uint8 * 6),
82                ('eth_src', ctypes.c_uint8 * 6),
83                ('eth_type', ctypes.c_uint16),
84                ('ip_vl', ctypes.c_uint8),
85                ('ip_de', ctypes.c_uint8),
86                ('ip_len', ctypes.c_uint16),
87                ('ip_id', ctypes.c_uint16),
88                ('ip_ff', ctypes.c_uint16),
89                ('ip_ttl', ctypes.c_uint8),
90                ('ip_proto', ctypes.c_uint8),
91                ('ip_cksum', ctypes.c_uint16),
92                ('ip_src', ctypes.c_uint32),
93                ('ip_dest', ctypes.c_uint32),
94                ('udp_src', ctypes.c_uint16),
95                ('udp_dest', ctypes.c_uint16),
96                ('udp_len', ctypes.c_uint16),
97                ('udp_cksum', ctypes.c_uint16),
98                ('bootp_op', ctypes.c_uint8),
99                ('bootp_htype', ctypes.c_uint8),
100                ('bootp_hlen', ctypes.c_uint8),
101                ('bootp_hops', ctypes.c_uint8),
102                ('bootp_xid', ctypes.c_uint32),
103                ('bootp_secs', ctypes.c_uint16),
104                ('bootp_flags', ctypes.c_uint16),
105                ('bootp_ciaddr', ctypes.c_uint32),
106                ('bootp_yiaddr', ctypes.c_uint32),
107                ('bootp_siaddr', ctypes.c_uint32),
108                ('bootp_giaddr', ctypes.c_uint32),
109                ('bootp_chaddr', ctypes.c_uint8 * 16),
110                ('bootp_sname', ctypes.c_uint8 * 64),
111                ('bootp_file', ctypes.c_uint8 * 128),
112                ('dhcp_magic', ctypes.c_uint32),
113                ('dhcp_options', ctypes.c_uint8 * 60)]
114
115MACFMT = '%02x:%02x:%02x:%02x:%02x:%02x'
116
117dev = 'pci0:2:1:0'
118
119logging.basicConfig(level=logging.DEBUG)
120
121pcicfg = bus.map(dev, 'pcicfg')
122logging.debug('pcicfg=%s (%s)' % (pcicfg, dev))
123
124vendor = bus.read_2(pcicfg, 0)
125device = bus.read_2(pcicfg, 2)
126if vendor != 0x1022 or device != 0x2000:
127    logging.error('Not an AMD PCnet-PCI (vendor=%x, device=%x)' %
128                  (vendor, device))
129    sys.exit(1)
130
131command = bus.read_2(pcicfg, 4)
132if not (command & 1):
133    logging.info('enabling I/O port decoding')
134    command |= 1
135    bus.write_2(pcicfg, 4, command)
136
137if not (command & 4):
138    logging.info('enabling bus mastering')
139    command |= 4
140    bus.write_2(pcicfg, 4, command)
141
142bus.unmap(pcicfg)
143
144io = bus.map(dev, '10.io')
145logging.debug('io=%s (%s)' % (io, dev))
146
147
148def delay(msec):
149    time.sleep(msec / 1000.0)
150
151
152def ffs(x):
153    y = (1 + (x ^ (x-1))) >> 1
154    return y.bit_length()
155
156
157def ip_str(a):
158    return '%d.%d.%d.%d' % ((a >> 24) & 255, (a >> 16) & 255, (a >> 8) & 255,
159                            a & 255)
160
161
162def mac_is(l, r):
163    for i in range(6):
164        if l[i] != r[i]:
165            return False
166    return True
167
168
169def mac_str(m):
170    return MACFMT % (m[0], m[1], m[2], m[3], m[4], m[5])
171
172
173def rdbcr(reg):
174    bus.write_2(io, 0x12, reg & 0xffff)
175    return bus.read_2(io, 0x16)
176
177
178def wrbcr(reg, val):
179    bus.write_2(io, 0x12, reg & 0xffff)
180    bus.write_2(io, 0x16, val & 0xffff)
181
182
183def rdcsr(reg):
184    bus.write_2(io, 0x12, reg & 0xffff)
185    return bus.read_2(io, 0x10)
186
187
188def wrcsr(reg, val):
189    bus.write_2(io, 0x12, reg & 0xffff)
190    bus.write_2(io, 0x10, val & 0xffff)
191
192
193def start():
194    wrcsr(0, 0x42)
195    delay(100)
196
197
198def stop():
199    wrcsr(0, 4)
200    delay(100)
201
202
203mac = ()
204bcast = ()
205for o in range(6):
206    mac += (bus.read_1(io, o),)
207    bcast += (0xff,)
208logging.info('ethernet address = ' + MACFMT % mac)
209
210stop()
211wrbcr(20, 2)            # reset
212wrcsr(3, 0)             # byte swapping mode
213wrbcr(2, rdbcr(2) | 2)  # Autoneg
214
215memsize = 32*1024
216bufsize = 1536
217nrxbufs = 16
218ntxbufs = 4
219logging.debug("DMA memory: size = %#x (TX buffers: %u, RX buffers: %u)" %
220              (memsize, ntxbufs, nrxbufs))
221
222mem_tag = busdma.tag_create(dev, 16, 0, 0xffffffff, memsize, 1, memsize, 0, 0)
223dmamem = busdma.mem_alloc(mem_tag, 0)
224busseg = busdma.md_first_seg(dmamem, busdma.MD_BUS_SPACE)
225cpuseg = busdma.md_first_seg(dmamem, busdma.MD_VIRT_SPACE)
226busaddr = busdma.seg_get_addr(busseg)
227cpuaddr = busdma.seg_get_addr(cpuseg)
228logging.debug("DMA memory: CPU address: %#x, device address: %#x" %
229              (cpuaddr, busaddr))
230
231addr_initblock = cpuaddr
232addr_rxdesc = addr_initblock + ctypes.sizeof(initblock)
233addr_txdesc = addr_rxdesc + ctypes.sizeof(bufdesc) * nrxbufs
234addr_rxbufs = addr_txdesc + ctypes.sizeof(bufdesc) * ntxbufs
235addr_txbufs = addr_rxbufs + bufsize * nrxbufs
236
237ib = initblock.from_address(addr_initblock)
238ib.mode = ((ffs(ntxbufs) - 1) << 28) | ((ffs(nrxbufs) - 1) << 20)
239for i in range(len(mac)):
240    ib.hwaddr[i] = mac[i]
241for i in range(4):
242    ib.filter[i] = 0xffff
243ib.rxdesc = busaddr + (addr_rxdesc - cpuaddr)
244ib.txdesc = busaddr + (addr_txdesc - cpuaddr)
245ib._pad1_ = 0
246ib._pad2_ = 0
247
248for i in range(nrxbufs):
249    bd = bufdesc.from_address(addr_rxdesc + ctypes.sizeof(bufdesc) * i)
250    bd.buffer = busaddr + (addr_rxbufs - cpuaddr) + bufsize * i
251    bd.flags = (1 << 31) | (15 << 12) | (-bufsize & 0xfff)
252    bd.length = 0
253    bd._pad_ = 0
254
255for i in range(ntxbufs):
256    bd = bufdesc.from_address(addr_txdesc + ctypes.sizeof(bufdesc) * i)
257    bd.buffer = busaddr + (addr_txbufs - cpuaddr) + bufsize * i
258    bd.flags = (15 << 12)
259    bd.length = 0
260    bd._pad_ = 0
261
262busdma.sync_range(dmamem, busdma.SYNC_PREWRITE, 0, addr_rxbufs - cpuaddr)
263
264# Program address of DMA memory
265wrcsr(1, busaddr)
266wrcsr(2, busaddr >> 16)
267delay(100)
268
269# Initialize hardware
270wrcsr(0, 1)
271logging.debug('Waiting for initialization to complete')
272csr = rdcsr(0)
273while (csr & 0x100) == 0:
274    logging.debug('CSR=%#x' % (csr))
275    csr = rdcsr(0)
276
277start()
278
279pkt = packet.from_address(addr_txbufs)
280ctypes.memset(addr_txbufs, 0, ctypes.sizeof(pkt))
281options = [53, 1, 1]
282for i in range(len(options)):
283    pkt.dhcp_options[i] = options[i]
284pkt.dhcp_magic = 0x63825363
285for i in range(6):
286    pkt.bootp_chaddr[i] = mac[i]
287pkt.bootp_hlen = 6
288pkt.bootp_htype = 1
289pkt.bootp_op = 1
290pkt.udp_len = ctypes.sizeof(pkt) - 34
291pkt.udp_dest = 67
292pkt.udp_src = 68
293pkt.ip_dest = 0xffffffff
294pkt.ip_cksum = 0x79a6
295pkt.ip_proto = 17
296pkt.ip_ttl = 64
297pkt.ip_len = ctypes.sizeof(pkt) - 14
298pkt.ip_vl = 0x45
299pkt.eth_type = 0x0800
300for i in range(6):
301    pkt.eth_src[i] = mac[i]
302    pkt.eth_dest[i] = bcast[i]
303pktlen = ctypes.sizeof(pkt)
304
305busdma.sync_range(dmamem, busdma.SYNC_PREWRITE, addr_txbufs - cpuaddr, bufsize)
306
307bd = bufdesc.from_address(addr_txdesc)
308bd.length = 0
309bd.flags = (1 << 31) | (1 << 25) | (1 << 24) | (0xf << 12) | (-pktlen & 0xfff)
310
311busdma.sync_range(dmamem, busdma.SYNC_PREWRITE, addr_txdesc - cpuaddr,
312                  ctypes.sizeof(bufdesc))
313
314wrcsr(0, 0x48)
315
316logging.info('DHCP discovery packet sent')
317
318# Now wait 2 seconds for a DHCP offer to be received.
319logging.debug('Waiting 2 seconds for an offer to be received')
320time.sleep(2)
321
322stop()
323
324busdma.sync_range(dmamem, busdma.SYNC_PREWRITE, addr_rxdesc - cpuaddr,
325                  ctypes.sizeof(bufdesc) * nrxbufs)
326
327for i in range(nrxbufs):
328    bd = bufdesc.from_address(addr_rxdesc + ctypes.sizeof(bufdesc) * i)
329    if (bd.flags & 0x80000000):
330        continue
331    pkt = packet.from_address(addr_rxbufs + i * bufsize)
332    if mac_is(pkt.eth_dest, bcast):
333        logging.debug('RX #%d: broadcast packet: length %u' % (i, bd.length))
334        continue
335    if not mac_is(pkt.eth_dest, mac):
336        logging.debug('RX #%d: packet for %s?' % (i, mac_str(pkt.eth_dest)))
337        continue
338    logging.debug('RX %d: packet from %s!' % (i, mac_str(pkt.eth_src)))
339    logging.info('Our IP address = %s' % (ip_str(pkt.ip_dest)))
340
341busdma.mem_free(dmamem)
342busdma.tag_destroy(mem_tag)
343bus.unmap(io)
344