MdeModulePkg: Add PEI USB drivers and related PPIs
Signed-off-by: jljusten Reviewed-by: mdkinney Reviewed-by: geekboy15a git-svn-id: https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2@11901 6f19259b-4bc3-4df7-8a09-765794883524
This commit is contained in:
630
MdeModulePkg/Bus/Usb/UsbBotPei/PeiAtapi.c
Normal file
630
MdeModulePkg/Bus/Usb/UsbBotPei/PeiAtapi.c
Normal file
@@ -0,0 +1,630 @@
|
||||
/** @file
|
||||
Pei USB ATATPI command implementations.
|
||||
|
||||
Copyright (c) 1999 - 2010, Intel Corporation. All rights reserved.<BR>
|
||||
|
||||
This program and the accompanying materials
|
||||
are licensed and made available under the terms and conditions
|
||||
of the BSD License which accompanies this distribution. The
|
||||
full text of the license may be found at
|
||||
http://opensource.org/licenses/bsd-license.php
|
||||
|
||||
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
||||
|
||||
**/
|
||||
|
||||
#include "UsbBotPeim.h"
|
||||
#include "BotPeim.h"
|
||||
|
||||
#define MAXSENSEKEY 5
|
||||
|
||||
/**
|
||||
Sends out ATAPI Inquiry Packet Command to the specified device. This command will
|
||||
return INQUIRY data of the device.
|
||||
|
||||
@param PeiServices The pointer of EFI_PEI_SERVICES.
|
||||
@param PeiBotDevice The pointer to PEI_BOT_DEVICE instance.
|
||||
|
||||
@retval EFI_SUCCESS Inquiry command completes successfully.
|
||||
@retval EFI_DEVICE_ERROR Inquiry command failed.
|
||||
|
||||
**/
|
||||
EFI_STATUS
|
||||
PeiUsbInquiry (
|
||||
IN EFI_PEI_SERVICES **PeiServices,
|
||||
IN PEI_BOT_DEVICE *PeiBotDevice
|
||||
)
|
||||
{
|
||||
ATAPI_PACKET_COMMAND Packet;
|
||||
EFI_STATUS Status;
|
||||
ATAPI_INQUIRY_DATA Idata;
|
||||
|
||||
//
|
||||
// fill command packet
|
||||
//
|
||||
ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));
|
||||
ZeroMem (&Idata, sizeof (ATAPI_INQUIRY_DATA));
|
||||
|
||||
Packet.Inquiry.opcode = ATA_CMD_INQUIRY;
|
||||
Packet.Inquiry.page_code = 0;
|
||||
Packet.Inquiry.allocation_length = 36;
|
||||
|
||||
//
|
||||
// Send scsi INQUIRY command packet.
|
||||
// According to SCSI Primary Commands-2 spec, host only needs to
|
||||
// retrieve the first 36 bytes for standard INQUIRY data.
|
||||
//
|
||||
Status = PeiAtapiCommand (
|
||||
PeiServices,
|
||||
PeiBotDevice,
|
||||
&Packet,
|
||||
(UINT8) sizeof (ATAPI_PACKET_COMMAND),
|
||||
&Idata,
|
||||
36,
|
||||
EfiUsbDataIn,
|
||||
2000
|
||||
);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
return EFI_DEVICE_ERROR;
|
||||
}
|
||||
|
||||
if ((Idata.peripheral_type & 0x1f) == 0x05) {
|
||||
PeiBotDevice->DeviceType = USBCDROM;
|
||||
PeiBotDevice->Media.BlockSize = 0x800;
|
||||
} else {
|
||||
PeiBotDevice->DeviceType = USBFLOPPY;
|
||||
PeiBotDevice->Media.BlockSize = 0x200;
|
||||
}
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
Sends out ATAPI Test Unit Ready Packet Command to the specified device
|
||||
to find out whether device is accessible.
|
||||
|
||||
@param PeiServices The pointer of EFI_PEI_SERVICES.
|
||||
@param PeiBotDevice The pointer to PEI_BOT_DEVICE instance.
|
||||
|
||||
@retval EFI_SUCCESS TestUnit command executed successfully.
|
||||
@retval EFI_DEVICE_ERROR Device cannot be executed TestUnit command successfully.
|
||||
|
||||
**/
|
||||
EFI_STATUS
|
||||
PeiUsbTestUnitReady (
|
||||
IN EFI_PEI_SERVICES **PeiServices,
|
||||
IN PEI_BOT_DEVICE *PeiBotDevice
|
||||
)
|
||||
{
|
||||
ATAPI_PACKET_COMMAND Packet;
|
||||
EFI_STATUS Status;
|
||||
|
||||
//
|
||||
// fill command packet
|
||||
//
|
||||
ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));
|
||||
Packet.TestUnitReady.opcode = ATA_CMD_TEST_UNIT_READY;
|
||||
|
||||
//
|
||||
// send command packet
|
||||
//
|
||||
Status = PeiAtapiCommand (
|
||||
PeiServices,
|
||||
PeiBotDevice,
|
||||
&Packet,
|
||||
(UINT8) sizeof (ATAPI_PACKET_COMMAND),
|
||||
NULL,
|
||||
0,
|
||||
EfiUsbNoData,
|
||||
2000
|
||||
);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
return EFI_DEVICE_ERROR;
|
||||
}
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
Sends out ATAPI Request Sense Packet Command to the specified device.
|
||||
|
||||
@param PeiServices The pointer of EFI_PEI_SERVICES.
|
||||
@param PeiBotDevice The pointer to PEI_BOT_DEVICE instance.
|
||||
@param SenseCounts Length of sense buffer.
|
||||
@param SenseKeyBuffer Pointer to sense buffer.
|
||||
|
||||
@retval EFI_SUCCESS Command executed successfully.
|
||||
@retval EFI_DEVICE_ERROR Some device errors happen.
|
||||
|
||||
**/
|
||||
EFI_STATUS
|
||||
PeiUsbRequestSense (
|
||||
IN EFI_PEI_SERVICES **PeiServices,
|
||||
IN PEI_BOT_DEVICE *PeiBotDevice,
|
||||
OUT UINTN *SenseCounts,
|
||||
IN UINT8 *SenseKeyBuffer
|
||||
)
|
||||
{
|
||||
EFI_STATUS Status;
|
||||
ATAPI_PACKET_COMMAND Packet;
|
||||
UINT8 *Ptr;
|
||||
BOOLEAN SenseReq;
|
||||
ATAPI_REQUEST_SENSE_DATA *Sense;
|
||||
|
||||
*SenseCounts = 0;
|
||||
|
||||
//
|
||||
// fill command packet for Request Sense Packet Command
|
||||
//
|
||||
ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));
|
||||
Packet.RequestSence.opcode = ATA_CMD_REQUEST_SENSE;
|
||||
Packet.RequestSence.allocation_length = (UINT8) sizeof (ATAPI_REQUEST_SENSE_DATA);
|
||||
|
||||
Ptr = SenseKeyBuffer;
|
||||
|
||||
SenseReq = TRUE;
|
||||
|
||||
//
|
||||
// request sense data from device continuously
|
||||
// until no sense data exists in the device.
|
||||
//
|
||||
while (SenseReq) {
|
||||
Sense = (ATAPI_REQUEST_SENSE_DATA *) Ptr;
|
||||
|
||||
//
|
||||
// send out Request Sense Packet Command and get one Sense
|
||||
// data form device.
|
||||
//
|
||||
Status = PeiAtapiCommand (
|
||||
PeiServices,
|
||||
PeiBotDevice,
|
||||
&Packet,
|
||||
(UINT8) sizeof (ATAPI_PACKET_COMMAND),
|
||||
(VOID *) Ptr,
|
||||
sizeof (ATAPI_REQUEST_SENSE_DATA),
|
||||
EfiUsbDataIn,
|
||||
2000
|
||||
);
|
||||
|
||||
//
|
||||
// failed to get Sense data
|
||||
//
|
||||
if (EFI_ERROR (Status)) {
|
||||
if (*SenseCounts == 0) {
|
||||
return EFI_DEVICE_ERROR;
|
||||
} else {
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
if (Sense->sense_key != ATA_SK_NO_SENSE) {
|
||||
|
||||
Ptr += sizeof (ATAPI_REQUEST_SENSE_DATA);
|
||||
//
|
||||
// Ptr is byte based pointer
|
||||
//
|
||||
(*SenseCounts)++;
|
||||
|
||||
if (*SenseCounts == MAXSENSEKEY) {
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
//
|
||||
// when no sense key, skip out the loop
|
||||
//
|
||||
SenseReq = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
Sends out ATAPI Read Capacity Packet Command to the specified device.
|
||||
This command will return the information regarding the capacity of the
|
||||
media in the device.
|
||||
|
||||
@param PeiServices The pointer of EFI_PEI_SERVICES.
|
||||
@param PeiBotDevice The pointer to PEI_BOT_DEVICE instance.
|
||||
|
||||
@retval EFI_SUCCESS Command executed successfully.
|
||||
@retval EFI_DEVICE_ERROR Some device errors happen.
|
||||
|
||||
**/
|
||||
EFI_STATUS
|
||||
PeiUsbReadCapacity (
|
||||
IN EFI_PEI_SERVICES **PeiServices,
|
||||
IN PEI_BOT_DEVICE *PeiBotDevice
|
||||
)
|
||||
{
|
||||
EFI_STATUS Status;
|
||||
ATAPI_PACKET_COMMAND Packet;
|
||||
ATAPI_READ_CAPACITY_DATA Data;
|
||||
|
||||
ZeroMem (&Data, sizeof (ATAPI_READ_CAPACITY_DATA));
|
||||
ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));
|
||||
|
||||
Packet.Inquiry.opcode = ATA_CMD_READ_CAPACITY;
|
||||
|
||||
//
|
||||
// send command packet
|
||||
//
|
||||
Status = PeiAtapiCommand (
|
||||
PeiServices,
|
||||
PeiBotDevice,
|
||||
&Packet,
|
||||
(UINT8) sizeof (ATAPI_PACKET_COMMAND),
|
||||
(VOID *) &Data,
|
||||
sizeof (ATAPI_READ_CAPACITY_DATA),
|
||||
EfiUsbDataIn,
|
||||
2000
|
||||
);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
return EFI_DEVICE_ERROR;
|
||||
}
|
||||
|
||||
PeiBotDevice->Media.LastBlock = (Data.LastLba3 << 24) | (Data.LastLba2 << 16) | (Data.LastLba1 << 8) | Data.LastLba0;
|
||||
|
||||
PeiBotDevice->Media.MediaPresent = TRUE;
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
Sends out ATAPI Read Format Capacity Data Command to the specified device.
|
||||
This command will return the information regarding the capacity of the
|
||||
media in the device.
|
||||
|
||||
@param PeiServices The pointer of EFI_PEI_SERVICES.
|
||||
@param PeiBotDevice The pointer to PEI_BOT_DEVICE instance.
|
||||
|
||||
@retval EFI_SUCCESS Command executed successfully.
|
||||
@retval EFI_DEVICE_ERROR Some device errors happen.
|
||||
|
||||
**/
|
||||
EFI_STATUS
|
||||
PeiUsbReadFormattedCapacity (
|
||||
IN EFI_PEI_SERVICES **PeiServices,
|
||||
IN PEI_BOT_DEVICE *PeiBotDevice
|
||||
)
|
||||
{
|
||||
EFI_STATUS Status;
|
||||
ATAPI_PACKET_COMMAND Packet;
|
||||
ATAPI_READ_FORMAT_CAPACITY_DATA FormatData;
|
||||
|
||||
ZeroMem (&FormatData, sizeof (ATAPI_READ_FORMAT_CAPACITY_DATA));
|
||||
ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));
|
||||
|
||||
Packet.ReadFormatCapacity.opcode = ATA_CMD_READ_FORMAT_CAPACITY;
|
||||
Packet.ReadFormatCapacity.allocation_length_lo = 12;
|
||||
|
||||
//
|
||||
// send command packet
|
||||
//
|
||||
Status = PeiAtapiCommand (
|
||||
PeiServices,
|
||||
PeiBotDevice,
|
||||
&Packet,
|
||||
(UINT8) sizeof (ATAPI_PACKET_COMMAND),
|
||||
(VOID *) &FormatData,
|
||||
sizeof (ATAPI_READ_FORMAT_CAPACITY_DATA),
|
||||
EfiUsbDataIn,
|
||||
2000
|
||||
);
|
||||
|
||||
if (EFI_ERROR (Status)) {
|
||||
return EFI_DEVICE_ERROR;
|
||||
}
|
||||
|
||||
if (FormatData.DesCode == 3) {
|
||||
//
|
||||
// Media is not present
|
||||
//
|
||||
PeiBotDevice->Media.MediaPresent = FALSE;
|
||||
PeiBotDevice->Media.LastBlock = 0;
|
||||
|
||||
} else {
|
||||
|
||||
PeiBotDevice->Media.LastBlock = (FormatData.LastLba3 << 24) | (FormatData.LastLba2 << 16) | (FormatData.LastLba1 << 8) | FormatData.LastLba0;
|
||||
|
||||
PeiBotDevice->Media.LastBlock--;
|
||||
|
||||
PeiBotDevice->Media.MediaPresent = TRUE;
|
||||
}
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
Execute Read(10) ATAPI command on a specific SCSI target.
|
||||
|
||||
Executes the ATAPI Read(10) command on the ATAPI target specified by PeiBotDevice.
|
||||
|
||||
@param PeiServices The pointer of EFI_PEI_SERVICES.
|
||||
@param PeiBotDevice The pointer to PEI_BOT_DEVICE instance.
|
||||
@param Buffer The pointer to data buffer.
|
||||
@param Lba The start logic block address of reading.
|
||||
@param NumberOfBlocks The block number of reading.
|
||||
|
||||
@retval EFI_SUCCESS Command executed successfully.
|
||||
@retval EFI_DEVICE_ERROR Some device errors happen.
|
||||
|
||||
**/
|
||||
EFI_STATUS
|
||||
PeiUsbRead10 (
|
||||
IN EFI_PEI_SERVICES **PeiServices,
|
||||
IN PEI_BOT_DEVICE *PeiBotDevice,
|
||||
IN VOID *Buffer,
|
||||
IN EFI_PEI_LBA Lba,
|
||||
IN UINTN NumberOfBlocks
|
||||
)
|
||||
{
|
||||
ATAPI_PACKET_COMMAND Packet;
|
||||
ATAPI_READ10_CMD *Read10Packet;
|
||||
UINT16 MaxBlock;
|
||||
UINT16 BlocksRemaining;
|
||||
UINT16 SectorCount;
|
||||
UINT32 Lba32;
|
||||
UINT32 BlockSize;
|
||||
UINT32 ByteCount;
|
||||
VOID *PtrBuffer;
|
||||
EFI_STATUS Status;
|
||||
UINT16 TimeOut;
|
||||
|
||||
//
|
||||
// prepare command packet for the Inquiry Packet Command.
|
||||
//
|
||||
ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));
|
||||
Read10Packet = &Packet.Read10;
|
||||
Lba32 = (UINT32) Lba;
|
||||
PtrBuffer = Buffer;
|
||||
|
||||
BlockSize = (UINT32) PeiBotDevice->Media.BlockSize;
|
||||
|
||||
MaxBlock = (UINT16) (65535 / BlockSize);
|
||||
BlocksRemaining = (UINT16) NumberOfBlocks;
|
||||
|
||||
Status = EFI_SUCCESS;
|
||||
while (BlocksRemaining > 0) {
|
||||
|
||||
if (BlocksRemaining <= MaxBlock) {
|
||||
|
||||
SectorCount = BlocksRemaining;
|
||||
|
||||
} else {
|
||||
|
||||
SectorCount = MaxBlock;
|
||||
}
|
||||
//
|
||||
// fill the Packet data structure
|
||||
//
|
||||
Read10Packet->opcode = ATA_CMD_READ_10;
|
||||
|
||||
//
|
||||
// Lba0 ~ Lba3 specify the start logical block address of the data transfer.
|
||||
// Lba0 is MSB, Lba3 is LSB
|
||||
//
|
||||
Read10Packet->Lba3 = (UINT8) (Lba32 & 0xff);
|
||||
Read10Packet->Lba2 = (UINT8) (Lba32 >> 8);
|
||||
Read10Packet->Lba1 = (UINT8) (Lba32 >> 16);
|
||||
Read10Packet->Lba0 = (UINT8) (Lba32 >> 24);
|
||||
|
||||
//
|
||||
// TranLen0 ~ TranLen1 specify the transfer length in block unit.
|
||||
// TranLen0 is MSB, TranLen is LSB
|
||||
//
|
||||
Read10Packet->TranLen1 = (UINT8) (SectorCount & 0xff);
|
||||
Read10Packet->TranLen0 = (UINT8) (SectorCount >> 8);
|
||||
|
||||
ByteCount = SectorCount * BlockSize;
|
||||
|
||||
TimeOut = (UINT16) (SectorCount * 2000);
|
||||
|
||||
//
|
||||
// send command packet
|
||||
//
|
||||
Status = PeiAtapiCommand (
|
||||
PeiServices,
|
||||
PeiBotDevice,
|
||||
&Packet,
|
||||
(UINT8) sizeof (ATAPI_PACKET_COMMAND),
|
||||
(VOID *) PtrBuffer,
|
||||
ByteCount,
|
||||
EfiUsbDataIn,
|
||||
TimeOut
|
||||
);
|
||||
|
||||
if (Status != EFI_SUCCESS) {
|
||||
return Status;
|
||||
}
|
||||
|
||||
Lba32 += SectorCount;
|
||||
PtrBuffer = (UINT8 *) PtrBuffer + SectorCount * BlockSize;
|
||||
BlocksRemaining = (UINT16) (BlocksRemaining - SectorCount);
|
||||
}
|
||||
|
||||
return Status;
|
||||
}
|
||||
|
||||
/**
|
||||
Check if there is media according to sense data.
|
||||
|
||||
@param SenseData Pointer to sense data.
|
||||
@param SenseCounts Count of sense data.
|
||||
|
||||
@retval TRUE No media
|
||||
@retval FALSE Media exists
|
||||
|
||||
**/
|
||||
BOOLEAN
|
||||
IsNoMedia (
|
||||
IN ATAPI_REQUEST_SENSE_DATA *SenseData,
|
||||
IN UINTN SenseCounts
|
||||
)
|
||||
{
|
||||
ATAPI_REQUEST_SENSE_DATA *SensePtr;
|
||||
UINTN Index;
|
||||
BOOLEAN NoMedia;
|
||||
|
||||
NoMedia = FALSE;
|
||||
SensePtr = SenseData;
|
||||
|
||||
for (Index = 0; Index < SenseCounts; Index++) {
|
||||
|
||||
switch (SensePtr->sense_key) {
|
||||
|
||||
case ATA_SK_NOT_READY:
|
||||
switch (SensePtr->addnl_sense_code) {
|
||||
//
|
||||
// if no media, fill IdeDev parameter with specific info.
|
||||
//
|
||||
case ATA_ASC_NO_MEDIA:
|
||||
NoMedia = TRUE;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
SensePtr++;
|
||||
}
|
||||
|
||||
return NoMedia;
|
||||
}
|
||||
|
||||
/**
|
||||
Check if there is media error according to sense data.
|
||||
|
||||
@param SenseData Pointer to sense data.
|
||||
@param SenseCounts Count of sense data.
|
||||
|
||||
@retval TRUE Media error
|
||||
@retval FALSE No media error
|
||||
|
||||
**/
|
||||
BOOLEAN
|
||||
IsMediaError (
|
||||
IN ATAPI_REQUEST_SENSE_DATA *SenseData,
|
||||
IN UINTN SenseCounts
|
||||
)
|
||||
{
|
||||
ATAPI_REQUEST_SENSE_DATA *SensePtr;
|
||||
UINTN Index;
|
||||
BOOLEAN Error;
|
||||
|
||||
SensePtr = SenseData;
|
||||
Error = FALSE;
|
||||
|
||||
for (Index = 0; Index < SenseCounts; Index++) {
|
||||
|
||||
switch (SensePtr->sense_key) {
|
||||
//
|
||||
// Medium error case
|
||||
//
|
||||
case ATA_SK_MEDIUM_ERROR:
|
||||
switch (SensePtr->addnl_sense_code) {
|
||||
case ATA_ASC_MEDIA_ERR1:
|
||||
//
|
||||
// fall through
|
||||
//
|
||||
case ATA_ASC_MEDIA_ERR2:
|
||||
//
|
||||
// fall through
|
||||
//
|
||||
case ATA_ASC_MEDIA_ERR3:
|
||||
//
|
||||
// fall through
|
||||
//
|
||||
case ATA_ASC_MEDIA_ERR4:
|
||||
Error = TRUE;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
//
|
||||
// Medium upside-down case
|
||||
//
|
||||
case ATA_SK_NOT_READY:
|
||||
switch (SensePtr->addnl_sense_code) {
|
||||
case ATA_ASC_MEDIA_UPSIDE_DOWN:
|
||||
Error = TRUE;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
SensePtr++;
|
||||
}
|
||||
|
||||
return Error;
|
||||
}
|
||||
|
||||
/**
|
||||
Check if media is changed according to sense data.
|
||||
|
||||
@param SenseData Pointer to sense data.
|
||||
@param SenseCounts Count of sense data.
|
||||
|
||||
@retval TRUE There is media change event.
|
||||
@retval FALSE media is NOT changed.
|
||||
|
||||
**/
|
||||
BOOLEAN
|
||||
IsMediaChange (
|
||||
IN ATAPI_REQUEST_SENSE_DATA *SenseData,
|
||||
IN UINTN SenseCounts
|
||||
)
|
||||
{
|
||||
ATAPI_REQUEST_SENSE_DATA *SensePtr;
|
||||
UINTN Index;
|
||||
BOOLEAN MediaChange;
|
||||
|
||||
MediaChange = FALSE;
|
||||
|
||||
SensePtr = SenseData;
|
||||
|
||||
for (Index = 0; Index < SenseCounts; Index++) {
|
||||
//
|
||||
// catch media change sense key and addition sense data
|
||||
//
|
||||
switch (SensePtr->sense_key) {
|
||||
case ATA_SK_UNIT_ATTENTION:
|
||||
switch (SensePtr->addnl_sense_code) {
|
||||
case ATA_ASC_MEDIA_CHANGE:
|
||||
MediaChange = TRUE;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
SensePtr++;
|
||||
}
|
||||
|
||||
return MediaChange;
|
||||
}
|
Reference in New Issue
Block a user