/* * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ /* * multi-media commands * * as of mmc-6, mode page 0x2a (capabilities & mechanical status) is legacy * and read-only, last defined in mmc-3. mode page 5 (write parameters) * applies only to cd-r(w) and dvd-r(w); *-rom, dvd+* and bd-* are right out. */#include <u.h>#include <libc.h>#include <disk.h>#include "../scuzz/scsireq.h"#include "dat.h"#include "fns.h" enum{ Desperate = 0, /* non-zero grubs around in inquiry string */ Pagesz = 255, Pagerrrecov = 1, /* error recovery */ Pagwrparams = 5, /* (cd|dvd)-r(w) device write parameters */ Pagcache = 8, Pagcapmechsts = 0x2a, Invistrack = 0xff, /* the invisible & incomplete track */ Maxresptracks = 120, /* (1024-slop) / 8 */ /* Pagerrrecov error control bits (buf[2]) */ Erawre = 1<<7, /* recover from write errors */ Erarre = 1<<6, /* recover from read errors */ Ertb = 1<<5, /* transfer bad block to host */ Errc = 1<<4, /* read continuous; no error recovery */ Erper = 1<<2, /* post error: report recovered errors */ Erdte = 1<<1, /* disable transfer on error */ Erdcr = 1<<0, /* disable correction */ Kilo = 1000LL, GB = Kilo * Kilo * Kilo,}; typedef struct Intfeat Intfeat;typedef struct Mmcaux Mmcaux; static Dev mmcdev; struct Mmcaux { /* drive characteristics */ uchar page05[Pagesz]; /* write parameters */ int page05ok; int pagecmdsz; /* disc characteristics */ long mmcnwa; /* next writable address (block #) */ int nropen; int nwopen; vlong ntotby; long ntotbk;}; struct Intfeat { int numb; char *name;}; enum { Featrandwrite = 0x20, Featdfctmgmt = 0x24, Featwriteonce = 0x25, Featedfctrpt = 0x29, Featdvdrw = 0x2a,}; Intfeat intfeats[] = { Featrandwrite, "random writable", /* for write-and-verify */// 0x21, "incr. streaming writable", Featdfctmgmt, "hw defect mgmt.", Featwriteonce, "write once", /* for write-and-verify */ Featedfctrpt, "enhanced defect reporting", Featdvdrw, "dvd+rw", /* for dvd write-and-verify */ 0x38, "pseudo-overwrite",// 0x40, "bd read",// 0x41, "bd write",}; /* these will be printed as user ids, so no spaces please */static char *dvdtype[] = { "dvd-rom", "dvd-ram", "dvd-r", "dvd-rw", "hd-dvd-rom", "hd-dvd-ram", "hd-dvd-r", "type-7-unknown", "type-8-unknown", "dvd+rw", "dvd+r", "type-11-unknown", "type-12-unknown", "dvd+rw-dl", "dvd+r-dl", "type-15-unknown",}; static int format(Drive *drive);static int getinvistrack(Drive *drive); static ulongbige(void *p){ uchar *a; a = p; return (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|(a[3]<<0);} static ushortbiges(void *p){ uchar *a; a = p; return (a[0]<<8) | a[1];} ulonggetnwa(Drive *drive){ Mmcaux *aux; aux = drive->aux; return aux->mmcnwa;} static voidhexdump(void *v, int n){ int i; uchar *p; p = v; for(i=0; i<n; i++){ print("%.2ux ", p[i]); if((i%8) == 7) print("\n"); } if(i%8) print("\n");} static voidinitcdb(uchar *cdb, int len, int cmd){ memset(cdb, 0, len); cdb[0] = cmd;} /* * SCSI CDBs (cmd arrays) are 6, 10, 12, 16 or 32 bytes long. * The mode sense/select commands implicitly refer to * a mode parameter list, which consists of an 8-byte * mode parameter header, followed by zero or more block * descriptors and zero or more mode pages (MMC-2 §5.5.2). * We'll ignore mode sub-pages. * Integers are stored big-endian. * * The format of the mode parameter (10) header is: * ushort mode_data_length; // of following bytes * uchar medium_type; * uchar device_specific; * uchar reserved[2]; * ushort block_descriptor_length; // zero * * The format of the mode parameter (6) header is: * uchar mode_data_length; // of following bytes * uchar medium_type; * uchar device_specific; * uchar block_descriptor_length; // zero * * The format of the mode pages is: * uchar page_code_and_PS; * uchar page_len; // of following bytes * uchar parameter[page_len]; * * see SPC-3 §4.3.4.6 for allocation length and §7.4 for mode parameter lists. */ enum { Mode10parmhdrlen= 8, Mode6parmhdrlen = 4, Modepaghdrlen = 2,}; static intmmcgetpage10(Drive *drive, int page, void *v){ uchar cmd[10], resp[512]; int n, r; initcdb(cmd, sizeof cmd, ScmdMsense10); cmd[2] = page; cmd[8] = 255; /* allocation length: buffer size */ n = scsi(drive, cmd, sizeof(cmd), resp, sizeof(resp), Sread); if(n < Mode10parmhdrlen) return -1; r = (resp[6]<<8) | resp[7]; /* block descriptor length */ n -= Mode10parmhdrlen + r; if(n < 0) return -1; if(n > Pagesz) n = Pagesz; memmove(v, &resp[Mode10parmhdrlen + r], n); return n;} static intmmcgetpage6(Drive *drive, int page, void *v){ uchar cmd[6], resp[512]; int n; initcdb(cmd, sizeof cmd, ScmdMsense6); cmd[2] = page; cmd[4] = 255; /* allocation length */ n = scsi(drive, cmd, sizeof(cmd), resp, sizeof(resp), Sread); if(n < Mode6parmhdrlen) return -1; n -= Mode6parmhdrlen + resp[3]; if(n < 0) return -1; if(n > Pagesz) n = Pagesz; memmove(v, &resp[Mode6parmhdrlen + resp[3]], n); return n;} static intmmcsetpage10(Drive *drive, int page, void *v){ uchar cmd[10], *p, *pagedata; int len, n; /* allocate parameter list, copy in mode page, fill in header */ pagedata = v; assert(pagedata[0] == page); len = Mode10parmhdrlen + Modepaghdrlen + pagedata[1]; p = emalloc(len); memmove(p + Mode10parmhdrlen, pagedata, pagedata[1]); /* parameter list header */ p[0] = 0; p[1] = len - 2; /* set up CDB */ initcdb(cmd, sizeof cmd, ScmdMselect10); cmd[1] = 0x10; /* format not vendor-specific */ cmd[8] = len; // print("set: sending cmd\n");// hexdump(cmd, 10);// print("parameter list header\n");// hexdump(p, Mode10parmhdrlen);// print("page\n");// hexdump(p + Mode10parmhdrlen, len - Mode10parmhdrlen); n = scsi(drive, cmd, sizeof(cmd), p, len, Swrite); // print("set: got cmd\n");// hexdump(cmd, 10); free(p); if(n < len) return -1; return 0;} static intmmcsetpage6(Drive *drive, int page, void *v){ uchar cmd[6], *p, *pagedata; int len, n; if (vflag) print("mmcsetpage6 called!\n"); pagedata = v; assert(pagedata[0] == page); len = Mode6parmhdrlen + Modepaghdrlen + pagedata[1]; p = emalloc(len); memmove(p + Mode6parmhdrlen, pagedata, pagedata[1]); initcdb(cmd, sizeof cmd, ScmdMselect6); cmd[1] = 0x10; /* format not vendor-specific */ cmd[4] = len; n = scsi(drive, cmd, sizeof(cmd), p, len, Swrite); free(p); if(n < len) return -1; return 0;} static intmmcgetpage(Drive *drive, int page, void *v){ Mmcaux *aux; aux = drive->aux; switch(aux->pagecmdsz) { case 10: return mmcgetpage10(drive, page, v); case 6: return mmcgetpage6(drive, page, v); default: assert(0); } return -1;} static intmmcsetpage(Drive *drive, int page, void *v){ Mmcaux *aux; aux = drive->aux; switch(aux->pagecmdsz) { case 10: return mmcsetpage10(drive, page, v); case 6: return mmcsetpage6(drive, page, v); default: assert(0); } return -1;} intmmcstatus(Drive *drive){ uchar cmd[12]; initcdb(cmd, sizeof cmd, ScmdCDstatus); /* mechanism status */ return scsi(drive, cmd, sizeof(cmd), nil, 0, Sread);} voidmmcgetspeed(Drive *drive){ int n, maxread, curread, maxwrite, curwrite; uchar buf[Pagesz]; memset(buf, 0, 22); n = mmcgetpage(drive, Pagcapmechsts, buf); /* legacy page */ if (n < 22) { if (vflag) fprint(2, "no Pagcapmechsts mode page!\n"); return; } maxread = (buf[8]<<8)|buf[9]; curread = (buf[14]<<8)|buf[15]; maxwrite = (buf[18]<<8)|buf[19]; curwrite = (buf[20]<<8)|buf[21]; if(maxread && maxread < 170 || curread && curread < 170) return; /* bogus data */ drive->readspeed = curread; drive->writespeed = curwrite; drive->maxreadspeed = maxread; drive->maxwritespeed = maxwrite;} static intgetdevtype(Drive *drive){ int n; uchar cmd[6], resp[Pagesz]; initcdb(cmd, sizeof cmd, ScmdInq); cmd[3] = sizeof resp >> 8; cmd[4] = sizeof resp; n = scsi(drive, cmd, sizeof(cmd), resp, sizeof resp, Sread); if (n < 8) return -1; return resp[0] & 037;} static intstart(Drive *drive, int code){ uchar cmd[6]; initcdb(cmd, sizeof cmd, ScmdStart); cmd[4] = code; return scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone);} static intsetcaching(Drive *drive){ int n; uchar buf[Pagesz]; /* * we can't actually control caching much. * see SBC-2 §6.3.3 but also MMC-6 §7.6. * * should set read ahead, MMC-6 §6.37; seems to control caching. */ n = mmcgetpage(drive, Pagcache, buf); if (n < 3) return -1; /* n == 255; buf[1] == 10 (10 bytes after buf[1]) */ buf[0] &= 077; /* clear reserved bits, MMC-6 §7.2.3 */ assert(buf[0] == Pagcache); assert(buf[1] >= 10); buf[2] = Ccwce; if (mmcsetpage(drive, Pagcache, buf) < 0) { if (vflag) print("mmcprobe: cache control NOT set\n"); return -1; } return 0;} Drive*mmcprobe(Scsi *scsi){ Mmcaux *aux; Drive *drive; uchar buf[Pagesz]; int cap; if (vflag) print("mmcprobe: inquiry: %s\n", scsi->inquire); drive = emalloc(sizeof(Drive)); drive->Scsi = *scsi; drive->Dev = mmcdev; drive->invistrack = -1; getinvistrack(drive); aux = emalloc(sizeof(Mmcaux)); drive->aux = aux; scsiready(drive); drive->type = getdevtype(drive); if (drive->type != TypeCD) { werrstr("not an mmc device"); free(aux); free(drive); return nil; } /* * drive is an mmc device; learn what we can about it * (as opposed to the disc in it). */ start(drive, 1); /* attempt to read CD capabilities page, but it's now legacy */ if(mmcgetpage10(drive, Pagcapmechsts, buf) >= 0) aux->pagecmdsz = 10; else if(mmcgetpage6(drive, Pagcapmechsts, buf) >= 0) aux->pagecmdsz = 6; else { if (vflag) fprint(2, "no Pagcapmechsts mode page!\n"); werrstr("can't read mode page %d!", Pagcapmechsts); free(aux); free(drive); return nil; } cap = 0; if(buf[Capwrite] & (Capcdr|Capcdrw|Capdvdr|Capdvdram) || buf[Capmisc] & Caprw) cap |= Cwrite; if(buf[Capmisc] & Capcdda) /* CD-DA commands supported? */ cap |= Ccdda; /* not used anywhere else */ // print("read %d max %d\n", biges(buf+14), biges(buf+8));// print("write %d max %d\n", biges(buf+20), biges(buf+18)); /* cache optional page 05 (write parameter page) */ if(/* (cap & Cwrite) && */Lines 1–500 of 2104. Load more
/* * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ /* * multi-media commands * * as of mmc-6, mode page 0x2a (capabilities & mechanical status) is legacy * and read-only, last defined in mmc-3. mode page 5 (write parameters) * applies only to cd-r(w) and dvd-r(w); *-rom, dvd+* and bd-* are right out. */ #include <u.h> #include <libc.h> #include <disk.h> #include "../scuzz/scsireq.h" #include "dat.h" #include "fns.h" enum { Desperate = 0, /* non-zero grubs around in inquiry string */ Pagesz = 255, Pagerrrecov = 1, /* error recovery */ Pagwrparams = 5, /* (cd|dvd)-r(w) device write parameters */ Pagcache = 8, Pagcapmechsts = 0x2a, Invistrack = 0xff, /* the invisible & incomplete track */ Maxresptracks = 120, /* (1024-slop) / 8 */ /* Pagerrrecov error control bits (buf[2]) */ Erawre = 1<<7, /* recover from write errors */ Erarre = 1<<6, /* recover from read errors */ Ertb = 1<<5, /* transfer bad block to host */ Errc = 1<<4, /* read continuous; no error recovery */ Erper = 1<<2, /* post error: report recovered errors */ Erdte = 1<<1, /* disable transfer on error */ Erdcr = 1<<0, /* disable correction */ Kilo = 1000LL, GB = Kilo * Kilo * Kilo, }; typedef struct Intfeat Intfeat; typedef struct Mmcaux Mmcaux; static Dev mmcdev; struct Mmcaux { /* drive characteristics */ uchar page05[Pagesz]; /* write parameters */ int page05ok; int pagecmdsz; /* disc characteristics */ long mmcnwa; /* next writable address (block #) */ int nropen; int nwopen; vlong ntotby; long ntotbk; }; struct Intfeat { int numb; char *name; }; enum { Featrandwrite = 0x20, Featdfctmgmt = 0x24, Featwriteonce = 0x25, Featedfctrpt = 0x29, Featdvdrw = 0x2a, }; Intfeat intfeats[] = { Featrandwrite, "random writable", /* for write-and-verify */ // 0x21, "incr. streaming writable", Featdfctmgmt, "hw defect mgmt.", Featwriteonce, "write once", /* for write-and-verify */ Featedfctrpt, "enhanced defect reporting", Featdvdrw, "dvd+rw", /* for dvd write-and-verify */ 0x38, "pseudo-overwrite", // 0x40, "bd read", // 0x41, "bd write", }; /* these will be printed as user ids, so no spaces please */ static char *dvdtype[] = { "dvd-rom", "dvd-ram", "dvd-r", "dvd-rw", "hd-dvd-rom", "hd-dvd-ram", "hd-dvd-r", "type-7-unknown", "type-8-unknown", "dvd+rw", "dvd+r", "type-11-unknown", "type-12-unknown", "dvd+rw-dl", "dvd+r-dl", "type-15-unknown", }; static int format(Drive *drive); static int getinvistrack(Drive *drive); static ulong bige(void *p) { uchar *a; a = p; return (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|(a[3]<<0); } static ushort biges(void *p) { uchar *a; a = p; return (a[0]<<8) | a[1]; } ulong getnwa(Drive *drive) { Mmcaux *aux; aux = drive->aux; return aux->mmcnwa; } static void hexdump(void *v, int n) { int i; uchar *p; p = v; for(i=0; i<n; i++){ print("%.2ux ", p[i]); if((i%8) == 7) print("\n"); } if(i%8) print("\n"); } static void initcdb(uchar *cdb, int len, int cmd) { memset(cdb, 0, len); cdb[0] = cmd; } /* * SCSI CDBs (cmd arrays) are 6, 10, 12, 16 or 32 bytes long. * The mode sense/select commands implicitly refer to * a mode parameter list, which consists of an 8-byte * mode parameter header, followed by zero or more block * descriptors and zero or more mode pages (MMC-2 §5.5.2). * We'll ignore mode sub-pages. * Integers are stored big-endian. * * The format of the mode parameter (10) header is: * ushort mode_data_length; // of following bytes * uchar medium_type; * uchar device_specific; * uchar reserved[2]; * ushort block_descriptor_length; // zero * * The format of the mode parameter (6) header is: * uchar mode_data_length; // of following bytes * uchar medium_type; * uchar device_specific; * uchar block_descriptor_length; // zero * * The format of the mode pages is: * uchar page_code_and_PS; * uchar page_len; // of following bytes * uchar parameter[page_len]; * * see SPC-3 §4.3.4.6 for allocation length and §7.4 for mode parameter lists. */ enum { Mode10parmhdrlen= 8, Mode6parmhdrlen = 4, Modepaghdrlen = 2, }; static int mmcgetpage10(Drive *drive, int page, void *v) { uchar cmd[10], resp[512]; int n, r; initcdb(cmd, sizeof cmd, ScmdMsense10); cmd[2] = page; cmd[8] = 255; /* allocation length: buffer size */ n = scsi(drive, cmd, sizeof(cmd), resp, sizeof(resp), Sread); if(n < Mode10parmhdrlen) return -1; r = (resp[6]<<8) | resp[7]; /* block descriptor length */ n -= Mode10parmhdrlen + r; if(n < 0) return -1; if(n > Pagesz) n = Pagesz; memmove(v, &resp[Mode10parmhdrlen + r], n); return n; } static int mmcgetpage6(Drive *drive, int page, void *v) { uchar cmd[6], resp[512]; int n; initcdb(cmd, sizeof cmd, ScmdMsense6); cmd[2] = page; cmd[4] = 255; /* allocation length */ n = scsi(drive, cmd, sizeof(cmd), resp, sizeof(resp), Sread); if(n < Mode6parmhdrlen) return -1; n -= Mode6parmhdrlen + resp[3]; if(n < 0) return -1; if(n > Pagesz) n = Pagesz; memmove(v, &resp[Mode6parmhdrlen + resp[3]], n); return n; } static int mmcsetpage10(Drive *drive, int page, void *v) { uchar cmd[10], *p, *pagedata; int len, n; /* allocate parameter list, copy in mode page, fill in header */ pagedata = v; assert(pagedata[0] == page); len = Mode10parmhdrlen + Modepaghdrlen + pagedata[1]; p = emalloc(len); memmove(p + Mode10parmhdrlen, pagedata, pagedata[1]); /* parameter list header */ p[0] = 0; p[1] = len - 2; /* set up CDB */ initcdb(cmd, sizeof cmd, ScmdMselect10); cmd[1] = 0x10; /* format not vendor-specific */ cmd[8] = len; // print("set: sending cmd\n"); // hexdump(cmd, 10); // print("parameter list header\n"); // hexdump(p, Mode10parmhdrlen); // print("page\n"); // hexdump(p + Mode10parmhdrlen, len - Mode10parmhdrlen); n = scsi(drive, cmd, sizeof(cmd), p, len, Swrite); // print("set: got cmd\n"); // hexdump(cmd, 10); free(p); if(n < len) return -1; return 0; } static int mmcsetpage6(Drive *drive, int page, void *v) { uchar cmd[6], *p, *pagedata; int len, n; if (vflag) print("mmcsetpage6 called!\n"); pagedata = v; assert(pagedata[0] == page); len = Mode6parmhdrlen + Modepaghdrlen + pagedata[1]; p = emalloc(len); memmove(p + Mode6parmhdrlen, pagedata, pagedata[1]); initcdb(cmd, sizeof cmd, ScmdMselect6); cmd[1] = 0x10; /* format not vendor-specific */ cmd[4] = len; n = scsi(drive, cmd, sizeof(cmd), p, len, Swrite); free(p); if(n < len) return -1; return 0; } static int mmcgetpage(Drive *drive, int page, void *v) { Mmcaux *aux; aux = drive->aux; switch(aux->pagecmdsz) { case 10: return mmcgetpage10(drive, page, v); case 6: return mmcgetpage6(drive, page, v); default: assert(0); } return -1; } static int mmcsetpage(Drive *drive, int page, void *v) { Mmcaux *aux; aux = drive->aux; switch(aux->pagecmdsz) { case 10: return mmcsetpage10(drive, page, v); case 6: return mmcsetpage6(drive, page, v); default: assert(0); } return -1; } int mmcstatus(Drive *drive) { uchar cmd[12]; initcdb(cmd, sizeof cmd, ScmdCDstatus); /* mechanism status */ return scsi(drive, cmd, sizeof(cmd), nil, 0, Sread); } void mmcgetspeed(Drive *drive) { int n, maxread, curread, maxwrite, curwrite; uchar buf[Pagesz]; memset(buf, 0, 22); n = mmcgetpage(drive, Pagcapmechsts, buf); /* legacy page */ if (n < 22) { if (vflag) fprint(2, "no Pagcapmechsts mode page!\n"); return; } maxread = (buf[8]<<8)|buf[9]; curread = (buf[14]<<8)|buf[15]; maxwrite = (buf[18]<<8)|buf[19]; curwrite = (buf[20]<<8)|buf[21]; if(maxread && maxread < 170 || curread && curread < 170) return; /* bogus data */ drive->readspeed = curread; drive->writespeed = curwrite; drive->maxreadspeed = maxread; drive->maxwritespeed = maxwrite; } static int getdevtype(Drive *drive) { int n; uchar cmd[6], resp[Pagesz]; initcdb(cmd, sizeof cmd, ScmdInq); cmd[3] = sizeof resp >> 8; cmd[4] = sizeof resp; n = scsi(drive, cmd, sizeof(cmd), resp, sizeof resp, Sread); if (n < 8) return -1; return resp[0] & 037; } static int start(Drive *drive, int code) { uchar cmd[6]; initcdb(cmd, sizeof cmd, ScmdStart); cmd[4] = code; return scsi(drive, cmd, sizeof(cmd), cmd, 0, Snone); } static int setcaching(Drive *drive) { int n; uchar buf[Pagesz]; /* * we can't actually control caching much. * see SBC-2 §6.3.3 but also MMC-6 §7.6. * * should set read ahead, MMC-6 §6.37; seems to control caching. */ n = mmcgetpage(drive, Pagcache, buf); if (n < 3) return -1; /* n == 255; buf[1] == 10 (10 bytes after buf[1]) */ buf[0] &= 077; /* clear reserved bits, MMC-6 §7.2.3 */ assert(buf[0] == Pagcache); assert(buf[1] >= 10); buf[2] = Ccwce; if (mmcsetpage(drive, Pagcache, buf) < 0) { if (vflag) print("mmcprobe: cache control NOT set\n"); return -1; } return 0; } Drive* mmcprobe(Scsi *scsi) { Mmcaux *aux; Drive *drive; uchar buf[Pagesz]; int cap; if (vflag) print("mmcprobe: inquiry: %s\n", scsi->inquire); drive = emalloc(sizeof(Drive)); drive->Scsi = *scsi; drive->Dev = mmcdev; drive->invistrack = -1; getinvistrack(drive); aux = emalloc(sizeof(Mmcaux)); drive->aux = aux; scsiready(drive); drive->type = getdevtype(drive); if (drive->type != TypeCD) { werrstr("not an mmc device"); free(aux); free(drive); return nil; } /* * drive is an mmc device; learn what we can about it * (as opposed to the disc in it). */ start(drive, 1); /* attempt to read CD capabilities page, but it's now legacy */ if(mmcgetpage10(drive, Pagcapmechsts, buf) >= 0) aux->pagecmdsz = 10; else if(mmcgetpage6(drive, Pagcapmechsts, buf) >= 0) aux->pagecmdsz = 6; else { if (vflag) fprint(2, "no Pagcapmechsts mode page!\n"); werrstr("can't read mode page %d!", Pagcapmechsts); free(aux); free(drive); return nil; } cap = 0; if(buf[Capwrite] & (Capcdr|Capcdrw|Capdvdr|Capdvdram) || buf[Capmisc] & Caprw) cap |= Cwrite; if(buf[Capmisc] & Capcdda) /* CD-DA commands supported? */ cap |= Ccdda; /* not used anywhere else */ // print("read %d max %d\n", biges(buf+14), biges(buf+8)); // print("write %d max %d\n", biges(buf+20), biges(buf+18)); /* cache optional page 05 (write parameter page) */ if(/* (cap & Cwrite) && */