if_ed_hpp.c revision 154924
1/*- 2 * Copyright (c) 2005, M. Warner Losh 3 * All rights reserved. 4 * Copyright (c) 1995, David Greenman 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice unmodified, this list of conditions, and the following 12 * disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30#include <sys/cdefs.h> 31__FBSDID("$FreeBSD: head/sys/dev/ed/if_ed_hpp.c 154924 2006-01-27 19:10:13Z imp $"); 32 33#include "opt_ed.h" 34 35#ifdef ED_HPP 36 37#include <sys/param.h> 38#include <sys/systm.h> 39#include <sys/sockio.h> 40#include <sys/mbuf.h> 41#include <sys/kernel.h> 42#include <sys/socket.h> 43#include <sys/syslog.h> 44 45#include <sys/bus.h> 46 47#include <machine/bus.h> 48#include <sys/rman.h> 49#include <machine/resource.h> 50 51#include <net/ethernet.h> 52#include <net/if.h> 53#include <net/if_arp.h> 54#include <net/if_dl.h> 55#include <net/if_mib.h> 56#include <net/if_media.h> 57 58#include <net/bpf.h> 59 60#include <dev/ed/if_edreg.h> 61#include <dev/ed/if_edvar.h> 62 63static void ed_hpp_readmem(struct ed_softc *, bus_size_t, uint8_t *, 64 uint16_t); 65static void ed_hpp_writemem(struct ed_softc *, uint8_t *, uint16_t, 66 uint16_t); 67static void ed_hpp_set_physical_link(struct ed_softc *sc); 68static u_short ed_hpp_write_mbufs(struct ed_softc *, struct mbuf *, 69 bus_size_t); 70 71/* 72 * Interrupt conversion table for the HP PC LAN+ 73 */ 74static uint16_t ed_hpp_intr_val[] = { 75 0, /* 0 */ 76 0, /* 1 */ 77 0, /* 2 */ 78 3, /* 3 */ 79 4, /* 4 */ 80 5, /* 5 */ 81 6, /* 6 */ 82 7, /* 7 */ 83 0, /* 8 */ 84 9, /* 9 */ 85 10, /* 10 */ 86 11, /* 11 */ 87 12, /* 12 */ 88 0, /* 13 */ 89 0, /* 14 */ 90 15 /* 15 */ 91}; 92 93#define ED_HPP_TEST_SIZE 16 94 95/* 96 * Probe and vendor specific initialization for the HP PC Lan+ Cards. 97 * (HP Part nos: 27247B and 27252A). 98 * 99 * The card has an asic wrapper around a DS8390 core. The asic handles 100 * host accesses and offers both standard register IO and memory mapped 101 * IO. Memory mapped I/O allows better performance at the expense of greater 102 * chance of an incompatibility with existing ISA cards. 103 * 104 * The card has a few caveats: it isn't tolerant of byte wide accesses, only 105 * short (16 bit) or word (32 bit) accesses are allowed. Some card revisions 106 * don't allow 32 bit accesses; these are indicated by a bit in the software 107 * ID register (see if_edreg.h). 108 * 109 * Other caveats are: we should read the MAC address only when the card 110 * is inactive. 111 * 112 * For more information; please consult the CRYNWR packet driver. 113 * 114 * The AUI port is turned on using the "link2" option on the ifconfig 115 * command line. 116 */ 117int 118ed_probe_HP_pclanp(device_t dev, int port_rid, int flags) 119{ 120 struct ed_softc *sc = device_get_softc(dev); 121 int error; 122 int n; /* temp var */ 123 int memsize; /* mem on board */ 124 u_char checksum; /* checksum of board address */ 125 u_char irq; /* board configured IRQ */ 126 uint8_t test_pattern[ED_HPP_TEST_SIZE]; /* read/write areas for */ 127 uint8_t test_buffer[ED_HPP_TEST_SIZE]; /* probing card */ 128 u_long conf_maddr, conf_msize, conf_irq, junk; 129 130 error = ed_alloc_port(dev, 0, ED_HPP_IO_PORTS); 131 if (error) 132 return (error); 133 134 /* Fill in basic information */ 135 sc->asic_offset = ED_HPP_ASIC_OFFSET; 136 sc->nic_offset = ED_HPP_NIC_OFFSET; 137 138 sc->chip_type = ED_CHIP_TYPE_DP8390; 139 sc->isa16bit = 0; /* the 8390 core needs to be in byte mode */ 140 141 /* 142 * Look for the HP PCLAN+ signature: "0x50,0x48,0x00,0x53" 143 */ 144 145 if ((ed_asic_inb(sc, ED_HPP_ID) != 0x50) || 146 (ed_asic_inb(sc, ED_HPP_ID + 1) != 0x48) || 147 ((ed_asic_inb(sc, ED_HPP_ID + 2) & 0xF0) != 0) || 148 (ed_asic_inb(sc, ED_HPP_ID + 3) != 0x53)) 149 return (ENXIO); 150 151 /* 152 * Read the MAC address and verify checksum on the address. 153 */ 154 155 ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_MAC); 156 for (n = 0, checksum = 0; n < ETHER_ADDR_LEN; n++) 157 checksum += (sc->enaddr[n] = 158 ed_asic_inb(sc, ED_HPP_MAC_ADDR + n)); 159 160 checksum += ed_asic_inb(sc, ED_HPP_MAC_ADDR + ETHER_ADDR_LEN); 161 162 if (checksum != 0xFF) 163 return (ENXIO); 164 165 /* 166 * Verify that the software model number is 0. 167 */ 168 169 ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_ID); 170 if (((sc->hpp_id = ed_asic_inw(sc, ED_HPP_PAGE_4)) & 171 ED_HPP_ID_SOFT_MODEL_MASK) != 0x0000) 172 return (ENXIO); 173 174 /* 175 * Read in and save the current options configured on card. 176 */ 177 178 sc->hpp_options = ed_asic_inw(sc, ED_HPP_OPTION); 179 180 sc->hpp_options |= (ED_HPP_OPTION_NIC_RESET | 181 ED_HPP_OPTION_CHIP_RESET | ED_HPP_OPTION_ENABLE_IRQ); 182 183 /* 184 * Reset the chip. This requires writing to the option register 185 * so take care to preserve the other bits. 186 */ 187 188 ed_asic_outw(sc, ED_HPP_OPTION, 189 (sc->hpp_options & ~(ED_HPP_OPTION_NIC_RESET | 190 ED_HPP_OPTION_CHIP_RESET))); 191 192 DELAY(5000); /* wait for chip reset to complete */ 193 194 ed_asic_outw(sc, ED_HPP_OPTION, 195 (sc->hpp_options | (ED_HPP_OPTION_NIC_RESET | 196 ED_HPP_OPTION_CHIP_RESET | 197 ED_HPP_OPTION_ENABLE_IRQ))); 198 199 DELAY(5000); 200 201 if (!(ed_nic_inb(sc, ED_P0_ISR) & ED_ISR_RST)) 202 return (ENXIO); /* reset did not complete */ 203 204 /* 205 * Read out configuration information. 206 */ 207 208 ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_HW); 209 210 irq = ed_asic_inb(sc, ED_HPP_HW_IRQ); 211 212 /* 213 * Check for impossible IRQ. 214 */ 215 216 if (irq >= (sizeof(ed_hpp_intr_val) / sizeof(ed_hpp_intr_val[0]))) 217 return (ENXIO); 218 219 /* 220 * If the kernel IRQ was specified with a '?' use the cards idea 221 * of the IRQ. If the kernel IRQ was explicitly specified, it 222 * should match that of the hardware. 223 */ 224 error = bus_get_resource(dev, SYS_RES_IRQ, 0, &conf_irq, &junk); 225 if (error) 226 bus_set_resource(dev, SYS_RES_IRQ, 0, ed_hpp_intr_val[irq], 1); 227 else { 228 if (conf_irq != ed_hpp_intr_val[irq]) 229 return (ENXIO); 230 } 231 232 /* 233 * Fill in softconfig info. 234 */ 235 236 sc->vendor = ED_VENDOR_HP; 237 sc->type = ED_TYPE_HP_PCLANPLUS; 238 sc->type_str = "HP-PCLAN+"; 239 240 sc->mem_shared = 0; /* we DON'T have dual ported RAM */ 241 sc->mem_start = 0; /* we use offsets inside the card RAM */ 242 243 sc->hpp_mem_start = NULL;/* no memory mapped I/O by default */ 244 245 /* 246 * The board has 32KB of memory. Is there a way to determine 247 * this programmatically? 248 */ 249 250 memsize = 32768; 251 252 /* 253 * Check if memory mapping of the I/O registers possible. 254 */ 255 if (sc->hpp_options & ED_HPP_OPTION_MEM_ENABLE) { 256 u_long mem_addr; 257 258 /* 259 * determine the memory address from the board. 260 */ 261 262 ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_HW); 263 mem_addr = (ed_asic_inw(sc, ED_HPP_HW_MEM_MAP) << 8); 264 265 /* 266 * Check that the kernel specified start of memory and 267 * hardware's idea of it match. 268 */ 269 error = bus_get_resource(dev, SYS_RES_MEMORY, 0, 270 &conf_maddr, &conf_msize); 271 if (error) 272 return (error); 273 274 if (mem_addr != conf_maddr) 275 return (ENXIO); 276 277 error = ed_alloc_memory(dev, 0, memsize); 278 if (error) 279 return (error); 280 281 sc->hpp_mem_start = rman_get_virtual(sc->mem_res); 282 } 283 284 /* 285 * Fill in the rest of the soft config structure. 286 */ 287 288 /* 289 * The transmit page index. 290 */ 291 292 sc->tx_page_start = ED_HPP_TX_PAGE_OFFSET; 293 294 if (device_get_flags(dev) & ED_FLAGS_NO_MULTI_BUFFERING) 295 sc->txb_cnt = 1; 296 else 297 sc->txb_cnt = 2; 298 299 /* 300 * Memory description 301 */ 302 303 sc->mem_size = memsize; 304 sc->mem_ring = sc->mem_start + 305 (sc->txb_cnt * ED_PAGE_SIZE * ED_TXBUF_SIZE); 306 sc->mem_end = sc->mem_start + sc->mem_size; 307 308 /* 309 * Receive area starts after the transmit area and 310 * continues till the end of memory. 311 */ 312 313 sc->rec_page_start = sc->tx_page_start + 314 (sc->txb_cnt * ED_TXBUF_SIZE); 315 sc->rec_page_stop = (sc->mem_size / ED_PAGE_SIZE); 316 317 318 sc->cr_proto = 0; /* value works */ 319 320 /* 321 * Set the wrap registers for string I/O reads. 322 */ 323 324 ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_HW); 325 ed_asic_outw(sc, ED_HPP_HW_WRAP, 326 ((sc->rec_page_start / ED_PAGE_SIZE) | 327 (((sc->rec_page_stop / ED_PAGE_SIZE) - 1) << 8))); 328 329 /* 330 * Reset the register page to normal operation. 331 */ 332 333 ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_PERF); 334 335 /* 336 * Verify that we can read/write from adapter memory. 337 * Create test pattern. 338 */ 339 340 for (n = 0; n < ED_HPP_TEST_SIZE; n++) 341 test_pattern[n] = (n*n) ^ ~n; 342 343#undef ED_HPP_TEST_SIZE 344 345 /* 346 * Check that the memory is accessible thru the I/O ports. 347 * Write out the contents of "test_pattern", read back 348 * into "test_buffer" and compare the two for any 349 * mismatch. 350 */ 351 352 for (n = 0; n < (32768 / ED_PAGE_SIZE); n ++) { 353 ed_hpp_writemem(sc, test_pattern, (n * ED_PAGE_SIZE), 354 sizeof(test_pattern)); 355 ed_hpp_readmem(sc, (n * ED_PAGE_SIZE), 356 test_buffer, sizeof(test_pattern)); 357 358 if (bcmp(test_pattern, test_buffer, 359 sizeof(test_pattern))) 360 return (ENXIO); 361 } 362 363 sc->sc_mediachg = ed_hpp_set_physical_link; 364 sc->sc_write_mbufs = ed_hpp_write_mbufs; 365 sc->readmem = ed_hpp_readmem; 366 return (0); 367} 368 369/* 370 * HP PC Lan+ : Set the physical link to use AUI or TP/TL. 371 */ 372 373static void 374ed_hpp_set_physical_link(struct ed_softc *sc) 375{ 376 struct ifnet *ifp = sc->ifp; 377 int lan_page; 378 379 ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_LAN); 380 lan_page = ed_asic_inw(sc, ED_HPP_PAGE_0); 381 382 if (ifp->if_flags & IFF_LINK2) { 383 /* 384 * Use the AUI port. 385 */ 386 387 lan_page |= ED_HPP_LAN_AUI; 388 ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_LAN); 389 ed_asic_outw(sc, ED_HPP_PAGE_0, lan_page); 390 } else { 391 /* 392 * Use the ThinLan interface 393 */ 394 395 lan_page &= ~ED_HPP_LAN_AUI; 396 ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_LAN); 397 ed_asic_outw(sc, ED_HPP_PAGE_0, lan_page); 398 } 399 400 /* 401 * Wait for the lan card to re-initialize itself 402 */ 403 DELAY(150000); /* wait 150 ms */ 404 405 /* 406 * Restore normal pages. 407 */ 408 ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_PERF); 409} 410 411/* 412 * Support routines to handle the HP PC Lan+ card. 413 */ 414 415/* 416 * HP PC Lan+: Read from NIC memory, using either PIO or memory mapped 417 * IO. 418 */ 419 420static void 421ed_hpp_readmem(struct ed_softc *sc, bus_size_t src, uint8_t *dst, 422 uint16_t amount) 423{ 424 int use_32bit_access = !(sc->hpp_id & ED_HPP_ID_16_BIT_ACCESS); 425 426 /* Program the source address in RAM */ 427 ed_asic_outw(sc, ED_HPP_PAGE_2, src); 428 429 /* 430 * The HP PC Lan+ card supports word reads as well as 431 * a memory mapped i/o port that is aliased to every 432 * even address on the board. 433 */ 434 if (sc->hpp_mem_start) { 435 /* Enable memory mapped access. */ 436 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options & 437 ~(ED_HPP_OPTION_MEM_DISABLE | 438 ED_HPP_OPTION_BOOT_ROM_ENB)); 439 440 if (use_32bit_access && (amount > 3)) { 441 uint32_t *dl = (uint32_t *) dst; 442 volatile uint32_t *const sl = 443 (uint32_t *) sc->hpp_mem_start; 444 uint32_t *const fence = dl + (amount >> 2); 445 446 /* 447 * Copy out NIC data. We could probably write this 448 * as a `movsl'. The currently generated code is lousy. 449 */ 450 while (dl < fence) 451 *dl++ = *sl; 452 453 dst += (amount & ~3); 454 amount &= 3; 455 456 } 457 458 /* Finish off any words left, as a series of short reads */ 459 if (amount > 1) { 460 u_short *d = (u_short *) dst; 461 volatile u_short *const s = 462 (u_short *) sc->hpp_mem_start; 463 u_short *const fence = d + (amount >> 1); 464 465 /* Copy out NIC data. */ 466 while (d < fence) 467 *d++ = *s; 468 469 dst += (amount & ~1); 470 amount &= 1; 471 } 472 473 /* 474 * read in a byte; however we need to always read 16 bits 475 * at a time or the hardware gets into a funny state 476 */ 477 478 if (amount == 1) { 479 /* need to read in a short and copy LSB */ 480 volatile u_short *const s = 481 (volatile u_short *) sc->hpp_mem_start; 482 *dst = (*s) & 0xFF; 483 } 484 485 /* Restore Boot ROM access. */ 486 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options); 487 } else { 488 /* Read in data using the I/O port */ 489 if (use_32bit_access && (amount > 3)) { 490 ed_asic_insl(sc, ED_HPP_PAGE_4, dst, amount >> 2); 491 dst += (amount & ~3); 492 amount &= 3; 493 } 494 if (amount > 1) { 495 ed_asic_insw(sc, ED_HPP_PAGE_4, dst, amount >> 1); 496 dst += (amount & ~1); 497 amount &= 1; 498 } 499 if (amount == 1) { /* read in a short and keep the LSB */ 500 *dst = ed_asic_inw(sc, ED_HPP_PAGE_4) & 0xFF; 501 } 502 } 503} 504 505/* 506 * HP PC Lan+: Write to NIC memory, using either PIO or memory mapped 507 * IO. 508 * Only used in the probe routine to test the memory. 'len' must 509 * be even. 510 */ 511static void 512ed_hpp_writemem(struct ed_softc *sc, uint8_t *src, uint16_t dst, uint16_t len) 513{ 514 /* reset remote DMA complete flag */ 515 ed_nic_outb(sc, ED_P0_ISR, ED_ISR_RDC); 516 517 /* program the write address in RAM */ 518 ed_asic_outw(sc, ED_HPP_PAGE_0, dst); 519 520 if (sc->hpp_mem_start) { 521 u_short *s = (u_short *) src; 522 volatile u_short *d = (u_short *) sc->hpp_mem_start; 523 u_short *const fence = s + (len >> 1); 524 525 /* 526 * Enable memory mapped access. 527 */ 528 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options & 529 ~(ED_HPP_OPTION_MEM_DISABLE | 530 ED_HPP_OPTION_BOOT_ROM_ENB)); 531 532 /* 533 * Copy to NIC memory. 534 */ 535 while (s < fence) 536 *d = *s++; 537 538 /* 539 * Restore Boot ROM access. 540 */ 541 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options); 542 } else { 543 /* write data using I/O writes */ 544 ed_asic_outsw(sc, ED_HPP_PAGE_4, src, len / 2); 545 } 546} 547 548/* 549 * Write to HP PC Lan+ NIC memory. Access to the NIC can be by using 550 * outsw() or via the memory mapped interface to the same register. 551 * Writes have to be in word units; byte accesses won't work and may cause 552 * the NIC to behave weirdly. Long word accesses are permitted if the ASIC 553 * allows it. 554 */ 555 556static u_short 557ed_hpp_write_mbufs(struct ed_softc *sc, struct mbuf *m, bus_size_t dst) 558{ 559 int len, wantbyte; 560 unsigned short total_len; 561 unsigned char savebyte[2]; 562 volatile u_short * const d = 563 (volatile u_short *) sc->hpp_mem_start; 564 int use_32bit_accesses = !(sc->hpp_id & ED_HPP_ID_16_BIT_ACCESS); 565 566 /* select page 0 registers */ 567 ed_nic_outb(sc, ED_P0_CR, sc->cr_proto | ED_CR_STA); 568 569 /* reset remote DMA complete flag */ 570 ed_nic_outb(sc, ED_P0_ISR, ED_ISR_RDC); 571 572 /* program the write address in RAM */ 573 ed_asic_outw(sc, ED_HPP_PAGE_0, dst); 574 575 if (sc->hpp_mem_start) /* enable memory mapped I/O */ 576 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options & 577 ~(ED_HPP_OPTION_MEM_DISABLE | 578 ED_HPP_OPTION_BOOT_ROM_ENB)); 579 580 wantbyte = 0; 581 total_len = 0; 582 583 if (sc->hpp_mem_start) { /* Memory mapped I/O port */ 584 while (m) { 585 total_len += (len = m->m_len); 586 if (len) { 587 caddr_t data = mtod(m, caddr_t); 588 /* finish the last word of the previous mbuf */ 589 if (wantbyte) { 590 savebyte[1] = *data; 591 *d = *((u_short *) savebyte); 592 data++; len--; wantbyte = 0; 593 } 594 /* output contiguous words */ 595 if ((len > 3) && (use_32bit_accesses)) { 596 volatile uint32_t *const dl = 597 (volatile uint32_t *) d; 598 uint32_t *sl = (uint32_t *) data; 599 uint32_t *fence = sl + (len >> 2); 600 601 while (sl < fence) 602 *dl = *sl++; 603 604 data += (len & ~3); 605 len &= 3; 606 } 607 /* finish off remain 16 bit writes */ 608 if (len > 1) { 609 u_short *s = (u_short *) data; 610 u_short *fence = s + (len >> 1); 611 612 while (s < fence) 613 *d = *s++; 614 615 data += (len & ~1); 616 len &= 1; 617 } 618 /* save last byte if needed */ 619 if ((wantbyte = (len == 1)) != 0) 620 savebyte[0] = *data; 621 } 622 m = m->m_next; /* to next mbuf */ 623 } 624 if (wantbyte) /* write last byte */ 625 *d = *((u_short *) savebyte); 626 } else { 627 /* use programmed I/O */ 628 while (m) { 629 total_len += (len = m->m_len); 630 if (len) { 631 caddr_t data = mtod(m, caddr_t); 632 /* finish the last word of the previous mbuf */ 633 if (wantbyte) { 634 savebyte[1] = *data; 635 ed_asic_outw(sc, ED_HPP_PAGE_4, 636 *((u_short *)savebyte)); 637 data++; 638 len--; 639 wantbyte = 0; 640 } 641 /* output contiguous words */ 642 if ((len > 3) && use_32bit_accesses) { 643 ed_asic_outsl(sc, ED_HPP_PAGE_4, 644 data, len >> 2); 645 data += (len & ~3); 646 len &= 3; 647 } 648 /* finish off remaining 16 bit accesses */ 649 if (len > 1) { 650 ed_asic_outsw(sc, ED_HPP_PAGE_4, 651 data, len >> 1); 652 data += (len & ~1); 653 len &= 1; 654 } 655 if ((wantbyte = (len == 1)) != 0) 656 savebyte[0] = *data; 657 658 } /* if len != 0 */ 659 m = m->m_next; 660 } 661 if (wantbyte) /* spit last byte */ 662 ed_asic_outw(sc, ED_HPP_PAGE_4, *(u_short *)savebyte); 663 664 } 665 666 if (sc->hpp_mem_start) /* turn off memory mapped i/o */ 667 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options); 668 669 return (total_len); 670} 671 672#endif /* ED_HPP */ 673