| /* |
| * Copyright (c) 2013-2015 ARM Limited |
| * All rights reserved |
| * |
| * The license below extends only to copyright in the software and shall |
| * not be construed as granting a license to any other intellectual |
| * property including but not limited to intellectual property relating |
| * to a hardware implementation of the functionality of the software |
| * licensed hereunder. You may use the software subject to the license |
| * terms below provided that you ensure that this notice is replicated |
| * unmodified and in its entirety in all distributions of the software, |
| * modified or unmodified, in source code or in binary form. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer; |
| * redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution; |
| * neither the name of the copyright holders nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| * Authors: Rene de Jong |
| */ |
| |
| /** @file |
| * This simplistic flash model is designed to model managed SLC NAND flash. |
| * This device will need an interface module (such as NVMe or UFS); Note that |
| * this model only calculates the delay and does not perform the actual |
| * transaction. |
| * |
| * To access the memory, use either readMemory or writeMemory. This will |
| * schedule an event at the tick where the action will finish. If a callback |
| * has been given as argument then that function will be called on completion |
| * of that event. Note that this does not guarantee that there are no other |
| * actions pending in the flash device. |
| * |
| * IMPORTANT: number of planes should be a power of 2. |
| */ |
| |
| #include "dev/arm/flash_device.hh" |
| |
| #include "base/trace.hh" |
| #include "debug/Drain.hh" |
| |
| /** |
| * Create this device |
| */ |
| |
| FlashDevice* |
| FlashDeviceParams::create() |
| { |
| return new FlashDevice(this); |
| } |
| |
| |
| /** |
| * Flash Device constructor and destructor |
| */ |
| |
| FlashDevice::FlashDevice(const FlashDeviceParams* p): |
| AbstractNVM(p), |
| diskSize(0), |
| blockSize(p->blk_size), |
| pageSize(p->page_size), |
| GCActivePercentage(p->GC_active), |
| readLatency(p->read_lat), |
| writeLatency(p->write_lat), |
| eraseLatency(p->erase_lat), |
| dataDistribution(p->data_distribution), |
| numPlanes(p->num_planes), |
| pagesPerBlock(0), |
| pagesPerDisk(0), |
| blocksPerDisk(0), |
| planeMask(numPlanes - 1), |
| planeEventQueue(numPlanes), |
| planeEvent([this]{ actionComplete(); }, name()) |
| { |
| |
| /* |
| * Let 'a' be a power of two of n bits, written such that a-n is the msb |
| * and a-0 is the lsb. Since it is a power of two, only one bit (a-x, |
| * with 0 <= x <= n) is set. If we subtract one from this number the bits |
| * a-(x-1) to a-0 are set and all the other bits are cleared. Hence a |
| * bitwise AND with those two numbers results in an integer with all bits |
| * cleared. |
| */ |
| if (numPlanes & planeMask) |
| fatal("Number of planes is not a power of 2 in flash device.\n"); |
| } |
| |
| /** |
| * Initiates all the flash functions: initializes the lookup tables, age of |
| * the device, etc. This can only be done once the disk image is known. |
| * Thats why it can't be done in the constructor. |
| */ |
| void |
| FlashDevice::initializeFlash(uint64_t disk_size, uint32_t sector_size) |
| { |
| diskSize = disk_size * sector_size; |
| pagesPerBlock = blockSize / pageSize; |
| pagesPerDisk = diskSize / pageSize; |
| blocksPerDisk = diskSize / blockSize; |
| |
| /** Sanity information: check flash configuration */ |
| DPRINTF(FlashDevice, "diskSize: %d Bytes; %d pages per block, %d pages " |
| "per disk\n", diskSize, pagesPerBlock, pagesPerDisk); |
| |
| locationTable.resize(pagesPerDisk); |
| |
| /**Garbage collection related*/ |
| blockValidEntries.resize(blocksPerDisk, 0); |
| blockEmptyEntries.resize(blocksPerDisk, pagesPerBlock); |
| |
| /** |
| * This is a bitmap. Every bit is a page |
| * unknownPages is a vector of 32 bit integers. If every page was an |
| * integer, the total size would be pagesPerDisk; since we can map one |
| * page per bit we need ceil(pagesPerDisk/32) entries. 32 = 1 << 5 hence |
| * it will do to just shift pagesPerDisk five positions and add one. This |
| * will allocate one integer to many for this data structure in the worst |
| * case. |
| */ |
| unknownPages.resize((pagesPerDisk >> 5) + 1, 0xFFFFFFFF); |
| |
| for (uint32_t count = 0; count < pagesPerDisk; count++) { |
| //setup lookup table + physical aspects |
| |
| if (dataDistribution == Enums::stripe) { |
| locationTable[count].page = count / blocksPerDisk; |
| locationTable[count].block = count % blocksPerDisk; |
| |
| } else { |
| locationTable[count].page = count % pagesPerBlock; |
| locationTable[count].block = count / pagesPerBlock; |
| } |
| } |
| } |
| |
| FlashDevice::~FlashDevice() |
| { |
| DPRINTF(FlashDevice, "Remove FlashDevice\n"); |
| } |
| |
| /** |
| * Handles the accesses to the device. |
| * The function determines when certain actions are scheduled and schedules |
| * an event that uses the callback function on completion of the action. |
| */ |
| void |
| FlashDevice::accessDevice(uint64_t address, uint32_t amount, Callback *event, |
| Actions action) |
| { |
| DPRINTF(FlashDevice, "Flash calculation for %d bytes in %d pages\n" |
| , amount, pageSize); |
| |
| std::vector<Tick> time(numPlanes, 0); |
| uint64_t logic_page_addr = address / pageSize; |
| uint32_t plane_address = 0; |
| |
| /** |
| * The access will be broken up in a number of page accesses. The number |
| * of page accesses depends on the amount that needs to be transfered. |
| * The assumption here is that the interface is completely ignorant of |
| * the page size and that this model has to figure out all of the |
| * transaction characteristics. |
| */ |
| for (uint32_t count = 0; amount > (count * pageSize); count++) { |
| uint32_t index = (locationTable[logic_page_addr].block * |
| pagesPerBlock) + (logic_page_addr % pagesPerBlock); |
| |
| DPRINTF(FlashDevice, "Index 0x%8x, Block 0x%8x, pages/block %d," |
| " logic address 0x%8x\n", index, |
| locationTable[logic_page_addr].block, pagesPerBlock, |
| logic_page_addr); |
| DPRINTF(FlashDevice, "Page %d; %d bytes up to this point\n", count, |
| (count * pageSize)); |
| |
| plane_address = locationTable[logic_page_addr].block & planeMask; |
| |
| if (action == ActionRead) { |
| //lookup |
| //call accessTimes |
| time[plane_address] += accessTimes(locationTable[logic_page_addr] |
| .block, ActionRead); |
| |
| /*stats*/ |
| stats.readAccess.sample(logic_page_addr); |
| stats.readLatency.sample(time[plane_address]); |
| } else { //write |
| //lookup |
| //call accessTimes if appropriate, page may be unknown, so lets |
| //give it the benefit of the doubt |
| |
| if (getUnknownPages(index)) |
| time[plane_address] += accessTimes |
| (locationTable[logic_page_addr].block, ActionWrite); |
| |
| else //A remap is needed |
| time[plane_address] += remap(logic_page_addr); |
| |
| /*stats*/ |
| stats.writeAccess.sample(logic_page_addr); |
| stats.writeLatency.sample(time[plane_address]); |
| } |
| |
| /** |
| * Check if the page is known and used. unknownPages is a bitmap of |
| * all the pages. It tracks wether we can be sure that the |
| * information of this page is taken into acount in the model (is it |
| * considered in blockValidEntries and blockEmptyEntries?). If it has |
| * been used in the past, then it is known. |
| */ |
| if (getUnknownPages(index)) { |
| clearUnknownPages(index); |
| --blockEmptyEntries[locationTable[logic_page_addr].block]; |
| ++blockValidEntries[locationTable[logic_page_addr].block]; |
| } |
| |
| stats.fileSystemAccess.sample(address); |
| ++logic_page_addr; |
| } |
| |
| /** |
| * previous part of the function found the times spend in different |
| * planes, now lets find the maximum to know when to callback the disk |
| */ |
| for (uint32_t count = 0; count < numPlanes; count++){ |
| plane_address = (time[plane_address] > time[count]) ? plane_address |
| : count; |
| |
| DPRINTF(FlashDevice, "Plane %d is busy for %d ticks\n", count, |
| time[count]); |
| |
| if (time[count] != 0) { |
| |
| struct CallBackEntry cbe; |
| /** |
| * If there are no events for this plane, then add the current |
| * time to the occupation time; otherwise, plan it after the |
| * last event. If by chance that event is handled in this tick, |
| * then we would still end up with the same result. |
| */ |
| if (planeEventQueue[count].empty()) |
| cbe.time = time[count] + curTick(); |
| else |
| cbe.time = time[count] + |
| planeEventQueue[count].back().time; |
| cbe.function = NULL; |
| planeEventQueue[count].push_back(cbe); |
| |
| DPRINTF(FlashDevice, "scheduled at: %ld\n", cbe.time); |
| |
| if (!planeEvent.scheduled()) |
| schedule(planeEvent, planeEventQueue[count].back().time); |
| else if (planeEventQueue[count].back().time < planeEvent.when()) |
| reschedule(planeEvent, |
| planeEventQueue[plane_address].back().time, true); |
| } |
| } |
| |
| //worst case two plane finish at the same time, each triggers an event |
| //and this callback will be called once. Maybe before the other plane |
| //could execute its event, but in the same tick. |
| planeEventQueue[plane_address].back().function = event; |
| DPRINTF(FlashDevice, "Callback queued for plane %d; %d in queue\n", |
| plane_address, planeEventQueue[plane_address].size()); |
| DPRINTF(FlashDevice, "first event @ %d\n", planeEvent.when()); |
| } |
| |
| /** |
| * When a plane completes its action, this event is triggered. When a |
| * callback function was associated with that event, it will be called. |
| */ |
| |
| void |
| FlashDevice::actionComplete() |
| { |
| DPRINTF(FlashDevice, "Plane action completed\n"); |
| uint8_t plane_address = 0; |
| |
| uint8_t next_event = 0; |
| |
| /**Search for a callback that is supposed to happen in this Tick*/ |
| for (plane_address = 0; plane_address < numPlanes; plane_address++) { |
| if (!planeEventQueue[plane_address].empty()) { |
| /** |
| * Invariant: All queued events are scheduled in the present |
| * or future. |
| */ |
| assert(planeEventQueue[plane_address].front().time >= curTick()); |
| |
| if (planeEventQueue[plane_address].front().time == curTick()) { |
| /** |
| * To ensure that the follow-up action is executed correctly, |
| * the callback entry first need to be cleared before it can |
| * be called. |
| */ |
| Callback *temp = planeEventQueue[plane_address].front(). |
| function; |
| planeEventQueue[plane_address].pop_front(); |
| |
| /**Found a callback, lets make it happen*/ |
| if (temp != NULL) { |
| DPRINTF(FlashDevice, "Callback, %d\n", plane_address); |
| temp->process(); |
| } |
| } |
| } |
| } |
| |
| /** Find when to schedule the planeEvent next */ |
| for (plane_address = 0; plane_address < numPlanes; plane_address++) { |
| if (!planeEventQueue[plane_address].empty()) |
| if (planeEventQueue[next_event].empty() || |
| (planeEventQueue[plane_address].front().time < |
| planeEventQueue[next_event].front().time)) |
| next_event = plane_address; |
| } |
| |
| /**Schedule the next plane that will be ready (if any)*/ |
| if (!planeEventQueue[next_event].empty()) { |
| DPRINTF(FlashDevice, "Schedule plane: %d\n", plane_address); |
| reschedule(planeEvent, planeEventQueue[next_event].front().time, true); |
| } |
| |
| checkDrain(); |
| |
| DPRINTF(FlashDevice, "returing from flash event\n"); |
| DPRINTF(FlashDevice, "first event @ %d\n", planeEvent.when()); |
| } |
| |
| /** |
| * Handles the remapping of the pages. It is a (I hope) sensible statistic |
| * approach. asumption: garbage collection happens when a clean is needed |
| * (may become stochastic function). |
| */ |
| Tick |
| FlashDevice::remap(uint64_t logic_page_addr) |
| { |
| /** |
| * Are there any empty left in this Block, or do we need to do an erase |
| */ |
| if (blockEmptyEntries[locationTable[logic_page_addr].block] > 0) { |
| //just a remap |
| //update tables |
| --blockEmptyEntries[locationTable[logic_page_addr].block]; |
| //access to this table won't be sequential anymore |
| locationTable[logic_page_addr].page = pagesPerBlock + 2; |
| //access new block |
| Tick time = accessTimes(locationTable[logic_page_addr].block, |
| ActionWrite); |
| |
| DPRINTF(FlashDevice, "Remap returns %d ticks\n", time); |
| return time; |
| |
| } else { |
| //calculate how much time GC would have taken |
| uint32_t block = locationTable[logic_page_addr].block; |
| Tick time = ((GCActivePercentage * |
| (accessTimes(block, ActionCopy) + |
| accessTimes(block, ActionErase))) |
| / 100); |
| |
| //use block as the logical start address of the block |
| block = locationTable[logic_page_addr].block * pagesPerBlock; |
| |
| //assumption: clean will improve locality |
| for (uint32_t count = 0; count < pagesPerBlock; count++) { |
| assert(block + count < pagesPerDisk); |
| locationTable[block + count].page = (block + count) % |
| pagesPerBlock; |
| } |
| |
| blockEmptyEntries[locationTable[logic_page_addr].block] = |
| pagesPerBlock; |
| /*stats*/ |
| ++stats.totalGCActivations; |
| |
| DPRINTF(FlashDevice, "Remap with erase action returns %d ticks\n", |
| time); |
| |
| return time; |
| } |
| |
| } |
| |
| /** |
| * Calculates the accesstime per operation needed |
| */ |
| Tick |
| FlashDevice::accessTimes(uint64_t block, Actions action) |
| { |
| Tick time = 0; |
| |
| switch(action) { |
| case ActionRead: { |
| /**Just read the page*/ |
| time = readLatency; |
| } break; |
| |
| case ActionWrite: { |
| /**Write the page, and read the result*/ |
| time = writeLatency + readLatency; |
| } break; |
| |
| case ActionErase: { |
| /**Erase and check wether it was successfull*/ |
| time = eraseLatency + readLatency; |
| } break; |
| |
| case ActionCopy: { |
| /**Copy every valid page*/ |
| uint32_t validpages = blockValidEntries[block]; |
| time = validpages * (readLatency + writeLatency); |
| } break; |
| |
| default: break; |
| } |
| |
| //Used to determine sequential action. |
| DPRINTF(FlashDevice, "Access returns %d ticks\n", time); |
| return time; |
| } |
| |
| /** |
| * clearUnknownPages. defines that a page is known and used |
| * unknownPages is a bitmap of all the pages. It tracks wether we can be sure |
| * that the information of this page is taken into acount in the model (is it |
| * considered in blockValidEntries and blockEmptyEntries?). If it has been |
| * used in the past, then it is known. But it needs to be tracked to make |
| * decisions about write accesses, and indirectly about copy actions. one |
| * unknownPage entry is a 32 bit integer. So if we have a page index, then |
| * that means that we need entry floor(index/32) (index >> 5) and we need to |
| * select the bit which number is equal to the remainder of index/32 |
| * (index%32). The bit is cleared to make sure that we see it as considered |
| * in the future. |
| */ |
| |
| inline |
| void |
| FlashDevice::clearUnknownPages(uint32_t index) |
| { |
| unknownPages[index >> 5] &= ~(0x01 << (index % 32)); |
| } |
| |
| /** |
| * getUnknownPages. Verify wether a page is known |
| */ |
| |
| inline |
| bool |
| FlashDevice::getUnknownPages(uint32_t index) |
| { |
| return unknownPages[index >> 5] & (0x01 << (index % 32)); |
| } |
| |
| void |
| FlashDevice::regStats() |
| { |
| AbstractNVM::regStats(); |
| |
| using namespace Stats; |
| |
| std::string fd_name = name() + ".FlashDevice"; |
| |
| // Register the stats |
| /** Amount of GC activations*/ |
| stats.totalGCActivations |
| .name(fd_name + ".totalGCActivations") |
| .desc("Number of Garbage collector activations") |
| .flags(none); |
| |
| /** Histogram of address accesses*/ |
| stats.writeAccess |
| .init(2) |
| .name(fd_name + ".writeAccessHist") |
| .desc("Histogram of write addresses") |
| .flags(pdf); |
| stats.readAccess |
| .init(2) |
| .name(fd_name + ".readAccessHist") |
| .desc("Histogram of read addresses") |
| .flags(pdf); |
| stats.fileSystemAccess |
| .init(100) |
| .name(fd_name + ".fileSystemAccessHist") |
| .desc("Histogram of file system accesses") |
| .flags(pdf); |
| |
| /** Histogram of access latencies*/ |
| stats.writeLatency |
| .init(100) |
| .name(fd_name + ".writeLatencyHist") |
| .desc("Histogram of write latency") |
| .flags(pdf); |
| stats.readLatency |
| .init(100) |
| .name(fd_name + ".readLatencyHist") |
| .desc("Histogram of read latency") |
| .flags(pdf); |
| } |
| |
| /** |
| * Serialize; needed to create checkpoints |
| */ |
| |
| void |
| FlashDevice::serialize(CheckpointOut &cp) const |
| { |
| SERIALIZE_SCALAR(planeMask); |
| |
| SERIALIZE_CONTAINER(unknownPages); |
| SERIALIZE_CONTAINER(blockValidEntries); |
| SERIALIZE_CONTAINER(blockEmptyEntries); |
| |
| int location_table_size = locationTable.size(); |
| SERIALIZE_SCALAR(location_table_size); |
| for (uint32_t count = 0; count < location_table_size; count++) { |
| paramOut(cp, csprintf("locationTable[%d].page", count), |
| locationTable[count].page); |
| paramOut(cp, csprintf("locationTable[%d].block", count), |
| locationTable[count].block); |
| } |
| }; |
| |
| /** |
| * Unserialize; needed to restore from checkpoints |
| */ |
| |
| void |
| FlashDevice::unserialize(CheckpointIn &cp) |
| { |
| UNSERIALIZE_SCALAR(planeMask); |
| |
| UNSERIALIZE_CONTAINER(unknownPages); |
| UNSERIALIZE_CONTAINER(blockValidEntries); |
| UNSERIALIZE_CONTAINER(blockEmptyEntries); |
| |
| int location_table_size; |
| UNSERIALIZE_SCALAR(location_table_size); |
| locationTable.resize(location_table_size); |
| for (uint32_t count = 0; count < location_table_size; count++) { |
| paramIn(cp, csprintf("locationTable[%d].page", count), |
| locationTable[count].page); |
| paramIn(cp, csprintf("locationTable[%d].block", count), |
| locationTable[count].block); |
| } |
| }; |
| |
| /** |
| * Drain; needed to enable checkpoints |
| */ |
| |
| DrainState |
| FlashDevice::drain() |
| { |
| if (planeEvent.scheduled()) { |
| DPRINTF(Drain, "Flash device is draining...\n"); |
| return DrainState::Draining; |
| } else { |
| DPRINTF(Drain, "Flash device in drained state\n"); |
| return DrainState::Drained; |
| } |
| } |
| |
| /** |
| * Checkdrain; needed to enable checkpoints |
| */ |
| |
| void |
| FlashDevice::checkDrain() |
| { |
| if (drainState() != DrainState::Draining) |
| return; |
| |
| if (planeEvent.when() > curTick()) { |
| DPRINTF(Drain, "Flash device is still draining\n"); |
| } else { |
| DPRINTF(Drain, "Flash device is done draining\n"); |
| signalDrainDone(); |
| } |
| } |