root/drivers/scsi/wd7000.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. wd7000_enable_intr
  2. wd7000_enable_dma
  3. delay
  4. command_out
  5. alloc_scb
  6. free_scb
  7. init_scbs
  8. mail_out
  9. make_code
  10. wd7000_scsi_done
  11. wd7000_intr_handle
  12. wd7000_queuecommand
  13. wd7000_command
  14. wd7000_init
  15. wd7000_revision
  16. wd7000_detect
  17. wd7000_append_info
  18. wd7000_info
  19. wd7000_abort
  20. wd7000_reset
  21. wd7000_biosparam

   1 /* $Id: wd7000.c,v 1.2 1994/01/15 06:02:32 drew Exp $
   2  *  linux/kernel/wd7000.c
   3  *
   4  *  Copyright (C) 1992  Thomas Wuensche
   5  *      closely related to the aha1542 driver from Tommy Thorn
   6  *      ( as close as different hardware allows on a lowlevel-driver :-) )
   7  *
   8  *  Revised (and renamed) by John Boyd <boyd@cis.ohio-state.edu> to
   9  *  accomodate Eric Youngdale's modifications to scsi.c.  Nov 1992.
  10  *
  11  *  Additional changes to support scatter/gather.  Dec. 1992.  tw/jb
  12  */
  13 
  14 #include <stdarg.h>
  15 #include <linux/kernel.h>
  16 #include <linux/head.h>
  17 #include <linux/types.h>
  18 #include <linux/string.h>
  19 #include <linux/sched.h>
  20 #include <asm/system.h>
  21 #include <asm/dma.h>
  22 #include <asm/io.h>
  23 #include <linux/ioport.h>
  24 
  25 #include "../block/blk.h"
  26 #include "scsi.h"
  27 #include "hosts.h"
  28 
  29 /* #define DEBUG  */
  30 
  31 #include "wd7000.h"
  32 
  33 
  34 #ifdef DEBUG
  35 #define DEB(x) x
  36 #else
  37 #define DEB(x)
  38 #endif
  39 
  40 
  41 /*
  42    Driver data structures:
  43    - mb and scbs are required for interfacing with the host adapter.
  44      An SCB has extra fields not visible to the adapter; mb's
  45      _cannot_ do this, since the adapter assumes they are contiguous in
  46      memory, 4 bytes each, with ICMBs following OGMBs, and uses this fact
  47      to access them.
  48    - An icb is for host-only (non-SCSI) commands.  ICBs are 16 bytes each;
  49      the additional bytes are used only by the driver.
  50    - For now, a pool of SCBs are kept in global storage by this driver,
  51      and are allocated and freed as needed.
  52 
  53   The 7000-FASST2 marks OGMBs empty as soon as it has _started_ a command,
  54   not when it has finished.  Since the SCB must be around for completion,
  55   problems arise when SCBs correspond to OGMBs, which may be reallocated
  56   earlier (or delayed unnecessarily until a command completes).
  57   Mailboxes are used as transient data structures, simply for
  58   carrying SCB addresses to/from the 7000-FASST2.
  59 
  60   Note also since SCBs are not "permanently" associated with mailboxes,
  61   there is no need to keep a global list of Scsi_Cmnd pointers indexed
  62   by OGMB.   Again, SCBs reference their Scsi_Cmnds directly, so mailbox
  63   indices need not be involved.
  64 */
  65 
  66 static struct {
  67        struct wd_mailbox ogmb[OGMB_CNT]; 
  68        struct wd_mailbox icmb[ICMB_CNT];
  69 } mb;
  70 static int next_ogmb = 0;   /* to reduce contention at mailboxes */
  71 
  72 static Scb scbs[MAX_SCBS];
  73 static Scb *scbfree = NULL;
  74 
  75 static int wd7000_host = 0;
  76 static unchar controlstat = 0;
  77 
  78 static unchar rev_1 = 0, rev_2 = 0;  /* filled in by wd7000_revision */
  79 
  80 #define wd7000_intr_ack()  outb(0,INTR_ACK)
  81 
  82 #define WAITnexttimeout 3000000
  83 
  84 
  85 static inline void wd7000_enable_intr(void)
     /* [previous][next][first][last][top][bottom][index][help] */
  86 {
  87     controlstat |= INT_EN;
  88     outb(controlstat,CONTROL);
  89 }
  90 
  91 
  92 static inline void wd7000_enable_dma(void)
     /* [previous][next][first][last][top][bottom][index][help] */
  93 {
  94     controlstat |= DMA_EN;
  95     outb(controlstat,CONTROL);
  96     set_dma_mode(DMA_CH, DMA_MODE_CASCADE);
  97     enable_dma(DMA_CH);
  98 }
  99 
 100 
 101 #define WAIT(port, mask, allof, noneof)                                 \
 102  { register WAITbits;                                                   \
 103    register WAITtimeout = WAITnexttimeout;                              \
 104    while (1) {                                                          \
 105      WAITbits = inb(port) & (mask);                                     \
 106      if ((WAITbits & (allof)) == (allof) && ((WAITbits & (noneof)) == 0)) \
 107        break;                                                           \
 108      if (--WAITtimeout == 0) goto fail;                                 \
 109    }                                                                    \
 110  }
 111 
 112 
 113 static inline void delay( unsigned how_long )
     /* [previous][next][first][last][top][bottom][index][help] */
 114 {
 115      unsigned long time = jiffies + how_long;
 116 
 117      while (jiffies < time);
 118 }
 119 
 120 
 121 static inline int command_out(unchar *cmdp, int len)
     /* [previous][next][first][last][top][bottom][index][help] */
 122 {
 123     while (len--)  {
 124         WAIT(ASC_STAT, STATMASK, CMD_RDY, 0);
 125         outb(*cmdp++, COMMAND);
 126     }
 127     return 1;
 128 
 129 fail:
 130     printk("wd7000_out WAIT failed(%d): ", len+1);
 131     return 0;
 132 }
 133 
 134 static inline Scb *alloc_scb(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 135 {
 136     Scb *scb;
 137     unsigned long flags;
 138 
 139     save_flags(flags);
 140     cli();
 141 
 142     if (scbfree == NULL)  {
 143         panic("wd7000: can't allocate free SCB.\n");
 144         restore_flags(flags);
 145         return NULL;
 146     }
 147     scb = scbfree;  scbfree = scb->next;
 148     memset(scb, 0, sizeof(Scb));  scb->next = NULL;
 149 
 150     restore_flags(flags);
 151 
 152     return scb;
 153 }
 154 
 155 
 156 static inline void free_scb( Scb *scb )
     /* [previous][next][first][last][top][bottom][index][help] */
 157 {
 158     unsigned long flags;
 159 
 160     save_flags(flags);
 161     cli();
 162 
 163     memset(scb, 0, sizeof(Scb));
 164     scb->next = scbfree;  scbfree = scb;
 165 
 166     restore_flags(flags);
 167 }
 168 
 169 
 170 static inline void init_scbs(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 171 {
 172     int i;
 173     unsigned long flags;
 174 
 175     save_flags(flags);
 176     cli();
 177 
 178     scbfree = &(scbs[0]);
 179     for (i = 0;  i < MAX_SCBS-1;  i++)  scbs[i].next = &(scbs[i+1]);
 180     scbs[MAX_SCBS-1].next = NULL;
 181 
 182     restore_flags(flags);
 183 }    
 184     
 185 
 186 static int mail_out( Scb *scbptr )
     /* [previous][next][first][last][top][bottom][index][help] */
 187 /*
 188  *  Note: this can also be used for ICBs; just cast to the parm type.
 189  */
 190 {
 191     int i, ogmb;
 192     unsigned long flags;
 193 
 194     DEB(printk("wd7000_scb_out: %06x");)
 195 
 196     /* We first look for a free outgoing mailbox */
 197     save_flags(flags);
 198     cli();
 199     ogmb = next_ogmb;
 200     for (i = 0; i < OGMB_CNT; i++) {
 201         if (mb.ogmb[ogmb].status == 0)  {
 202             DEB(printk(" using OGMB %x",ogmb));
 203             mb.ogmb[ogmb].status = 1;
 204             any2scsi(mb.ogmb[ogmb].scbptr, scbptr);
 205 
 206             next_ogmb = (ogmb+1) % OGMB_CNT;
 207             break;
 208         }  else
 209             ogmb = (++ogmb) % OGMB_CNT;
 210     }
 211     restore_flags(flags);
 212     DEB(printk(", scb is %x",scbptr);)
 213 
 214     if (i >= OGMB_CNT) {
 215         DEB(printk(", no free OGMBs.\n");)
 216         /* Alternatively, issue "interrupt on free OGMB", and sleep... */
 217         return 0;
 218     }
 219 
 220     wd7000_enable_intr(); 
 221     do  {
 222         WAIT(ASC_STAT,STATMASK,CMD_RDY,0);
 223         outb(START_OGMB|ogmb, COMMAND);
 224         WAIT(ASC_STAT,STATMASK,CMD_RDY,0);
 225     }  while (inb(ASC_STAT) & CMD_REJ);
 226 
 227     DEB(printk(", awaiting interrupt.\n");)
 228     return 1;
 229 
 230 fail:
 231     DEB(printk(", WAIT timed out.\n");)
 232     return 0;
 233 }
 234 
 235 
 236 int make_code(unsigned hosterr, unsigned scsierr)
     /* [previous][next][first][last][top][bottom][index][help] */
 237 {   
 238 #ifdef DEBUG
 239     int in_error = hosterr;
 240 #endif
 241 
 242     switch ((hosterr>>8)&0xff){
 243         case 0: /* Reserved */
 244                 hosterr = DID_ERROR;
 245                 break;
 246         case 1: /* Command Complete, no errors */
 247                 hosterr = DID_OK;
 248                 break;
 249         case 2: /* Command complete, error logged in scb status (scsierr) */ 
 250                 hosterr = DID_OK;
 251                 break;
 252         case 4: /* Command failed to complete - timeout */
 253                 hosterr = DID_TIME_OUT;
 254                 break;
 255         case 5: /* Command terminated; Bus reset by external device */
 256                 hosterr = DID_RESET;
 257                 break;
 258         case 6: /* Unexpected Command Received w/ host as target */
 259                 hosterr = DID_BAD_TARGET;
 260                 break;
 261         case 80: /* Unexpected Reselection */
 262         case 81: /* Unexpected Selection */
 263                 hosterr = DID_BAD_INTR;
 264                 break;
 265         case 82: /* Abort Command Message  */
 266                 hosterr = DID_ABORT;
 267                 break;
 268         case 83: /* SCSI Bus Software Reset */
 269         case 84: /* SCSI Bus Hardware Reset */
 270                 hosterr = DID_RESET;
 271                 break;
 272         default: /* Reserved */
 273                 hosterr = DID_ERROR;
 274                 break;
 275         }
 276 #ifdef DEBUG
 277     if (scsierr||hosterr)
 278         printk("\nSCSI command error: SCSI %02x host %04x return %d",
 279                scsierr,in_error,hosterr);
 280 #endif
 281     return scsierr | (hosterr << 16);
 282 }
 283 
 284 
 285 static void wd7000_scsi_done(Scsi_Cmnd * SCpnt)
     /* [previous][next][first][last][top][bottom][index][help] */
 286 {
 287     DEB(printk("wd7000_scsi_done: %06x\n",SCpnt);)
 288     SCpnt->SCp.phase = 0;
 289 }
 290 
 291 
 292 void wd7000_intr_handle(int irq)
     /* [previous][next][first][last][top][bottom][index][help] */
 293 {
 294     int flag, icmb, errstatus, icmb_status;
 295     int host_error, scsi_error;
 296     Scb *scb;             /* for SCSI commands */
 297     unchar *icb;          /* for host commands */
 298     Scsi_Cmnd *SCpnt;
 299 
 300     flag = inb(INTR_STAT);
 301     DEB(printk("wd7000_intr_handle: intr stat = %02x",flag);)
 302 
 303     if (!(inb(ASC_STAT)&0x80)){ 
 304         DEB(printk("\nwd7000_intr_handle: phantom interrupt...\n");)
 305         wd7000_intr_ack();
 306         return; 
 307     }
 308 
 309     /* check for an incoming mailbox */
 310     if ((flag & 0x40) == 0) {
 311         /*  for a free OGMB - need code for this case... */
 312         DEB(printk("wd7000_intr_handle: free outgoing mailbox\n");)
 313         wd7000_intr_ack();
 314         return;
 315     }
 316     /* The interrupt is for an incoming mailbox */
 317     icmb = flag & 0x3f;
 318     scb = (struct scb *) scsi2int(mb.icmb[icmb].scbptr);
 319     icmb_status = mb.icmb[icmb].status;
 320     mb.icmb[icmb].status = 0;
 321 
 322 #ifdef DEBUG
 323     printk(" ICMB %d posted for SCB/ICB %06x, status %02x, vue %02x",
 324            icmb, scb, icmb_status, scb->vue );
 325 #endif
 326 
 327     if (!(scb->op & 0x80))  {   /* an SCB is done */
 328         SCpnt = scb->SCpnt;
 329         if (--(SCpnt->SCp.phase) <= 0)  {  /* all scbs for SCpnt are done */
 330             host_error = scb->vue | (icmb_status << 8);
 331             scsi_error = scb->status;
 332             errstatus = make_code(host_error,scsi_error);    
 333             SCpnt->result = errstatus;
 334 
 335             if (SCpnt->host_scribble != NULL)
 336                 scsi_free(SCpnt->host_scribble,WD7000_SCRIBBLE);
 337             free_scb(scb);
 338 
 339             SCpnt->scsi_done(SCpnt);
 340         }
 341     }  else  {    /* an ICB is done */
 342         icb = (unchar *) scb;
 343         icb[ICB_STATUS] = icmb_status;
 344         icb[ICB_PHASE] = 0;
 345     }
 346 
 347     wd7000_intr_ack();
 348     DEB(printk(".\n");)
 349     return;
 350 }
 351 
 352 
 353 int wd7000_queuecommand(Scsi_Cmnd * SCpnt, void (*done)(Scsi_Cmnd *))
     /* [previous][next][first][last][top][bottom][index][help] */
 354 {
 355     Scb *scb;
 356     Sgb *sgb;
 357     unchar *cdb;
 358     unchar idlun;
 359     short cdblen;
 360 
 361     cdb = (unchar *) SCpnt->cmnd;
 362     cdblen = COMMAND_SIZE(*cdb);
 363     idlun = ((SCpnt->target << 5) & 0xe0) | (SCpnt->lun & 7);
 364     SCpnt->scsi_done = done;
 365     SCpnt->SCp.phase = 1;
 366     scb = alloc_scb();
 367     scb->idlun = idlun;
 368     memcpy(scb->cdb, cdb, cdblen);
 369     scb->direc = 0x40;          /* Disable direction check */
 370     scb->SCpnt = SCpnt;         /* so we can find stuff later */
 371     SCpnt->host_scribble = NULL;
 372     DEB(printk("request_bufflen is %x, bufflen is %x\n",\
 373         SCpnt->request_bufflen, SCpnt->bufflen);)
 374 
 375     if (SCpnt->use_sg)  {
 376         struct scatterlist *sg = (struct scatterlist *) SCpnt->request_buffer;
 377         unsigned i;
 378 
 379         if (scsi_hosts[wd7000_host].sg_tablesize <= 0)  {
 380             panic("wd7000_queuecommand: scatter/gather not supported.\n");
 381         }
 382 #ifdef DEBUG
 383         printk("Using scatter/gather with %d elements.\n",SCpnt->use_sg);
 384 #endif
 385         /*
 386             Allocate memory for a scatter/gather-list in wd7000 format.
 387             Save the pointer at host_scribble.
 388         */
 389 #ifdef DEBUG
 390         if (SCpnt->use_sg > WD7000_SG)
 391             panic("WD7000: requesting too many scatterblocks\n");
 392 #endif
 393         SCpnt->host_scribble = (unsigned char *) scsi_malloc(WD7000_SCRIBBLE);
 394         sgb = (Sgb *) SCpnt->host_scribble;
 395         if (sgb == NULL)
 396             panic("wd7000_queuecommand: scsi_malloc() failed.\n");
 397 
 398         scb->op = 1;
 399         any2scsi(scb->dataptr, sgb);
 400         any2scsi(scb->maxlen, SCpnt->use_sg * sizeof (Sgb) );
 401 
 402         for (i = 0;  i < SCpnt->use_sg;  i++)  {
 403             any2scsi(sgb->ptr, sg[i].address);
 404             any2scsi(sgb->len, sg[i].length);
 405             sgb++;
 406         }
 407         DEB(printk("Using %d bytes for %d scatter/gather blocks\n",\
 408             scsi2int(scb->maxlen), SCpnt->use_sg);)
 409     }  else  {
 410         scb->op = 0;
 411         any2scsi(scb->dataptr, SCpnt->request_buffer);
 412         any2scsi(scb->maxlen, SCpnt->request_bufflen);
 413     }
 414 
 415     return mail_out(scb);
 416 }
 417 
 418 
 419 int wd7000_command(Scsi_Cmnd *SCpnt)
     /* [previous][next][first][last][top][bottom][index][help] */
 420 {
 421     wd7000_queuecommand(SCpnt, wd7000_scsi_done);
 422 
 423     while (SCpnt->SCp.phase > 0);  /* phase counts scbs down to 0 */
 424 
 425     return SCpnt->result;
 426 }
 427 
 428 
 429 int wd7000_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 430 {   int i;
 431     unchar init_block[] = {
 432         INITIALIZATION, 7, BUS_ON, BUS_OFF, 0, 0, 0, 0, OGMB_CNT, ICMB_CNT
 433     };
 434 
 435     /* Reset the adapter. */
 436     outb(SCSI_RES|ASC_RES, CONTROL);
 437     delay(1);  /* reset pulse: this is 10ms, only need 25us */
 438     outb(0,CONTROL);  controlstat = 0;
 439     /*
 440        Wait 2 seconds, then expect Command Port Ready.
 441 
 442        I suspect something else needs to be done here, but I don't know
 443        what.  The OEM doc says power-up diagnostics take 2 seconds, and
 444        indeed, SCSI commands submitted before then will time out, but
 445        none of what follows seems deterred by _not_ waiting 2 secs.
 446     */
 447     delay(200);
 448 
 449     WAIT(ASC_STAT, STATMASK, CMD_RDY, 0);
 450     DEB(printk("wd7000_init: Power-on Diagnostics finished\n");)
 451     if (((i=inb(INTR_STAT)) != 1) && (i != 7)) {
 452         panic("wd7000_init: Power-on Diagnostics error\n"); 
 453         return 0;
 454     }
 455     
 456     /* Clear mailboxes */
 457     memset(&mb,0,sizeof (mb));
 458     /* Set up SCB free list */
 459     init_scbs();
 460 
 461     /* Set up init block */
 462     any2scsi(init_block+5,&mb);
 463     /* Execute init command */
 464     if (!command_out(init_block,sizeof(init_block)))  {
 465         panic("WD-7000 Initialization failed.\n"); 
 466         return 0;
 467     }
 468     
 469     /* Wait until init finished */
 470     WAIT(ASC_STAT, STATMASK, CMD_RDY | ASC_INI, 0);
 471     outb(DISABLE_UNS_INTR, COMMAND); 
 472     WAIT(ASC_STAT, STATMASK, CMD_RDY | ASC_INI, 0);
 473 
 474     /* Enable Interrupt and DMA */
 475     if (request_irq(IRQ_LVL, wd7000_intr_handle)) {
 476       panic("Unable to allocate IRQ for WD-7000.\n");
 477       return 0;
 478     };
 479     if(request_dma(DMA_CH)) {
 480       panic("Unable to allocate DMA channel for WD-7000.\n");
 481       free_irq(IRQ_LVL);
 482       return 0;
 483     };
 484     wd7000_enable_dma();
 485     wd7000_enable_intr();
 486 
 487     printk("WD-7000 initialized.\n");
 488     return 1;
 489   fail:
 490     return 0;                                   /* 0 = not ok */
 491 }
 492 
 493 
 494 void wd7000_revision(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 495 {
 496     volatile unchar icb[ICB_LEN] = {0x8c};  /* read firmware revision level */
 497 
 498     icb[ICB_PHASE] = 1;
 499     mail_out( (struct scb *) icb );
 500     while (icb[ICB_PHASE]) /* wait for completion */;
 501     rev_1 = icb[1];
 502     rev_2 = icb[2];
 503 
 504     /*
 505         For boards at rev 7.0 or later, enable scatter/gather.
 506     */
 507     if (rev_1 >= 7)  scsi_hosts[wd7000_host].sg_tablesize = WD7000_SG;
 508 }
 509 
 510 
 511 static const char *wd_bases[] = {(char *)0xce000,(char *)0xd8000};
 512 
 513 typedef struct {
 514     char * signature;
 515     unsigned offset;
 516     unsigned length;
 517 } Signature;
 518 
 519 static const Signature signatures[] = {{"SSTBIOS",0xd,0x7}};
 520 
 521 #define NUM_SIGNATURES (sizeof(signatures)/sizeof(Signature))
 522 
 523 
 524 int wd7000_detect(int hostnum)
     /* [previous][next][first][last][top][bottom][index][help] */
 525 /* 
 526  *  return non-zero on detection
 527  */
 528 {
 529     int i,j;
 530     char const *base_address = NULL;
 531 
 532     if(check_region(IO_BASE, 4)) return 0;  /* IO ports in use */
 533     for(i=0;i<(sizeof(wd_bases)/sizeof(char *));i++){
 534         for(j=0;j<NUM_SIGNATURES;j++){
 535             if(!memcmp((void *)(wd_bases[i] + signatures[j].offset),
 536                 (void *) signatures[j].signature,signatures[j].length)){
 537                     base_address=wd_bases[i];
 538                     printk("WD-7000 detected.\n");
 539             }   
 540         }
 541     }
 542     if (base_address == NULL) return 0;
 543 
 544     snarf_region(IO_BASE, 4); /* Register our ports */
 545     /* Store our host number */
 546     wd7000_host = hostnum;
 547 
 548     wd7000_init();    
 549     wd7000_revision();  /* will set scatter/gather by rev level */
 550 
 551     return 1;
 552 }
 553 
 554 
 555 
 556 static void wd7000_append_info( char *info, const char *fmt, ... )
     /* [previous][next][first][last][top][bottom][index][help] */
 557 /*
 558  *  This is just so I can use vsprintf...
 559  */
 560 {
 561     va_list args;
 562     extern int vsprintf(char *buf, const char *fmt, va_list args);
 563 
 564     va_start(args, fmt);
 565     vsprintf(info, fmt, args);
 566     va_end(args);
 567 
 568     return;
 569 }
 570 
 571 
 572 const char *wd7000_info(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 573 {
 574     static char info[80] = "Western Digital WD-7000, Firmware Revision ";
 575 
 576     wd7000_revision();
 577     wd7000_append_info( info+strlen(info), "%d.%d.\n", rev_1, rev_2 );
 578 
 579     return info;
 580 }
 581 
 582 int wd7000_abort(Scsi_Cmnd * SCpnt, int i)
     /* [previous][next][first][last][top][bottom][index][help] */
 583 {
 584 #ifdef DEBUG
 585     printk("wd7000_abort: Scsi_Cmnd = 0x%08x, code = %d ", SCpnt, i);
 586     printk("id %d lun %d cdb", SCpnt->target, SCpnt->lun);
 587     {  int j;  unchar *cdbj = (unchar *) SCpnt->cmnd;
 588        for (j=0; j < COMMAND_SIZE(*cdbj);  j++)  printk(" %02x", *(cdbj++));
 589        printk(" result %08x\n", SCpnt->result);
 590     }
 591 #endif
 592     return 0;
 593 }
 594 
 595 
 596 /* We do not implement a reset function here, but the upper level code assumes
 597    that it will get some kind of response for the command in SCpnt.  We must
 598    oblige, or the command will hang the scsi system */
 599 
 600 int wd7000_reset(Scsi_Cmnd * SCpnt)
     /* [previous][next][first][last][top][bottom][index][help] */
 601 {
 602 #ifdef DEBUG
 603     printk("wd7000_reset\n");
 604 #endif
 605     if (SCpnt) SCpnt->flags |= NEEDS_JUMPSTART;
 606     return 0;
 607 }
 608 
 609 
 610 int wd7000_biosparam(int size, int dev, int* ip)
     /* [previous][next][first][last][top][bottom][index][help] */
 611 /*
 612  *  This is borrowed directly from aha1542.c, but my disks are organized
 613  *   this way, so I think it will work OK.
 614  */
 615 {
 616   ip[0] = 64;
 617   ip[1] = 32;
 618   ip[2] = size >> 11;
 619 /*  if (ip[2] >= 1024) ip[2] = 1024; */
 620   return 0;
 621 }
 622 

/* [previous][next][first][last][top][bottom][index][help] */