SimpleSSD / SimpleSSD-FullSystem

Open-Source Licensed Educational SSD Simulator for High-Performance Storage and Full-System Evaluations
BSD 3-Clause "New" or "Revised" License
88 stars 46 forks source link

How to add a new nvme command? #23

Closed CkTD closed 3 years ago

CkTD commented 3 years ago

Explain what you want to ask here:

Hi, I'm trying to add a new nvme IO command. I hope the data transfer direction of that command to be bidirectional. The controller will first read the data pointed by the Data Pointer in the command and update it with some new value.

Following the implementation of Namespace::read and Namespace::write, I add a new handler Namespace::myCommand. Here is my simplified code

void Namespace::myCommand(SQEntryWrapper &req, RequestFunction &func) {
  bool err = false;

  CQEntryWrapper resp(req);
  int bufflen =  ...... ; 

  if (!attached) {
    err = true;
    resp.makeStatus(true, false, TYPE_COMMAND_SPECIFIC_STATUS,
                    STATUS_NAMESPACE_NOT_ATTACHED);
  }

  if (!err) {
    DMAFunction doMyCmd = [this](uint64_t tick, void *context) {
      DMAFunction update = [this](uint64_t tick, void *context) {
        DMAFunction dmaDone = [this](uint64_t tick, void *context) {
          IOContext *pContext = (IOContext *)context;
          pContext->function(pContext->resp);
          if (pContext->buffer) {
              free(pContext->buffer);
          }
          delete pContext->dma;
          delete pContext;
        };

        IOContext *pContext = (IOContext *)context;
        // update data in pContext->buffer 
        pContext->dma->write(0, bufflen, pContext->buffer, dmaDone, context);
      };

      IOContext *pContext = (IOContext *)context;
      pContext->buffer = (uint8_t *)calloc(bufflen, 1);
      pContext->dma->read(0, bufflen, pContext->buffer, update, context);                          
    };

    IOContext *pContext = new IOContext(func, resp);
    CPUContext *pCPU = new CPUContext(doMyCmd, pContext, CPU::NVME__NAMESPACE, CPU::READ);

    if (req.useSGL) {
      pContext->dma =
          new SGL(cfgdata, cpuHandler, pCPU, req.entry.data1, req.entry.data2);
    }
    else {
      pContext->dma =
          new PRPList(cfgdata, cpuHandler, pCPU, req.entry.data1,
                      req.entry.data2, bufflen);
    }
  }
  else {
    func(resp);
  }
}

On the host side, I use ioctl to issue this nvme command. The controller can read the data via dma, but seems that the dma write doesn't work. What is the right way to transfer data bidirectional? Thanks

kukdh1 commented 3 years ago

Hi,

I tested you code (modified to below) and it works well (read data from host, modify in SSD, write to host again).

#define BUFFER_SIZE 4096

void Namespace::myCommand(SQEntryWrapper &req, RequestFunction &func) {
  bool err = false;

  CQEntryWrapper resp(req);

  if (!attached) {
    err = true;
    resp.makeStatus(true, false, TYPE_COMMAND_SPECIFIC_STATUS,
                    STATUS_NAMESPACE_NOT_ATTACHED);
  }

  if (!err) {
    DMAFunction doMyCmd = [this](uint64_t, void *context) {
      DMAFunction update = [this](uint64_t, void *context) {
        DMAFunction dmaDone = [this](uint64_t, void *context) {
          IOContext *pContext = (IOContext *)context;
          pContext->function(pContext->resp);
          if (pContext->buffer) {
            free(pContext->buffer);
          }
          delete pContext->dma;
          delete pContext;
        };

        IOContext *pContext = (IOContext *)context;

        // Print data
        printf("======== H2D DMA RESULT ========\n");
        for (uint64_t i = 0; i < BUFFER_SIZE; i += 32) {
          for (uint64_t j = 0; j < 32; j++) {
            printf("%02x ", pContext->buffer[i + j]);
          }
          printf("\n");
        }
        printf("======== H2D DMA RESULT ========\n");

        // update data in pContext->buffer
        memset(pContext->buffer, 0x80, BUFFER_SIZE);

        pContext->dma->write(0, BUFFER_SIZE, pContext->buffer, dmaDone,
                             context);
      };

      IOContext *pContext = (IOContext *)context;
      pContext->buffer = (uint8_t *)calloc(BUFFER_SIZE, 1);
      pContext->dma->read(0, BUFFER_SIZE, pContext->buffer, update, context);
    };

    IOContext *pContext = new IOContext(func, resp);
    CPUContext *pCPU =
        new CPUContext(doMyCmd, pContext, CPU::NVME__NAMESPACE, CPU::READ);

    if (req.useSGL) {
      pContext->dma =
          new SGL(cfgdata, cpuHandler, pCPU, req.entry.data1, req.entry.data2);
    }
    else {
      pContext->dma = new PRPList(cfgdata, cpuHandler, pCPU, req.entry.data1,
                                  req.entry.data2, (uint64_t)BUFFER_SIZE);
    }
  }
  else {
    func(resp);
  }
}

I used following NVMe command: nvme io-passthru -o 0x90 -p 3 -n 1 -l 4096 -r -s /dev/nvme0n1

The buffer is prefilled by memset(buffer, 0x03, 4096) by nvme-cli, and gem5 shows buffer is read correctly:

1078129331000: HIL::NVMe: SQ 2    | Submission Queue Tail Doorbell | Item count in queue 0 -> 1 | head 2 | tail 2 -> 3
======== H2D DMA RESULT ========
03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03
...
03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03
======== H2D DMA RESULT ========
1078152096369: HIL::NVMe: INTR    | MSI-X sent | vector 1
1078152950500: HIL::NVMe: CQ 2    | Completion Queue Head Doorbell | Item count in queue 1 -> 0 | head 2 -> 3 | tail 3

After command completion, nvme-cli reported that buffer is modified correctly:

root@genericarmv8:~# ./nvme io-passthru -o 0x90 -p 3 -n 1 -l 4096 -r -s /dev/nvme0n1
opcode       : 90
flags        : 00
rsvd1        : 0000
nsid         : 00000001
cdw2         : 00000000
cdw3         : 00000000
data_len     : 00001000
metadata_len : 00000000
addr         : 443000
metadata     : 0
cdw10        : 00000000
cdw11        : 00000000
cdw12        : 00000000
cdw13        : 00000000
cdw14        : 00000000
cdw15        : 00000000
timeout_ms   : 00000000
NVMe command result:00000000
       0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
0000: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 "................"
0010: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 "................"
...
0ff0: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 "................"
root@genericarmv8:~#

I tested this on ARM architecture with AtomicSimpleCPU. If you still have same problem, please let me know.

Thanks.