/********************************************************************* Function : process_doorbell Description : Processing Doorbell and SQ commands Return Type : void Arguments : NVMEState * : Pointer to NVME device State target_phys_addr_t : Address (offset address) uint32_t : Value to be written *********************************************************************/ static void process_doorbell(NVMEState *nvme_dev, target_phys_addr_t addr, uint32_t val) { /* Used to get the SQ/CQ number to be written to */ uint32_t queue_id; int64_t deadline; LOG_DBG("%s(): addr = 0x%08x, val = 0x%08x", __func__, (unsigned)addr, val); /* Check if it is CQ or SQ doorbell */ queue_id = (addr - NVME_SQ0TDBL) / sizeof(uint32_t); if (queue_id % 2) { /* CQ */ uint16_t new_head = val & 0xffff; queue_id = (addr - NVME_CQ0HDBL) / QUEUE_BASE_ADDRESS_WIDTH; if (adm_check_cqid(nvme_dev, queue_id)) { LOG_NORM("Wrong CQ ID: %d", queue_id); enqueue_async_event(nvme_dev, event_type_error, event_info_err_invalid_sq, NVME_LOG_ERROR_INFORMATION); return; } if (new_head >= nvme_dev->cq[queue_id].size) { LOG_NORM("Bad cq head value: %d", new_head); enqueue_async_event(nvme_dev, event_type_error, event_info_err_invalid_db, NVME_LOG_ERROR_INFORMATION); return; } if (is_cq_full(nvme_dev, queue_id)) { /* queue was previously full, schedule submission queue check in case there are commands that couldn't be processed */ nvme_dev->sq_processing_timer_target = qemu_get_clock_ns(vm_clock) + 5000; qemu_mod_timer(nvme_dev->sq_processing_timer, nvme_dev->sq_processing_timer_target); } nvme_dev->cq[queue_id].head = new_head; /* Reset the P bit if head == tail for all Queues on * a specific interrupt vector */ if (nvme_dev->cq[queue_id].irq_enabled && !(nvme_irqcq_empty(nvme_dev, nvme_dev->cq[queue_id].vector))) { /* reset the P bit */ LOG_DBG("Reset P bit for vec:%d", nvme_dev->cq[queue_id].vector); msix_clr_pending(&nvme_dev->dev, nvme_dev->cq[queue_id].vector); } if (nvme_dev->cq[queue_id].tail != nvme_dev->cq[queue_id].head) { /* more completion entries, submit interrupt */ isr_notify(nvme_dev, &nvme_dev->cq[queue_id]); } } else { /* SQ */ uint16_t new_tail = val & 0xffff; queue_id = (addr - NVME_SQ0TDBL) / QUEUE_BASE_ADDRESS_WIDTH; if (adm_check_sqid(nvme_dev, queue_id)) { LOG_NORM("Wrong SQ ID: %d", queue_id); enqueue_async_event(nvme_dev, event_type_error, event_info_err_invalid_sq, NVME_LOG_ERROR_INFORMATION); return; } if (new_tail >= nvme_dev->sq[queue_id].size) { LOG_NORM("Bad sq tail value: %d", new_tail); enqueue_async_event(nvme_dev, event_type_error, event_info_err_invalid_db, NVME_LOG_ERROR_INFORMATION); return; } nvme_dev->sq[queue_id].tail = new_tail; /* Check if the SQ processing routine is scheduled for * execution within 5 uS.If it isn't, make it so */ deadline = qemu_get_clock_ns(vm_clock) + 5000; if (nvme_dev->sq_processing_timer_target == 0) { qemu_mod_timer(nvme_dev->sq_processing_timer, deadline); nvme_dev->sq_processing_timer_target = deadline; } } return; }
void process_sq(NVMEState *n, uint16_t sq_id) { target_phys_addr_t addr; uint16_t cq_id; NVMECmd sqe; NVMECQE cqe; NVMEStatusField *sf = (NVMEStatusField *) &cqe.status; if (n->sq[sq_id].dma_addr == 0 || n->cq[n->sq[sq_id].cq_id].dma_addr == 0) { LOG_ERR("Required Submission/Completion Queue does not exist"); n->sq[sq_id].head = n->sq[sq_id].tail = 0; goto exit; } cq_id = n->sq[sq_id].cq_id; if (is_cq_full(n, cq_id)) { return; } memset(&cqe, 0, sizeof(cqe)); LOG_DBG("%s(): called", __func__); /* Process SQE */ if (sq_id == ASQ_ID || n->sq[sq_id].phys_contig) { addr = n->sq[sq_id].dma_addr + n->sq[sq_id].head * sizeof(sqe); } else { /* PRP implementation */ addr = find_discontig_queue_entry(n->page_size, n->sq[sq_id].head, sizeof(sqe), n->sq[sq_id].dma_addr); } nvme_dma_mem_read(addr, (uint8_t *)&sqe, sizeof(sqe)); if (n->abort) { if (abort_command(n, sq_id, &sqe)) { incr_sq_head(&n->sq[sq_id]); return; } } incr_sq_head(&n->sq[sq_id]); if (sq_id == ASQ_ID) { nvme_admin_command(n, &sqe, &cqe); } else { /* TODO add support for IO commands with different sizes of Q elements */ nvme_io_command(n, &sqe, &cqe); } /* Filling up the CQ entry */ cqe.sq_id = sq_id; cqe.sq_head = n->sq[sq_id].head; cqe.command_id = sqe.cid; sf->p = n->cq[cq_id].phase_tag; sf->m = 0; sf->dnr = 0; /* TODO add support for dnr */ /* write cqe to completion queue */ if (cq_id == ACQ_ID || n->cq[cq_id].phys_contig) { addr = n->cq[cq_id].dma_addr + n->cq[cq_id].tail * sizeof(cqe); } else { /* PRP implementation */ addr = find_discontig_queue_entry(n->page_size, n->cq[cq_id].tail, sizeof(cqe), n->cq[cq_id].dma_addr); } nvme_dma_mem_write(addr, (uint8_t *)&cqe, sizeof(cqe)); incr_cq_tail(&n->cq[cq_id]); if (cq_id == ACQ_ID) { /* 3.1.9 says: "This queue is always associated with interrupt vector 0" */ msix_notify(&(n->dev), 0); return; } if (n->cq[cq_id].irq_enabled) { msix_notify(&(n->dev), n->cq[cq_id].vector); } else { LOG_NORM("kw q: IRQ not enabled for CQ: %d", cq_id); } exit: return; }