diff --git a/drivers/target/target_core_file.c b/drivers/target/target_core_file.c index e1878bfd97b7..7e02304a1024 100644 --- a/drivers/target/target_core_file.c +++ b/drivers/target/target_core_file.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -524,6 +525,115 @@ fd_execute_write_same_unmap(struct se_cmd *cmd) return 0; } +static sense_reason_t +fd_execute_unmap(struct se_cmd *cmd) +{ + struct se_device *dev = cmd->se_dev; + struct fd_dev *fd_dev = FD_DEV(dev); + struct file *file = fd_dev->fd_file; + struct inode *inode = file->f_mapping->host; + unsigned char *buf, *ptr = NULL; + sector_t lba; + int size; + u32 range; + sense_reason_t ret = 0; + int dl, bd_dl, err; + + /* We never set ANC_SUP */ + if (cmd->t_task_cdb[1]) + return TCM_INVALID_CDB_FIELD; + + if (cmd->data_length == 0) { + target_complete_cmd(cmd, SAM_STAT_GOOD); + return 0; + } + + if (cmd->data_length < 8) { + pr_warn("UNMAP parameter list length %u too small\n", + cmd->data_length); + return TCM_PARAMETER_LIST_LENGTH_ERROR; + } + + buf = transport_kmap_data_sg(cmd); + if (!buf) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + + dl = get_unaligned_be16(&buf[0]); + bd_dl = get_unaligned_be16(&buf[2]); + + size = cmd->data_length - 8; + if (bd_dl > size) + pr_warn("UNMAP parameter list length %u too small, ignoring bd_dl %u\n", + cmd->data_length, bd_dl); + else + size = bd_dl; + + if (size / 16 > dev->dev_attrib.max_unmap_block_desc_count) { + ret = TCM_INVALID_PARAMETER_LIST; + goto err; + } + + /* First UNMAP block descriptor starts at 8 byte offset */ + ptr = &buf[8]; + pr_debug("UNMAP: Sub: %s Using dl: %u bd_dl: %u size: %u" + " ptr: %p\n", dev->transport->name, dl, bd_dl, size, ptr); + + while (size >= 16) { + lba = get_unaligned_be64(&ptr[0]); + range = get_unaligned_be32(&ptr[8]); + pr_debug("UNMAP: Using lba: %llu and range: %u\n", + (unsigned long long)lba, range); + + if (range > dev->dev_attrib.max_unmap_lba_count) { + ret = TCM_INVALID_PARAMETER_LIST; + goto err; + } + + if (lba + range > dev->transport->get_blocks(dev) + 1) { + ret = TCM_ADDRESS_OUT_OF_RANGE; + goto err; + } + + if (S_ISBLK(inode->i_mode)) { + /* The backend is block device, use discard */ + struct block_device *bdev = inode->i_bdev; + + err = blkdev_issue_discard(bdev, lba, range, GFP_KERNEL, 0); + if (err < 0) { + pr_err("FILEIO: blkdev_issue_discard() failed: %d\n", + err); + ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + goto err; + } + } else { + /* The backend is normal file, use fallocate */ + loff_t pos = lba * dev->dev_attrib.block_size; + unsigned int len = range * dev->dev_attrib.block_size; + int mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE; + + if (!file->f_op->fallocate) { + ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + goto err; + } + err = file->f_op->fallocate(file, mode, pos, len); + if (err < 0) { + pr_warn("FILEIO: fallocate() failed: %d\n", err); + ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + goto err; + } + } + + ptr += 16; + size -= 16; + } + +err: + transport_kunmap_data_sg(cmd); + if (!ret) + target_complete_cmd(cmd, GOOD); + return ret; +} + static sense_reason_t fd_execute_rw(struct se_cmd *cmd) { @@ -684,6 +794,7 @@ static struct sbc_ops fd_sbc_ops = { .execute_sync_cache = fd_execute_sync_cache, .execute_write_same = fd_execute_write_same, .execute_write_same_unmap = fd_execute_write_same_unmap, + .execute_unmap = fd_execute_unmap, }; static sense_reason_t