/** @file Minimal block driver for Mini-OS. Copyright (c) 2007-2008 Samuel Thibault. Copyright (C) 2014, Citrix Ltd. Copyright (c) 2014, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include "BlockFront.h" #include #include /** Helper to read an integer from XenStore. If the number overflows according to the range defined by UINT64, then ASSERT(). @param This A pointer to a XENBUS_PROTOCOL instance. @param Node The XenStore node to read from. @param FromBackend Read frontend or backend value. @param ValuePtr Where to put the value. @retval XENSTORE_STATUS_SUCCESS If successful, will update ValuePtr. @return Any other return value indicate the error, ValuePtr is not updated in this case. **/ STATIC XENSTORE_STATUS XenBusReadUint64 ( IN XENBUS_PROTOCOL *This, IN CONST CHAR8 *Node, IN BOOLEAN FromBackend, OUT UINT64 *ValuePtr ) { XENSTORE_STATUS Status; CHAR8 *Ptr; if (!FromBackend) { Status = This->XsRead (This, XST_NIL, Node, (VOID**)&Ptr); } else { Status = This->XsBackendRead (This, XST_NIL, Node, (VOID**)&Ptr); } if (Status != XENSTORE_STATUS_SUCCESS) { return Status; } // AsciiStrDecimalToUint64 will ASSERT if Ptr overflow UINT64. *ValuePtr = AsciiStrDecimalToUint64 (Ptr); FreePool (Ptr); return Status; } /** Free an instance of XEN_BLOCK_FRONT_DEVICE. @param Dev The instance to free. **/ STATIC VOID XenPvBlockFree ( IN XEN_BLOCK_FRONT_DEVICE *Dev ) { XENBUS_PROTOCOL *XenBusIo = Dev->XenBusIo; if (Dev->RingRef != 0) { XenBusIo->GrantEndAccess (XenBusIo, Dev->RingRef); } if (Dev->Ring.sring != NULL) { FreePages (Dev->Ring.sring, 1); } if (Dev->EventChannel != 0) { XenBusIo->EventChannelClose (XenBusIo, Dev->EventChannel); } FreePool (Dev); } /** Wait until until the backend has reached the ExpectedState. @param Dev A XEN_BLOCK_FRONT_DEVICE instance. @param ExpectedState The backend state expected. @param LastStatePtr An optional pointer where to right the final state. @return Return XENSTORE_STATUS_SUCCESS if the new backend state is ExpectedState or return an error otherwise. **/ STATIC XENSTORE_STATUS XenPvBlkWaitForBackendState ( IN XEN_BLOCK_FRONT_DEVICE *Dev, IN XenbusState ExpectedState, OUT XenbusState *LastStatePtr OPTIONAL ) { XENBUS_PROTOCOL *XenBusIo = Dev->XenBusIo; XenbusState State; UINT64 Value; XENSTORE_STATUS Status = XENSTORE_STATUS_SUCCESS; while (TRUE) { Status = XenBusReadUint64 (XenBusIo, "state", TRUE, &Value); if (Status != XENSTORE_STATUS_SUCCESS) { return Status; } if (Value > XenbusStateReconfigured) { // // Value is not a State value. // return XENSTORE_STATUS_EIO; } State = Value; if (State == ExpectedState) { break; } else if (State > ExpectedState) { Status = XENSTORE_STATUS_FAIL; break; } DEBUG ((DEBUG_INFO, "XenPvBlk: waiting backend state %d, current: %d\n", ExpectedState, State)); XenBusIo->WaitForWatch (XenBusIo, Dev->StateWatchToken); } if (LastStatePtr != NULL) { *LastStatePtr = State; } return Status; } EFI_STATUS XenPvBlockFrontInitialization ( IN XENBUS_PROTOCOL *XenBusIo, IN CONST CHAR8 *NodeName, OUT XEN_BLOCK_FRONT_DEVICE **DevPtr ) { XENSTORE_TRANSACTION Transaction; CHAR8 *DeviceType; blkif_sring_t *SharedRing; XENSTORE_STATUS Status; XEN_BLOCK_FRONT_DEVICE *Dev; XenbusState State; UINT64 Value; CHAR8 *Params; ASSERT (NodeName != NULL); Dev = AllocateZeroPool (sizeof (XEN_BLOCK_FRONT_DEVICE)); if (Dev == NULL) { return EFI_OUT_OF_RESOURCES; } Dev->Signature = XEN_BLOCK_FRONT_SIGNATURE; Dev->NodeName = NodeName; Dev->XenBusIo = XenBusIo; Dev->DeviceId = XenBusIo->DeviceId; XenBusIo->XsRead (XenBusIo, XST_NIL, "device-type", (VOID**)&DeviceType); if (AsciiStrCmp (DeviceType, "cdrom") == 0) { Dev->MediaInfo.CdRom = TRUE; } else { Dev->MediaInfo.CdRom = FALSE; } FreePool (DeviceType); if (Dev->MediaInfo.CdRom) { Status = XenBusIo->XsBackendRead (XenBusIo, XST_NIL, "params", (VOID**)&Params); if (Status != XENSTORE_STATUS_SUCCESS) { DEBUG ((DEBUG_ERROR, "%a: Failed to read params (%d)\n", __FUNCTION__, Status)); goto Error; } if (AsciiStrLen (Params) == 0 || AsciiStrCmp (Params, "aio:") == 0) { FreePool (Params); DEBUG ((DEBUG_INFO, "%a: Empty cdrom\n", __FUNCTION__)); goto Error; } FreePool (Params); } Status = XenBusReadUint64 (XenBusIo, "backend-id", FALSE, &Value); if (Status != XENSTORE_STATUS_SUCCESS || Value > MAX_UINT16) { DEBUG ((DEBUG_ERROR, "XenPvBlk: Failed to get backend-id (%d)\n", Status)); goto Error; } Dev->DomainId = (domid_t)Value; XenBusIo->EventChannelAllocate (XenBusIo, Dev->DomainId, &Dev->EventChannel); SharedRing = (blkif_sring_t*) AllocatePages (1); SHARED_RING_INIT (SharedRing); FRONT_RING_INIT (&Dev->Ring, SharedRing, EFI_PAGE_SIZE); XenBusIo->GrantAccess (XenBusIo, Dev->DomainId, (INTN) SharedRing >> EFI_PAGE_SHIFT, FALSE, &Dev->RingRef); Again: Status = XenBusIo->XsTransactionStart (XenBusIo, &Transaction); if (Status != XENSTORE_STATUS_SUCCESS) { DEBUG ((DEBUG_WARN, "XenPvBlk: Failed to start transaction, %d\n", Status)); goto Error; } Status = XenBusIo->XsPrintf (XenBusIo, &Transaction, NodeName, "ring-ref", "%d", Dev->RingRef); if (Status != XENSTORE_STATUS_SUCCESS) { DEBUG ((DEBUG_ERROR, "XenPvBlk: Failed to write ring-ref.\n")); goto AbortTransaction; } Status = XenBusIo->XsPrintf (XenBusIo, &Transaction, NodeName, "event-channel", "%d", Dev->EventChannel); if (Status != XENSTORE_STATUS_SUCCESS) { DEBUG ((DEBUG_ERROR, "XenPvBlk: Failed to write event-channel.\n")); goto AbortTransaction; } Status = XenBusIo->XsPrintf (XenBusIo, &Transaction, NodeName, "protocol", "%a", XEN_IO_PROTO_ABI_NATIVE); if (Status != XENSTORE_STATUS_SUCCESS) { DEBUG ((DEBUG_ERROR, "XenPvBlk: Failed to write protocol.\n")); goto AbortTransaction; } Status = XenBusIo->SetState (XenBusIo, &Transaction, XenbusStateConnected); if (Status != XENSTORE_STATUS_SUCCESS) { DEBUG ((DEBUG_ERROR, "XenPvBlk: Failed to switch state.\n")); goto AbortTransaction; } Status = XenBusIo->XsTransactionEnd (XenBusIo, &Transaction, FALSE); if (Status == XENSTORE_STATUS_EAGAIN) { goto Again; } XenBusIo->RegisterWatchBackend (XenBusIo, "state", &Dev->StateWatchToken); // // Waiting for backend // Status = XenPvBlkWaitForBackendState (Dev, XenbusStateConnected, &State); if (Status != XENSTORE_STATUS_SUCCESS) { DEBUG ((DEBUG_ERROR, "XenPvBlk: backend for %a/%d not available, rc=%d state=%d\n", XenBusIo->Type, XenBusIo->DeviceId, Status, State)); goto Error2; } Status = XenBusReadUint64 (XenBusIo, "info", TRUE, &Value); if (Status != XENSTORE_STATUS_SUCCESS || Value > MAX_UINT32) { goto Error2; } Dev->MediaInfo.VDiskInfo = (UINT32)Value; if (Dev->MediaInfo.VDiskInfo & VDISK_READONLY) { Dev->MediaInfo.ReadWrite = FALSE; } else { Dev->MediaInfo.ReadWrite = TRUE; } Status = XenBusReadUint64 (XenBusIo, "sectors", TRUE, &Dev->MediaInfo.Sectors); if (Status != XENSTORE_STATUS_SUCCESS) { goto Error2; } Status = XenBusReadUint64 (XenBusIo, "sector-size", TRUE, &Value); if (Status != XENSTORE_STATUS_SUCCESS || Value > MAX_UINT32) { goto Error2; } if ((UINT32)Value % 512 != 0) { // // This is not supported by the driver. // DEBUG ((DEBUG_ERROR, "XenPvBlk: Unsupported sector-size value %Lu, " "it must be a multiple of 512\n", Value)); goto Error2; } Dev->MediaInfo.SectorSize = (UINT32)Value; // Default value Value = 0; XenBusReadUint64 (XenBusIo, "feature-barrier", TRUE, &Value); if (Value == 1) { Dev->MediaInfo.FeatureBarrier = TRUE; } else { Dev->MediaInfo.FeatureBarrier = FALSE; } // Default value Value = 0; XenBusReadUint64 (XenBusIo, "feature-flush-cache", TRUE, &Value); if (Value == 1) { Dev->MediaInfo.FeatureFlushCache = TRUE; } else { Dev->MediaInfo.FeatureFlushCache = FALSE; } DEBUG ((DEBUG_INFO, "XenPvBlk: New disk with %ld sectors of %d bytes\n", Dev->MediaInfo.Sectors, Dev->MediaInfo.SectorSize)); *DevPtr = Dev; return EFI_SUCCESS; Error2: XenBusIo->UnregisterWatch (XenBusIo, Dev->StateWatchToken); XenBusIo->XsRemove (XenBusIo, XST_NIL, "ring-ref"); XenBusIo->XsRemove (XenBusIo, XST_NIL, "event-channel"); XenBusIo->XsRemove (XenBusIo, XST_NIL, "protocol"); goto Error; AbortTransaction: XenBusIo->XsTransactionEnd (XenBusIo, &Transaction, TRUE); Error: XenPvBlockFree (Dev); return EFI_DEVICE_ERROR; } VOID XenPvBlockFrontShutdown ( IN XEN_BLOCK_FRONT_DEVICE *Dev ) { XENBUS_PROTOCOL *XenBusIo = Dev->XenBusIo; XENSTORE_STATUS Status; UINT64 Value; XenPvBlockSync (Dev); Status = XenBusIo->SetState (XenBusIo, XST_NIL, XenbusStateClosing); if (Status != XENSTORE_STATUS_SUCCESS) { DEBUG ((DEBUG_ERROR, "XenPvBlk: error while changing state to Closing: %d\n", Status)); goto Close; } Status = XenPvBlkWaitForBackendState (Dev, XenbusStateClosing, NULL); if (Status != XENSTORE_STATUS_SUCCESS) { DEBUG ((DEBUG_ERROR, "XenPvBlk: error while waiting for closing backend state: %d\n", Status)); goto Close; } Status = XenBusIo->SetState (XenBusIo, XST_NIL, XenbusStateClosed); if (Status != XENSTORE_STATUS_SUCCESS) { DEBUG ((DEBUG_ERROR, "XenPvBlk: error while changing state to Closed: %d\n", Status)); goto Close; } Status = XenPvBlkWaitForBackendState (Dev, XenbusStateClosed, NULL); if (Status != XENSTORE_STATUS_SUCCESS) { DEBUG ((DEBUG_ERROR, "XenPvBlk: error while waiting for closed backend state: %d\n", Status)); goto Close; } Status = XenBusIo->SetState (XenBusIo, XST_NIL, XenbusStateInitialising); if (Status != XENSTORE_STATUS_SUCCESS) { DEBUG ((DEBUG_ERROR, "XenPvBlk: error while changing state to initialising: %d\n", Status)); goto Close; } while (TRUE) { Status = XenBusReadUint64 (XenBusIo, "state", TRUE, &Value); if (Status != XENSTORE_STATUS_SUCCESS) { DEBUG ((DEBUG_ERROR, "XenPvBlk: error while waiting for new backend state: %d\n", Status)); goto Close; } if (Value <= XenbusStateInitWait || Value >= XenbusStateClosed) { break; } DEBUG ((DEBUG_INFO, "XenPvBlk: waiting backend state %d, current: %Lu\n", XenbusStateInitWait, Value)); XenBusIo->WaitForWatch (XenBusIo, Dev->StateWatchToken); } Close: XenBusIo->UnregisterWatch (XenBusIo, Dev->StateWatchToken); XenBusIo->XsRemove (XenBusIo, XST_NIL, "ring-ref"); XenBusIo->XsRemove (XenBusIo, XST_NIL, "event-channel"); XenBusIo->XsRemove (XenBusIo, XST_NIL, "protocol"); XenPvBlockFree (Dev); } STATIC VOID XenPvBlockWaitSlot ( IN XEN_BLOCK_FRONT_DEVICE *Dev ) { /* Wait for a slot */ if (RING_FULL (&Dev->Ring)) { while (TRUE) { XenPvBlockAsyncIoPoll (Dev); if (!RING_FULL (&Dev->Ring)) { break; } /* Really no slot, could wait for an event on Dev->EventChannel. */ } } } VOID XenPvBlockAsyncIo ( IN OUT XEN_BLOCK_FRONT_IO *IoData, IN BOOLEAN IsWrite ) { XEN_BLOCK_FRONT_DEVICE *Dev = IoData->Dev; XENBUS_PROTOCOL *XenBusIo = Dev->XenBusIo; blkif_request_t *Request; RING_IDX RingIndex; BOOLEAN Notify; INT32 NumSegments, Index; UINTN Start, End; // Can't io at non-sector-aligned location ASSERT(!(IoData->Sector & ((Dev->MediaInfo.SectorSize / 512) - 1))); // Can't io non-sector-sized amounts ASSERT(!(IoData->Size & (Dev->MediaInfo.SectorSize - 1))); // Can't io non-sector-aligned buffer ASSERT(!((UINTN) IoData->Buffer & (Dev->MediaInfo.SectorSize - 1))); Start = (UINTN) IoData->Buffer & ~EFI_PAGE_MASK; End = ((UINTN) IoData->Buffer + IoData->Size + EFI_PAGE_SIZE - 1) & ~EFI_PAGE_MASK; IoData->NumRef = NumSegments = (INT32)((End - Start) / EFI_PAGE_SIZE); ASSERT (NumSegments <= BLKIF_MAX_SEGMENTS_PER_REQUEST); XenPvBlockWaitSlot (Dev); RingIndex = Dev->Ring.req_prod_pvt; Request = RING_GET_REQUEST (&Dev->Ring, RingIndex); Request->operation = IsWrite ? BLKIF_OP_WRITE : BLKIF_OP_READ; Request->nr_segments = (UINT8)NumSegments; Request->handle = Dev->DeviceId; Request->id = (UINTN) IoData; Request->sector_number = IoData->Sector; for (Index = 0; Index < NumSegments; Index++) { Request->seg[Index].first_sect = 0; Request->seg[Index].last_sect = EFI_PAGE_SIZE / 512 - 1; } Request->seg[0].first_sect = (UINT8)(((UINTN) IoData->Buffer & EFI_PAGE_MASK) / 512); Request->seg[NumSegments - 1].last_sect = (UINT8)((((UINTN) IoData->Buffer + IoData->Size - 1) & EFI_PAGE_MASK) / 512); for (Index = 0; Index < NumSegments; Index++) { UINTN Data = Start + Index * EFI_PAGE_SIZE; XenBusIo->GrantAccess (XenBusIo, Dev->DomainId, Data >> EFI_PAGE_SHIFT, IsWrite, &Request->seg[Index].gref); IoData->GrantRef[Index] = Request->seg[Index].gref; } Dev->Ring.req_prod_pvt = RingIndex + 1; MemoryFence (); RING_PUSH_REQUESTS_AND_CHECK_NOTIFY (&Dev->Ring, Notify); if (Notify) { UINT32 ReturnCode; ReturnCode = XenBusIo->EventChannelNotify (XenBusIo, Dev->EventChannel); if (ReturnCode != 0) { DEBUG ((DEBUG_ERROR, "XenPvBlk: Unexpected return value from EventChannelNotify: %d\n", ReturnCode)); } } } EFI_STATUS XenPvBlockIo ( IN OUT XEN_BLOCK_FRONT_IO *IoData, IN BOOLEAN IsWrite ) { // // Status value that correspond to an IO in progress. // IoData->Status = EFI_ALREADY_STARTED; XenPvBlockAsyncIo (IoData, IsWrite); while (IoData->Status == EFI_ALREADY_STARTED) { XenPvBlockAsyncIoPoll (IoData->Dev); } return IoData->Status; } STATIC VOID XenPvBlockPushOperation ( IN XEN_BLOCK_FRONT_DEVICE *Dev, IN UINT8 Operation, IN UINT64 Id ) { INT32 Index; blkif_request_t *Request; BOOLEAN Notify; XenPvBlockWaitSlot (Dev); Index = Dev->Ring.req_prod_pvt; Request = RING_GET_REQUEST(&Dev->Ring, Index); Request->operation = Operation; Request->nr_segments = 0; Request->handle = Dev->DeviceId; Request->id = Id; /* Not needed anyway, but the backend will check it */ Request->sector_number = 0; Dev->Ring.req_prod_pvt = Index + 1; MemoryFence (); RING_PUSH_REQUESTS_AND_CHECK_NOTIFY (&Dev->Ring, Notify); if (Notify) { XENBUS_PROTOCOL *XenBusIo = Dev->XenBusIo; UINT32 ReturnCode; ReturnCode = XenBusIo->EventChannelNotify (XenBusIo, Dev->EventChannel); if (ReturnCode != 0) { DEBUG ((DEBUG_ERROR, "XenPvBlk: Unexpected return value from EventChannelNotify: %d\n", ReturnCode)); } } } VOID XenPvBlockSync ( IN XEN_BLOCK_FRONT_DEVICE *Dev ) { if (Dev->MediaInfo.ReadWrite) { if (Dev->MediaInfo.FeatureBarrier) { XenPvBlockPushOperation (Dev, BLKIF_OP_WRITE_BARRIER, 0); } if (Dev->MediaInfo.FeatureFlushCache) { XenPvBlockPushOperation (Dev, BLKIF_OP_FLUSH_DISKCACHE, 0); } } /* Note: This won't finish if another thread enqueues requests. */ while (TRUE) { XenPvBlockAsyncIoPoll (Dev); if (RING_FREE_REQUESTS (&Dev->Ring) == RING_SIZE (&Dev->Ring)) { break; } } } VOID XenPvBlockAsyncIoPoll ( IN XEN_BLOCK_FRONT_DEVICE *Dev ) { RING_IDX ProducerIndex, ConsumerIndex; blkif_response_t *Response; INT32 More; do { ProducerIndex = Dev->Ring.sring->rsp_prod; /* Ensure we see queued responses up to 'ProducerIndex'. */ MemoryFence (); ConsumerIndex = Dev->Ring.rsp_cons; while (ConsumerIndex != ProducerIndex) { XEN_BLOCK_FRONT_IO *IoData = NULL; INT16 Status; Response = RING_GET_RESPONSE (&Dev->Ring, ConsumerIndex); IoData = (VOID *) (UINTN) Response->id; Status = Response->status; switch (Response->operation) { case BLKIF_OP_READ: case BLKIF_OP_WRITE: { INT32 Index; if (Status != BLKIF_RSP_OKAY) { DEBUG ((DEBUG_ERROR, "XenPvBlk: " "%a error %d on %a at sector %Lx, num bytes %Lx\n", Response->operation == BLKIF_OP_READ ? "read" : "write", Status, IoData->Dev->NodeName, (UINT64)IoData->Sector, (UINT64)IoData->Size)); } for (Index = 0; Index < IoData->NumRef; Index++) { Dev->XenBusIo->GrantEndAccess (Dev->XenBusIo, IoData->GrantRef[Index]); } break; } case BLKIF_OP_WRITE_BARRIER: if (Status != BLKIF_RSP_OKAY) { DEBUG ((DEBUG_ERROR, "XenPvBlk: write barrier error %d\n", Status)); } break; case BLKIF_OP_FLUSH_DISKCACHE: if (Status != BLKIF_RSP_OKAY) { DEBUG ((DEBUG_ERROR, "XenPvBlk: flush error %d\n", Status)); } break; default: DEBUG ((DEBUG_ERROR, "XenPvBlk: unrecognized block operation %d response (status %d)\n", Response->operation, Status)); break; } Dev->Ring.rsp_cons = ++ConsumerIndex; if (IoData != NULL) { IoData->Status = Status ? EFI_DEVICE_ERROR : EFI_SUCCESS; } if (Dev->Ring.rsp_cons != ConsumerIndex) { /* We reentered, we must not continue here */ break; } } RING_FINAL_CHECK_FOR_RESPONSES (&Dev->Ring, More); } while (More != 0); }