idedrv Implement DMA reading/writing with interrupts
This commit is contained in:
@@ -113,6 +113,16 @@ static void ide_irq(void* arg, void* regs, bool user, struct reschedule_ctx* rct
|
|||||||
|
|
||||||
struct idedrv_request* req = idedrv->current_req;
|
struct idedrv_request* req = idedrv->current_req;
|
||||||
|
|
||||||
|
if (idedrv->bm_support) {
|
||||||
|
uint8_t bm_status = inb(idedrv->bmbase + IDE_DMA_REG_STATUS);
|
||||||
|
|
||||||
|
if (!(bm_status & IDE_DMA_STATUS_INTR))
|
||||||
|
return;
|
||||||
|
|
||||||
|
outb(idedrv->bmbase + IDE_DMA_REG_STATUS,
|
||||||
|
bm_status | IDE_DMA_STATUS_INTR | IDE_DMA_STATUS_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
if (req == NULL) {
|
if (req == NULL) {
|
||||||
(void)inb(idedrv->io + IDE_REG_STATUS);
|
(void)inb(idedrv->io + IDE_REG_STATUS);
|
||||||
return;
|
return;
|
||||||
@@ -120,6 +130,13 @@ static void ide_irq(void* arg, void* regs, bool user, struct reschedule_ctx* rct
|
|||||||
|
|
||||||
uint8_t status = inb(idedrv->io + IDE_REG_STATUS);
|
uint8_t status = inb(idedrv->io + IDE_REG_STATUS);
|
||||||
|
|
||||||
|
if (idedrv->bm_support) {
|
||||||
|
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x00);
|
||||||
|
atomic_store(&req->done, 1);
|
||||||
|
idedrv->current_req = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ((status & (IDE_ERR | IDE_DF))) {
|
if ((status & (IDE_ERR | IDE_DF))) {
|
||||||
atomic_store(&req->done, 1);
|
atomic_store(&req->done, 1);
|
||||||
idedrv->current_req = NULL;
|
idedrv->current_req = NULL;
|
||||||
@@ -280,10 +297,10 @@ DEFINE_DEVICE_INIT(idedrv_init) {
|
|||||||
idedrv->prdt_entry_count = PAGE_SIZE / sizeof(struct ide_prd_entry);
|
idedrv->prdt_entry_count = PAGE_SIZE / sizeof(struct ide_prd_entry);
|
||||||
idedrv->prdt = (struct ide_prd_entry*)((uintptr_t)hhdm->offset + idedrv->prdt_phys);
|
idedrv->prdt = (struct ide_prd_entry*)((uintptr_t)hhdm->offset + idedrv->prdt_phys);
|
||||||
|
|
||||||
idedrv->bounce_buffer_phys = pmm_alloc_aligned(16, 16);
|
idedrv->bounce_buffer_phys = pmm_alloc_aligned(64, 16);
|
||||||
|
|
||||||
if (idedrv->bounce_buffer_phys >= 0xFFFFFFFF) {
|
if (idedrv->bounce_buffer_phys >= 0xFFFFFFFF) {
|
||||||
pmm_free(idedrv->bounce_buffer_phys, 16);
|
pmm_free(idedrv->bounce_buffer_phys, 64);
|
||||||
pmm_free(idedrv->prdt_phys, 1);
|
pmm_free(idedrv->prdt_phys, 1);
|
||||||
free(idedrv);
|
free(idedrv);
|
||||||
return false;
|
return false;
|
||||||
@@ -312,8 +329,8 @@ DEFINE_DEVICE_FINI(idedrv_fini) {
|
|||||||
irq_detach(idedrv->irq);
|
irq_detach(idedrv->irq);
|
||||||
|
|
||||||
if (idedrv->bm_support) {
|
if (idedrv->bm_support) {
|
||||||
pmm_free(idedrv->bounce_buffer_phys, 16);
|
pmm_free(idedrv->bounce_buffer_phys, 64);
|
||||||
pmm_free(idedrv->prdt_phys, 16);
|
pmm_free(idedrv->prdt_phys, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(idedrv);
|
free(idedrv);
|
||||||
@@ -334,10 +351,42 @@ static int idedrv_do_read_irqs(struct idedrv* idedrv, size_t sector, size_t sect
|
|||||||
|
|
||||||
idedrv->current_req = req;
|
idedrv->current_req = req;
|
||||||
|
|
||||||
ide_prepare(idedrv, sector, sector_count, true);
|
if (idedrv->bm_support) {
|
||||||
|
size_t rem = sector_count * idedrv->sector_size;
|
||||||
|
uint32_t phys = idedrv->bounce_buffer_phys;
|
||||||
|
size_t prd_idx = 0;
|
||||||
|
|
||||||
uint8_t cmd = idedrv->lba48 ? IDE_CMD_READ48 : IDE_CMD_READ28;
|
while (rem > 0 && prd_idx < idedrv->prdt_entry_count) {
|
||||||
outb(idedrv->io + IDE_REG_CMD, cmd);
|
uint32_t chunk = (rem >= 0x10000) ? 0x10000 : rem;
|
||||||
|
idedrv->prdt[prd_idx].phys_addr = phys;
|
||||||
|
idedrv->prdt[prd_idx].size = (uint16_t)chunk;
|
||||||
|
|
||||||
|
rem -= chunk;
|
||||||
|
phys += chunk;
|
||||||
|
|
||||||
|
idedrv->prdt[prd_idx].rsvd_eot = (rem == 0) ? 0x8000 : 0x0000;
|
||||||
|
prd_idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
outl(idedrv->bmbase + IDE_DMA_REG_PRDT, (uint32_t)idedrv->prdt_phys);
|
||||||
|
|
||||||
|
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x08);
|
||||||
|
|
||||||
|
uint8_t status = inb(idedrv->bmbase + IDE_DMA_REG_STATUS);
|
||||||
|
outb(idedrv->bmbase + IDE_DMA_REG_STATUS, status | IDE_DMA_STATUS_INTR | IDE_DMA_STATUS_ERROR);
|
||||||
|
|
||||||
|
ide_prepare(idedrv, sector, sector_count, true);
|
||||||
|
|
||||||
|
uint8_t cmd = idedrv->lba48 ? IDE_CMD_READ_DMA48 : IDE_CMD_READ_DMA28;
|
||||||
|
outb(idedrv->io + IDE_REG_CMD, cmd);
|
||||||
|
|
||||||
|
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x08 | 0x01);
|
||||||
|
} else {
|
||||||
|
ide_prepare(idedrv, sector, sector_count, true);
|
||||||
|
|
||||||
|
uint8_t cmd = idedrv->lba48 ? IDE_CMD_READ48 : IDE_CMD_READ28;
|
||||||
|
outb(idedrv->io + IDE_REG_CMD, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
biglock_unlock();
|
biglock_unlock();
|
||||||
|
|
||||||
@@ -348,65 +397,64 @@ static int idedrv_do_read_irqs(struct idedrv* idedrv, size_t sector, size_t sect
|
|||||||
|
|
||||||
free(req);
|
free(req);
|
||||||
|
|
||||||
|
if (idedrv->bm_support)
|
||||||
|
memcpy(buffer, idedrv->bounce_buffer, sector_count * idedrv->sector_size);
|
||||||
|
|
||||||
return ST_OK;
|
return ST_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int idedrv_do_read_no_irqs(struct idedrv* idedrv, size_t sector, size_t sector_count,
|
static int idedrv_do_read_no_irqs(struct idedrv* idedrv, size_t sector, size_t sector_count,
|
||||||
void* buffer) {
|
void* buffer) {
|
||||||
if (idedrv->bm_support) {
|
if (idedrv->bm_support) {
|
||||||
size_t sectors_done = 0;
|
size_t rem = sector_count * idedrv->sector_size;
|
||||||
|
uint32_t phys = idedrv->bounce_buffer_phys;
|
||||||
|
size_t prd_idx = 0;
|
||||||
|
|
||||||
while (sectors_done < sector_count) {
|
while (rem > 0 && prd_idx < idedrv->prdt_entry_count) {
|
||||||
size_t chunk_sectors = sector_count - sectors_done;
|
uint32_t chunk = (rem >= 0x10000) ? 0x10000 : rem;
|
||||||
size_t max_chunk = (16 * PAGE_SIZE) / idedrv->sector_size;
|
idedrv->prdt[prd_idx].phys_addr = phys;
|
||||||
|
idedrv->prdt[prd_idx].size = (uint16_t)chunk;
|
||||||
|
|
||||||
if (chunk_sectors > max_chunk)
|
rem -= chunk;
|
||||||
chunk_sectors = max_chunk;
|
phys += chunk;
|
||||||
|
|
||||||
size_t byte_count = chunk_sectors * idedrv->sector_size;
|
idedrv->prdt[prd_idx].rsvd_eot = (rem == 0) ? 0x8000 : 0x0000;
|
||||||
|
prd_idx++;
|
||||||
|
}
|
||||||
|
|
||||||
idedrv->prdt[0].phys_addr = (uint32_t)idedrv->bounce_buffer_phys;
|
outl(idedrv->bmbase + IDE_DMA_REG_PRDT, (uint32_t)idedrv->prdt_phys);
|
||||||
idedrv->prdt[0].size = (uint16_t)byte_count;
|
|
||||||
idedrv->prdt[0].rsvd_eot = 0x8000;
|
|
||||||
|
|
||||||
outl(idedrv->bmbase + IDE_DMA_REG_PRDT, (uint32_t)idedrv->prdt_phys);
|
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x08);
|
||||||
|
|
||||||
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x08);
|
uint8_t status = inb(idedrv->bmbase + IDE_DMA_REG_STATUS);
|
||||||
|
outb(idedrv->bmbase + IDE_DMA_REG_STATUS, status | IDE_DMA_STATUS_INTR | IDE_DMA_STATUS_ERROR);
|
||||||
|
|
||||||
uint8_t status = inb(idedrv->bmbase + IDE_DMA_REG_STATUS);
|
ide_prepare(idedrv, sector, sector_count, false);
|
||||||
outb(idedrv->bmbase + IDE_DMA_REG_STATUS,
|
|
||||||
status | IDE_DMA_STATUS_INTR | IDE_DMA_STATUS_ERROR);
|
|
||||||
|
|
||||||
ide_prepare(idedrv, sector + sectors_done, chunk_sectors, false);
|
uint8_t cmd = idedrv->lba48 ? IDE_CMD_READ_DMA48 : IDE_CMD_READ_DMA28;
|
||||||
|
outb(idedrv->io + IDE_REG_CMD, cmd);
|
||||||
|
|
||||||
uint8_t cmd = idedrv->lba48 ? IDE_CMD_READ_DMA48 : IDE_CMD_READ_DMA28;
|
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x08 | 0x01);
|
||||||
outb(idedrv->io + IDE_REG_CMD, cmd);
|
|
||||||
|
|
||||||
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x08 | 0x01);
|
for (;;) {
|
||||||
|
uint8_t bm_status = inb(idedrv->bmbase + IDE_DMA_REG_STATUS);
|
||||||
|
|
||||||
for (;;) {
|
if (!(bm_status & IDE_DMA_STATUS_ACTIVE))
|
||||||
uint8_t bm_status = inb(idedrv->bmbase + IDE_DMA_REG_STATUS);
|
break;
|
||||||
|
|
||||||
if (!(bm_status & IDE_DMA_STATUS_ACTIVE))
|
if ((bm_status & IDE_DMA_STATUS_ERROR)) {
|
||||||
break;
|
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x00);
|
||||||
|
return -ST_XDRV_READ_ERROR;
|
||||||
if ((bm_status & IDE_DMA_STATUS_ERROR)) {
|
|
||||||
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x00);
|
|
||||||
return -ST_XDRV_READ_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_lock_relax();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x00);
|
spin_lock_relax();
|
||||||
|
|
||||||
inb(idedrv->io + IDE_REG_STATUS);
|
|
||||||
|
|
||||||
memcpy((void*)((uint16_t*)buffer + (sectors_done * (idedrv->sector_size / 2))),
|
|
||||||
idedrv->bounce_buffer, byte_count);
|
|
||||||
|
|
||||||
sectors_done += chunk_sectors;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x00);
|
||||||
|
|
||||||
|
inb(idedrv->io + IDE_REG_STATUS);
|
||||||
|
|
||||||
|
memcpy(buffer, idedrv->bounce_buffer, sector_count * idedrv->sector_size);
|
||||||
} else {
|
} else {
|
||||||
ide_prepare(idedrv, sector, sector_count, false);
|
ide_prepare(idedrv, sector, sector_count, false);
|
||||||
|
|
||||||
@@ -438,9 +486,6 @@ DEFINE_DEVICE_OP(idedrv_read) {
|
|||||||
if (sector + sector_count > idedrv->sector_count)
|
if (sector + sector_count > idedrv->sector_count)
|
||||||
return -ST_OOB_ERROR;
|
return -ST_OOB_ERROR;
|
||||||
|
|
||||||
if (idedrv->bm_support && ((sector_count * idedrv->sector_size) >= 16 * PAGE_SIZE))
|
|
||||||
return -ST_OOB_ERROR;
|
|
||||||
|
|
||||||
if (idedrv->current_req != NULL)
|
if (idedrv->current_req != NULL)
|
||||||
return -ST_TRY_AGAIN;
|
return -ST_TRY_AGAIN;
|
||||||
|
|
||||||
@@ -468,21 +513,55 @@ static int idedrv_do_write_irqs(struct idedrv* idedrv, size_t sector, size_t sec
|
|||||||
|
|
||||||
idedrv->current_req = req;
|
idedrv->current_req = req;
|
||||||
|
|
||||||
ide_prepare(idedrv, sector, sector_count, true);
|
if (idedrv->bm_support) {
|
||||||
|
memcpy(idedrv->bounce_buffer, buffer, sector_count * idedrv->sector_size);
|
||||||
|
|
||||||
uint8_t cmd = idedrv->lba48 ? IDE_CMD_WRITE48 : IDE_CMD_WRITE28;
|
size_t rem = sector_count * idedrv->sector_size;
|
||||||
outb(idedrv->io + IDE_REG_CMD, cmd);
|
uint32_t phys = idedrv->bounce_buffer_phys;
|
||||||
|
size_t prd_idx = 0;
|
||||||
|
|
||||||
if (!ide_wait(idedrv->io, 100000, true, true)) {
|
while (rem > 0 && prd_idx < idedrv->prdt_entry_count) {
|
||||||
idedrv->current_req = NULL;
|
uint32_t chunk = (rem >= 0x10000) ? 0x10000 : rem;
|
||||||
free(req);
|
idedrv->prdt[prd_idx].phys_addr = phys;
|
||||||
return -ST_XDRV_WRITE_ERROR;
|
idedrv->prdt[prd_idx].size = (uint16_t)chunk;
|
||||||
|
|
||||||
|
rem -= chunk;
|
||||||
|
phys += chunk;
|
||||||
|
|
||||||
|
idedrv->prdt[prd_idx].rsvd_eot = (rem == 0) ? 0x8000 : 0x0000;
|
||||||
|
prd_idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
outl(idedrv->bmbase + IDE_DMA_REG_PRDT, (uint32_t)idedrv->prdt_phys);
|
||||||
|
|
||||||
|
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x00);
|
||||||
|
|
||||||
|
uint8_t status = inb(idedrv->bmbase + IDE_DMA_REG_STATUS);
|
||||||
|
outb(idedrv->bmbase + IDE_DMA_REG_STATUS, status | IDE_DMA_STATUS_INTR | IDE_DMA_STATUS_ERROR);
|
||||||
|
|
||||||
|
ide_prepare(idedrv, sector, sector_count, true);
|
||||||
|
|
||||||
|
uint8_t cmd = idedrv->lba48 ? IDE_CMD_WRITE_DMA48 : IDE_CMD_WRITE_DMA28;
|
||||||
|
outb(idedrv->io + IDE_REG_CMD, cmd);
|
||||||
|
|
||||||
|
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x01);
|
||||||
|
} else {
|
||||||
|
ide_prepare(idedrv, sector, sector_count, true);
|
||||||
|
|
||||||
|
uint8_t cmd = idedrv->lba48 ? IDE_CMD_WRITE48 : IDE_CMD_WRITE28;
|
||||||
|
outb(idedrv->io + IDE_REG_CMD, cmd);
|
||||||
|
|
||||||
|
if (!ide_wait(idedrv->io, 100000, true, true)) {
|
||||||
|
idedrv->current_req = NULL;
|
||||||
|
free(req);
|
||||||
|
return -ST_XDRV_WRITE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
outsw(idedrv->io + IDE_REG_DATA, buffer, idedrv->sector_size / 2);
|
||||||
|
|
||||||
|
req->sector_done_count = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
outsw(idedrv->io + IDE_REG_DATA, buffer, idedrv->sector_size / 2);
|
|
||||||
|
|
||||||
req->sector_done_count = 1;
|
|
||||||
|
|
||||||
biglock_unlock();
|
biglock_unlock();
|
||||||
|
|
||||||
while (!atomic_load(&req->done))
|
while (!atomic_load(&req->done))
|
||||||
@@ -500,52 +579,49 @@ static int idedrv_do_write_no_irqs(struct idedrv* idedrv, size_t sector, size_t
|
|||||||
if (idedrv->bm_support) {
|
if (idedrv->bm_support) {
|
||||||
memcpy(idedrv->bounce_buffer, buffer, sector_count * idedrv->sector_size);
|
memcpy(idedrv->bounce_buffer, buffer, sector_count * idedrv->sector_size);
|
||||||
|
|
||||||
size_t sectors_done = 0;
|
size_t rem = sector_count * idedrv->sector_size;
|
||||||
|
uint32_t phys = idedrv->bounce_buffer_phys;
|
||||||
|
size_t prd_idx = 0;
|
||||||
|
|
||||||
while (sectors_done < sector_count) {
|
while (rem > 0 && idedrv->prdt_entry_count) {
|
||||||
size_t chunk_sectors = sector_count - sectors_done;
|
uint32_t chunk = (rem >= 0x10000) ? 0x10000 : rem;
|
||||||
size_t max_chunk = (16 * PAGE_SIZE) / idedrv->sector_size;
|
idedrv->prdt[prd_idx].phys_addr = phys;
|
||||||
|
idedrv->prdt[prd_idx].size = (uint16_t)chunk;
|
||||||
|
|
||||||
if (chunk_sectors > max_chunk)
|
rem -= chunk;
|
||||||
chunk_sectors = max_chunk;
|
phys += chunk;
|
||||||
|
|
||||||
size_t byte_count = chunk_sectors * idedrv->sector_size;
|
idedrv->prdt[prd_idx].rsvd_eot = (rem == 0) ? 0x8000 : 0x0000;
|
||||||
|
prd_idx++;
|
||||||
|
}
|
||||||
|
|
||||||
idedrv->prdt[0].phys_addr = (uint32_t)idedrv->bounce_buffer_phys;
|
uint8_t status = inb(idedrv->bmbase + IDE_DMA_REG_STATUS);
|
||||||
idedrv->prdt[0].size = (uint16_t)byte_count;
|
outb(idedrv->bmbase + IDE_DMA_REG_STATUS, status | IDE_DMA_STATUS_INTR | IDE_DMA_STATUS_ERROR);
|
||||||
idedrv->prdt[0].rsvd_eot = 0x8000;
|
|
||||||
|
|
||||||
uint8_t status = inb(idedrv->bmbase + IDE_DMA_REG_STATUS);
|
ide_prepare(idedrv, sector, sector_count, false);
|
||||||
outb(idedrv->bmbase + IDE_DMA_REG_STATUS,
|
|
||||||
status | IDE_DMA_STATUS_INTR | IDE_DMA_STATUS_ERROR);
|
|
||||||
|
|
||||||
ide_prepare(idedrv, sector + sectors_done, chunk_sectors, false);
|
uint8_t cmd = idedrv->lba48 ? IDE_CMD_WRITE_DMA48 : IDE_CMD_WRITE_DMA28;
|
||||||
|
outb(idedrv->io + IDE_REG_CMD, cmd);
|
||||||
|
|
||||||
uint8_t cmd = idedrv->lba48 ? IDE_CMD_WRITE_DMA48 : IDE_CMD_WRITE_DMA28;
|
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x01);
|
||||||
outb(idedrv->io + IDE_REG_CMD, cmd);
|
|
||||||
|
|
||||||
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x01);
|
for (;;) {
|
||||||
|
uint8_t bm_status = inb(idedrv->bmbase + IDE_DMA_REG_STATUS);
|
||||||
|
|
||||||
for (;;) {
|
if (!(bm_status & IDE_DMA_STATUS_ACTIVE))
|
||||||
uint8_t bm_status = inb(idedrv->bmbase + IDE_DMA_REG_STATUS);
|
break;
|
||||||
|
|
||||||
if (!(bm_status & IDE_DMA_STATUS_ACTIVE))
|
if ((bm_status & IDE_DMA_STATUS_ERROR)) {
|
||||||
break;
|
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x00);
|
||||||
|
return -ST_XDRV_WRITE_ERROR;
|
||||||
if ((bm_status & IDE_DMA_STATUS_ERROR)) {
|
|
||||||
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x00);
|
|
||||||
return -ST_XDRV_WRITE_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_lock_relax();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x00);
|
spin_lock_relax();
|
||||||
|
|
||||||
inb(idedrv->io + IDE_REG_STATUS);
|
|
||||||
|
|
||||||
sectors_done += chunk_sectors;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outb(idedrv->bmbase + IDE_DMA_REG_CMD, 0x00);
|
||||||
|
|
||||||
|
inb(idedrv->io + IDE_REG_STATUS);
|
||||||
} else {
|
} else {
|
||||||
ide_prepare(idedrv, sector, sector_count, false);
|
ide_prepare(idedrv, sector, sector_count, false);
|
||||||
|
|
||||||
@@ -579,9 +655,6 @@ DEFINE_DEVICE_OP(idedrv_write) {
|
|||||||
if (sector + sector_count > idedrv->sector_count)
|
if (sector + sector_count > idedrv->sector_count)
|
||||||
return -ST_OOB_ERROR;
|
return -ST_OOB_ERROR;
|
||||||
|
|
||||||
if (idedrv->bm_support && ((sector_count * idedrv->sector_size) >= 16 * PAGE_SIZE))
|
|
||||||
return -ST_OOB_ERROR;
|
|
||||||
|
|
||||||
if (idedrv->current_req != NULL)
|
if (idedrv->current_req != NULL)
|
||||||
return -ST_TRY_AGAIN;
|
return -ST_TRY_AGAIN;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user