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