From: Dam Sunwoo Date: Wed, 2 Jun 2010 17:58:18 +0000 (-0500) Subject: ARM: Added support for Access Flag and some CP15 regs (V2PCWPR, V2PCWPW, V2PCWUR... X-Git-Tag: stable_2012_02_02~1057 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=6c8dd32fa4f21771a2c83886b08c3d68be516044;p=gem5.git ARM: Added support for Access Flag and some CP15 regs (V2PCWPR, V2PCWPW, V2PCWUR, V2PCWUW,...) --- diff --git a/src/arch/arm/isa.cc b/src/arch/arm/isa.cc index 25e616e8e..60f00e438 100644 --- a/src/arch/arm/isa.cc +++ b/src/arch/arm/isa.cc @@ -117,6 +117,38 @@ ISA::clear() miscRegs[MISCREG_MPIDR] = 0; + // Reset values of PRRR and NMRR are implementation dependent + + miscRegs[MISCREG_PRRR] = + (1 << 19) | // 19 + (0 << 18) | // 18 + (0 << 17) | // 17 + (1 << 16) | // 16 + (2 << 14) | // 15:14 + (0 << 12) | // 13:12 + (2 << 10) | // 11:10 + (2 << 8) | // 9:8 + (2 << 6) | // 7:6 + (2 << 4) | // 5:4 + (1 << 2) | // 3:2 + 0; // 1:0 + miscRegs[MISCREG_NMRR] = + (1 << 30) | // 31:30 + (0 << 26) | // 27:26 + (0 << 24) | // 25:24 + (3 << 22) | // 23:22 + (2 << 20) | // 21:20 + (0 << 18) | // 19:18 + (0 << 16) | // 17:16 + (1 << 14) | // 15:14 + (0 << 12) | // 13:12 + (2 << 10) | // 11:10 + (0 << 8) | // 9:8 + (3 << 6) | // 7:6 + (2 << 4) | // 5:4 + (0 << 2) | // 3:2 + 0; // 1:0 + //XXX We need to initialize the rest of the state. } @@ -362,6 +394,63 @@ ISA::setMiscReg(int misc_reg, const MiscReg &val, ThreadContext *tc) case MISCREG_DTLBIASID: tc->getDTBPtr()->flushAsid(bits(newVal, 7,0)); return; + case MISCREG_V2PCWPR: + case MISCREG_V2PCWPW: + case MISCREG_V2PCWUR: + case MISCREG_V2PCWUW: + case MISCREG_V2POWPR: + case MISCREG_V2POWPW: + case MISCREG_V2POWUR: + case MISCREG_V2POWUW: + { + RequestPtr req = new Request; + unsigned flags; + BaseTLB::Mode mode; + Fault fault; + switch(misc_reg) { + case MISCREG_V2PCWPR: + flags = TLB::MustBeOne; + mode = BaseTLB::Read; + break; + case MISCREG_V2PCWPW: + flags = TLB::MustBeOne; + mode = BaseTLB::Write; + break; + case MISCREG_V2PCWUR: + flags = TLB::MustBeOne | TLB::UserMode; + mode = BaseTLB::Read; + break; + case MISCREG_V2PCWUW: + flags = TLB::MustBeOne | TLB::UserMode; + mode = BaseTLB::Write; + break; + case MISCREG_V2POWPR: + case MISCREG_V2POWPW: + case MISCREG_V2POWUR: + case MISCREG_V2POWUW: + panic("Security Extensions not implemented!"); + } + req->setVirt(0, val, 1, flags, tc->readPC()); + fault = tc->getDTBPtr()->translateAtomic(req, tc, mode); + if (fault == NoFault) { + miscRegs[MISCREG_PAR] = + (req->getPaddr() & 0xfffff000) | + (tc->getDTBPtr()->getAttr() ); + DPRINTF(MiscRegs, + "MISCREG: Translated addr 0x%08x: PAR: 0x%08x\n", + val, miscRegs[MISCREG_PAR]); + } + else { + // Set fault bit and FSR + FSR fsr = miscRegs[MISCREG_DFSR]; + miscRegs[MISCREG_PAR] = + (fsr.ext << 6) | + (fsr.fsHigh << 5) | + (fsr.fsLow << 1) | + 0x1; // F bit + } + return; + } } } setMiscRegNoEffect(misc_reg, newVal); diff --git a/src/arch/arm/miscregs.hh b/src/arch/arm/miscregs.hh index ad8f01dd0..267626e74 100644 --- a/src/arch/arm/miscregs.hh +++ b/src/arch/arm/miscregs.hh @@ -138,6 +138,15 @@ namespace ArmISA MISCREG_CTR, MISCREG_SCR, MISCREG_SDER, + MISCREG_PAR, + MISCREG_V2PCWPR, + MISCREG_V2PCWPW, + MISCREG_V2PCWUR, + MISCREG_V2PCWUW, + MISCREG_V2POWPR, + MISCREG_V2POWPW, + MISCREG_V2POWUR, + MISCREG_V2POWUW, MISCREG_CP15_UNIMP_START, MISCREG_TCMTR = MISCREG_CP15_UNIMP_START, MISCREG_ID_PFR1, @@ -153,7 +162,6 @@ namespace ArmISA MISCREG_ID_ISAR3, MISCREG_ID_ISAR4, MISCREG_ID_ISAR5, - MISCREG_PAR, MISCREG_AIDR, MISCREG_ACTLR, MISCREG_ADFSR, @@ -163,14 +171,6 @@ namespace ArmISA MISCREG_MCCSW, MISCREG_DCCMVAU, MISCREG_NSACR, - MISCREG_V2PCWPR, - MISCREG_V2PCWPW, - MISCREG_V2PCWUR, - MISCREG_V2PCWUW, - MISCREG_V2POWPR, - MISCREG_V2POWPW, - MISCREG_V2POWUR, - MISCREG_V2POWUW, MISCREG_VBAR, MISCREG_MVBAR, MISCREG_ISR, @@ -206,20 +206,20 @@ namespace ArmISA "dtlbiall", "dtlbimva", "dtlbiasid", "tlbiall", "tlbimva", "tlbiasid", "tlbimvaa", "dfsr", "ifsr", "dfar", "ifar", "mpidr", - "prrr", "nmrr", "ttbcr", "id_pfr0", "ctr" - "scr", "sder" + "prrr", "nmrr", "ttbcr", "id_pfr0", "ctr", + "scr", "sder", "par", + "v2pcwpr", "v2pcwpw", "v2pcwur", "v2pcwuw", + "v2powpr", "v2powpw", "v2powur", "v2powuw", // Unimplemented below "tcmtr", "id_pfr1", "id_dfr0", "id_afr0", "id_mmfr0", "id_mmfr1", "id_mmfr2", "id_mmfr3", "id_isar0", "id_isar1", "id_isar2", "id_isar3", "id_isar4", "id_isar5", - "par", "aidr", "actlr", + "aidr", "actlr", "adfsr", "aifsr", "dcimvac", "dcisw", "mccsw", "dccmvau", "nsacr", - "v2pcwpr", "v2pcwpw", "v2pcwur", "v2pcwuw", - "v2powpr", "v2powpw", "v2powur", "v2powuw", "vbar", "mvbar", "isr", "fceidr", "nop", "raz" }; diff --git a/src/arch/arm/pagetable.hh b/src/arch/arm/pagetable.hh index f1e86f0cc..76b0e3bb8 100644 --- a/src/arch/arm/pagetable.hh +++ b/src/arch/arm/pagetable.hh @@ -133,6 +133,14 @@ struct TlbEntry bool nonCacheable; // Can we wrap this in mtype? bool sNp; // Section descriptor + // Memory Attributes + MemoryType mtype; + uint8_t innerAttrs; + uint8_t outerAttrs; + bool shareable; + uint32_t attributes; // Memory attributes formatted for PAR + + // Access permissions bool xn; // Execute Never uint8_t ap:3; // Access permissions bits diff --git a/src/arch/arm/table_walker.cc b/src/arch/arm/table_walker.cc index e3ecb7ddd..85d5b00cf 100644 --- a/src/arch/arm/table_walker.cc +++ b/src/arch/arm/table_walker.cc @@ -96,8 +96,8 @@ TableWalker::walk(RequestPtr _req, ThreadContext *_tc, uint8_t _cid, TLB::Mode m contextId = _cid; timing = _timing; - // XXX These should be cached or grabbed from cached copies in - // the TLB, all these miscreg reads are expensive + /** @todo These should be cached or grabbed from cached copies in + the TLB, all these miscreg reads are expensive */ vaddr = req->getVaddr() & ~PcModeMask; sctlr = tc->readMiscReg(MISCREG_SCTLR); cpsr = tc->readMiscReg(MISCREG_CPSR); @@ -149,58 +149,224 @@ TableWalker::walk(RequestPtr _req, ThreadContext *_tc, uint8_t _cid, TLB::Mode m } void -TableWalker::memAttrs(TlbEntry &te, uint8_t texcb) +TableWalker::memAttrs(TlbEntry &te, uint8_t texcb, bool s) { - + DPRINTF(TLBVerbose, "memAttrs texcb:%d s:%d\n", texcb, s); + te.shareable = false; // default value + bool outer_shareable = false; if (sctlr.tre == 0) { switch(texcb) { - case 0: - case 1: - case 4: - case 8: + case 0: // Stongly-ordered + te.nonCacheable = true; + te.mtype = TlbEntry::StronglyOrdered; + te.shareable = true; + te.innerAttrs = 1; + te.outerAttrs = 0; + break; + case 1: // Shareable Device te.nonCacheable = true; + te.mtype = TlbEntry::Device; + te.shareable = true; + te.innerAttrs = 3; + te.outerAttrs = 0; + break; + case 2: // Outer and Inner Write-Through, no Write-Allocate + te.mtype = TlbEntry::Normal; + te.shareable = s; + te.innerAttrs = 6; + te.outerAttrs = bits(texcb, 1, 0); + break; + case 3: // Outer and Inner Write-Back, no Write-Allocate + te.mtype = TlbEntry::Normal; + te.shareable = s; + te.innerAttrs = 7; + te.outerAttrs = bits(texcb, 1, 0); + break; + case 4: // Outer and Inner Non-cacheable + te.nonCacheable = true; + te.mtype = TlbEntry::Normal; + te.shareable = s; + te.innerAttrs = 0; + te.outerAttrs = bits(texcb, 1, 0); + break; + case 5: // Reserved + break; + case 6: // Implementation Defined break; - case 16: + case 7: // Outer and Inner Write-Back, Write-Allocate + te.mtype = TlbEntry::Normal; + te.shareable = s; + te.innerAttrs = 5; + te.outerAttrs = 1; + break; + case 8: // Non-shareable Device + te.nonCacheable = true; + te.mtype = TlbEntry::Device; + te.shareable = false; + te.innerAttrs = 3; + te.outerAttrs = 0; + break; + case 9 ... 15: // Reserved + break; + case 16 ... 31: // Cacheable Memory + te.mtype = TlbEntry::Normal; + te.shareable = s; if (bits(texcb, 1,0) == 0 || bits(texcb, 3,2) == 0) te.nonCacheable = true; + te.innerAttrs = bits(texcb, 1, 0); + te.outerAttrs = bits(texcb, 3, 2); break; + default: + panic("More than 32 states for 5 bits?\n"); } } else { PRRR prrr = tc->readMiscReg(MISCREG_PRRR); NMRR nmrr = tc->readMiscReg(MISCREG_NMRR); + DPRINTF(TLBVerbose, "memAttrs PRRR:%08x NMRR:%08x\n", prrr, nmrr); + uint8_t curr_tr, curr_ir, curr_or; switch(bits(texcb, 2,0)) { case 0: - if (nmrr.ir0 == 0 || nmrr.or0 == 0 || prrr.tr0 != 0x2) - te.nonCacheable = true; + curr_tr = prrr.tr0; + curr_ir = nmrr.ir0; + curr_or = nmrr.or0; + outer_shareable = (prrr.nos0 == 0); break; case 1: - if (nmrr.ir1 == 0 || nmrr.or1 == 0 || prrr.tr1 != 0x2) - te.nonCacheable = true; + curr_tr = prrr.tr1; + curr_ir = nmrr.ir1; + curr_or = nmrr.or1; + outer_shareable = (prrr.nos1 == 0); break; case 2: - if (nmrr.ir2 == 0 || nmrr.or2 == 0 || prrr.tr2 != 0x2) - te.nonCacheable = true; + curr_tr = prrr.tr2; + curr_ir = nmrr.ir2; + curr_or = nmrr.or2; + outer_shareable = (prrr.nos2 == 0); break; case 3: - if (nmrr.ir3 == 0 || nmrr.or3 == 0 || prrr.tr3 != 0x2) - te.nonCacheable = true; + curr_tr = prrr.tr3; + curr_ir = nmrr.ir3; + curr_or = nmrr.or3; + outer_shareable = (prrr.nos3 == 0); break; case 4: - if (nmrr.ir4 == 0 || nmrr.or4 == 0 || prrr.tr4 != 0x2) - te.nonCacheable = true; + curr_tr = prrr.tr4; + curr_ir = nmrr.ir4; + curr_or = nmrr.or4; + outer_shareable = (prrr.nos4 == 0); break; case 5: - if (nmrr.ir5 == 0 || nmrr.or5 == 0 || prrr.tr5 != 0x2) - te.nonCacheable = true; + curr_tr = prrr.tr5; + curr_ir = nmrr.ir5; + curr_or = nmrr.or5; + outer_shareable = (prrr.nos5 == 0); break; case 6: panic("Imp defined type\n"); case 7: - if (nmrr.ir7 == 0 || nmrr.or7 == 0 || prrr.tr7 != 0x2) - te.nonCacheable = true; + curr_tr = prrr.tr7; + curr_ir = nmrr.ir7; + curr_or = nmrr.or7; + outer_shareable = (prrr.nos7 == 0); break; } + + switch(curr_tr) { + case 0: + DPRINTF(TLBVerbose, "StronglyOrdered\n"); + te.mtype = TlbEntry::StronglyOrdered; + te.nonCacheable = true; + te.innerAttrs = 1; + te.outerAttrs = 0; + te.shareable = true; + break; + case 1: + DPRINTF(TLBVerbose, "Device ds1:%d ds0:%d s:%d\n", + prrr.ds1, prrr.ds0, s); + te.mtype = TlbEntry::Device; + te.nonCacheable = true; + te.innerAttrs = 3; + te.outerAttrs = 0; + if (prrr.ds1 && s) + te.shareable = true; + if (prrr.ds0 && !s) + te.shareable = true; + break; + case 2: + DPRINTF(TLBVerbose, "Normal ns1:%d ns0:%d s:%d\n", + prrr.ns1, prrr.ns0, s); + te.mtype = TlbEntry::Normal; + if (prrr.ns1 && s) + te.shareable = true; + if (prrr.ns0 && !s) + te.shareable = true; + //te.shareable = outer_shareable; + break; + case 3: + panic("Reserved type"); + } + + if (te.mtype == TlbEntry::Normal){ + switch(curr_ir) { + case 0: + te.nonCacheable = true; + te.innerAttrs = 0; + break; + case 1: + te.innerAttrs = 5; + break; + case 2: + te.innerAttrs = 6; + break; + case 3: + te.innerAttrs = 7; + break; + } + + switch(curr_or) { + case 0: + te.nonCacheable = true; + te.outerAttrs = 0; + break; + case 1: + te.outerAttrs = 1; + break; + case 2: + te.outerAttrs = 2; + break; + case 3: + te.outerAttrs = 3; + break; + } + } } + + /** Formatting for Physical Address Register (PAR) + * Only including lower bits (TLB info here) + * PAR: + * PA [31:12] + * Reserved [11] + * TLB info [10:1] + * NOS [10] (Not Outer Sharable) + * NS [9] (Non-Secure) + * -- [8] (Implementation Defined) + * SH [7] (Sharable) + * Inner[6:4](Inner memory attributes) + * Outer[3:2](Outer memory attributes) + * SS [1] (SuperSection) + * F [0] (Fault, Fault Status in [6:1] if faulted) + */ + te.attributes = ( + ((outer_shareable ? 0:1) << 10) | + // TODO: NS Bit + ((te.shareable ? 1:0) << 7) | + (te.innerAttrs << 4) | + (te.outerAttrs << 2) + // TODO: Supersection bit + // TODO: Fault bit + ); + + } void @@ -218,11 +384,19 @@ TableWalker::doL1Descriptor() if (isFetch) fault = new PrefetchAbort(vaddr, ArmFault::Translation0); else - fault = new DataAbort(vaddr, NULL, isWrite, ArmFault::Translation0); + fault = new DataAbort(vaddr, NULL, isWrite, + ArmFault::Translation0); return; case L1Descriptor::Section: - if (sctlr.afe && bits(l1Desc.ap(), 0) == 0) - panic("Haven't implemented AFE\n"); + if (sctlr.afe && bits(l1Desc.ap(), 0) == 0) { + /** @todo: check sctlr.ha (bit[17]) if Hardware Access Flag is + * enabled if set, do l1.Desc.setAp0() instead of generating + * AccessFlag0 + */ + + fault = new DataAbort(vaddr, NULL, isWrite, + ArmFault::AccessFlag0); + } if (l1Desc.supersection()) { panic("Haven't implemented supersections\n"); @@ -238,7 +412,7 @@ TableWalker::doL1Descriptor() te.ap = l1Desc.ap(); te.domain = l1Desc.domain(); te.asid = contextId; - memAttrs(te, l1Desc.texcb()); + memAttrs(te, l1Desc.texcb(), l1Desc.shareable()); DPRINTF(TLB, "Inserting Section Descriptor into TLB\n"); DPRINTF(TLB, " - N%d pfn:%#x size: %#x global:%d valid: %d\n", @@ -256,7 +430,8 @@ TableWalker::doL1Descriptor() case L1Descriptor::PageTable: Addr l2desc_addr; l2desc_addr = l1Desc.l2Addr() | (bits(vaddr, 19,12) << 2); - DPRINTF(TLB, "L1 descriptor points to page table at: %#x\n", l2desc_addr); + DPRINTF(TLB, "L1 descriptor points to page table at: %#x\n", + l2desc_addr); // Trickbox address check fault = tlb->walkTrickBoxCheck(l2desc_addr, vaddr, sizeof(uint32_t), @@ -288,9 +463,6 @@ TableWalker::doL2Descriptor() DPRINTF(TLB, "L2 descriptor for %#x is %#x\n", vaddr, l2Desc.data); TlbEntry te; - if (sctlr.afe && bits(l1Desc.ap(), 0) == 0) - panic("Haven't implemented AFE\n"); - if (l2Desc.invalid()) { DPRINTF(TLB, "L2 descriptor invalid, causing fault\n"); tc = NULL; @@ -298,10 +470,19 @@ TableWalker::doL2Descriptor() if (isFetch) fault = new PrefetchAbort(vaddr, ArmFault::Translation1); else - fault = new DataAbort(vaddr, l1Desc.domain(), isWrite, ArmFault::Translation1); + fault = new DataAbort(vaddr, l1Desc.domain(), isWrite, + ArmFault::Translation1); return; } + if (sctlr.afe && bits(l2Desc.ap(), 0) == 0) { + /** @todo: check sctlr.ha (bit[17]) if Hardware Access Flag is enabled + * if set, do l2.Desc.setAp0() instead of generating AccessFlag0 + */ + + fault = new DataAbort(vaddr, NULL, isWrite, ArmFault::AccessFlag1); + } + if (l2Desc.large()) { te.N = 16; te.pfn = l2Desc.pfn(); @@ -319,7 +500,7 @@ TableWalker::doL2Descriptor() te.xn = l2Desc.xn(); te.ap = l2Desc.ap(); te.domain = l1Desc.domain(); - memAttrs(te, l2Desc.texcb()); + memAttrs(te, l2Desc.texcb(), l2Desc.shareable()); tc = NULL; req = NULL; diff --git a/src/arch/arm/table_walker.hh b/src/arch/arm/table_walker.hh index 8e851acd7..f6d3bee06 100644 --- a/src/arch/arm/table_walker.hh +++ b/src/arch/arm/table_walker.hh @@ -68,8 +68,13 @@ class TableWalker : public MemObject Reserved }; + /** The raw bits of the entry */ uint32_t data; + /** This entry has been modified (access flag set) and needs to be + * written back to memory */ + bool _dirty; + EntryType type() const { return (EntryType)(data & 0x3); @@ -127,19 +132,48 @@ class TableWalker : public MemObject return mbits(data, 31,10); } - /** Memory region attributes: ARM DDI 0406B: B3-32 */ + /** Memory region attributes: ARM DDI 0406B: B3-32. + * These bits are largly ignored by M5 and only used to + * provide the illusion that the memory system cares about + * anything but cachable vs. uncachable. + */ uint8_t texcb() const { return bits(data, 2) | bits(data,3) << 1 | bits(data, 14, 12) << 2; } + /** If the section is shareable. See texcb() comment. */ + bool shareable() const + { + return bits(data, 16); + } + + /** Set access flag that this entry has been touched. Mark + * the entry as requiring a writeback, in the future. + */ + void setAp0() + { + data |= 1 << 10; + _dirty = true; + } + + /** This entry needs to be written back to memory */ + bool dirty() const + { + return _dirty; + } }; /** Level 2 page table descriptor */ struct L2Descriptor { + /** The raw bits of the entry. */ uint32_t data; + /** This entry has been modified (access flag set) and needs to be + * written back to memory */ + bool _dirty; + /** Is the entry invalid */ bool invalid() const { @@ -184,6 +218,27 @@ class TableWalker : public MemObject return large() ? bits(data, 31, 16) : bits(data, 31, 12); } + /** If the section is shareable. See texcb() comment. */ + bool shareable() const + { + return bits(data, 10); + } + + /** Set access flag that this entry has been touched. Mark + * the entry as requiring a writeback, in the future. + */ + void setAp0() + { + data |= 1 << 4; + _dirty = true; + } + + /** This entry needs to be written back to memory */ + bool dirty() const + { + return _dirty; + } + }; /** Port to issue translation requests from */ @@ -252,9 +307,9 @@ class TableWalker : public MemObject TLB::Translation *_trans, bool timing); void setTlb(TLB *_tlb) { tlb = _tlb; } + void memAttrs(TlbEntry &te, uint8_t texcb, bool s); private: - void memAttrs(TlbEntry &te, uint8_t texcb); void doL1Descriptor(); EventWrapper doL1DescEvent; diff --git a/src/arch/arm/tlb.cc b/src/arch/arm/tlb.cc index 05d65457c..7f02061b0 100644 --- a/src/arch/arm/tlb.cc +++ b/src/arch/arm/tlb.cc @@ -384,6 +384,16 @@ TLB::translateFs(RequestPtr req, ThreadContext *tc, Mode mode, if (nmrr.ir0 == 0 || nmrr.or0 == 0 || prrr.tr0 != 0x2) req->setFlags(Request::UNCACHEABLE); } + + // Set memory attributes + TlbEntry temp_te; + tableWalker->memAttrs(temp_te, 0, 1); + temp_te.shareable = true; + DPRINTF(TLBVerbose, "(No MMU) setting memory attributes: shareable:\ + %d, innerAttrs: %d, outerAttrs: %d\n", temp_te.shareable, + temp_te.innerAttrs, temp_te.outerAttrs); + setAttr(temp_te.attributes); + return trickBoxCheck(req, mode, 0, false); } @@ -409,6 +419,13 @@ TLB::translateFs(RequestPtr req, ThreadContext *tc, Mode mode, assert(te); } + // Set memory attributes + DPRINTF(TLBVerbose, + "Setting memory attributes: shareable: %d, innerAttrs: %d, \ + outerAttrs: %d\n", + te->shareable, te->innerAttrs, te->outerAttrs); + setAttr(te->attributes); + uint32_t dacr = tc->readMiscReg(MISCREG_DACR); switch ( (dacr >> (te->domain * 2)) & 0x3) { case 0: diff --git a/src/arch/arm/tlb.hh b/src/arch/arm/tlb.hh index c2894e5cd..a779a492d 100644 --- a/src/arch/arm/tlb.hh +++ b/src/arch/arm/tlb.hh @@ -88,6 +88,8 @@ class TLB : public BaseTLB int size; // TLB Size int nlu; // not last used entry (for replacement) + uint32_t _attr; // Memory attributes for last accessed TLB entry + #if FULL_SYSTEM TableWalker *tableWalker; #endif @@ -151,6 +153,19 @@ class TLB : public BaseTLB static bool validVirtualAddress(Addr vaddr); + /** Accessor functions for memory attributes for last accessed TLB entry + */ + void + setAttr(uint32_t attr) + { + _attr = attr; + } + uint32_t + getAttr() const + { + return _attr; + } + #if FULL_SYSTEM Fault translateFs(RequestPtr req, ThreadContext *tc, Mode mode, Translation *translation, bool &delay, bool timing);