|
@@ -1741,6 +1741,176 @@ static struct target_core_configfs_attribute target_core_attr_dev_alua_lu_gp = {
|
|
|
.store = target_core_store_alua_lu_gp,
|
|
|
};
|
|
|
|
|
|
+static ssize_t target_core_show_dev_lba_map(void *p, char *page)
|
|
|
+{
|
|
|
+ struct se_device *dev = p;
|
|
|
+ struct t10_alua_lba_map *map;
|
|
|
+ struct t10_alua_lba_map_member *mem;
|
|
|
+ char *b = page;
|
|
|
+ int bl = 0;
|
|
|
+ char state;
|
|
|
+
|
|
|
+ spin_lock(&dev->t10_alua.lba_map_lock);
|
|
|
+ if (!list_empty(&dev->t10_alua.lba_map_list))
|
|
|
+ bl += sprintf(b + bl, "%u %u\n",
|
|
|
+ dev->t10_alua.lba_map_segment_size,
|
|
|
+ dev->t10_alua.lba_map_segment_multiplier);
|
|
|
+ list_for_each_entry(map, &dev->t10_alua.lba_map_list, lba_map_list) {
|
|
|
+ bl += sprintf(b + bl, "%llu %llu",
|
|
|
+ map->lba_map_first_lba, map->lba_map_last_lba);
|
|
|
+ list_for_each_entry(mem, &map->lba_map_mem_list,
|
|
|
+ lba_map_mem_list) {
|
|
|
+ switch (mem->lba_map_mem_alua_state) {
|
|
|
+ case ALUA_ACCESS_STATE_ACTIVE_OPTIMIZED:
|
|
|
+ state = 'O';
|
|
|
+ break;
|
|
|
+ case ALUA_ACCESS_STATE_ACTIVE_NON_OPTIMIZED:
|
|
|
+ state = 'A';
|
|
|
+ break;
|
|
|
+ case ALUA_ACCESS_STATE_STANDBY:
|
|
|
+ state = 'S';
|
|
|
+ break;
|
|
|
+ case ALUA_ACCESS_STATE_UNAVAILABLE:
|
|
|
+ state = 'U';
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ state = '.';
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ bl += sprintf(b + bl, " %d:%c",
|
|
|
+ mem->lba_map_mem_alua_pg_id, state);
|
|
|
+ }
|
|
|
+ bl += sprintf(b + bl, "\n");
|
|
|
+ }
|
|
|
+ spin_unlock(&dev->t10_alua.lba_map_lock);
|
|
|
+ return bl;
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t target_core_store_dev_lba_map(
|
|
|
+ void *p,
|
|
|
+ const char *page,
|
|
|
+ size_t count)
|
|
|
+{
|
|
|
+ struct se_device *dev = p;
|
|
|
+ struct t10_alua_lba_map *lba_map = NULL;
|
|
|
+ struct list_head lba_list;
|
|
|
+ char *map_entries, *ptr;
|
|
|
+ char state;
|
|
|
+ int pg_num = -1, pg;
|
|
|
+ int ret = 0, num = 0, pg_id, alua_state;
|
|
|
+ unsigned long start_lba = -1, end_lba = -1;
|
|
|
+ unsigned long segment_size = -1, segment_mult = -1;
|
|
|
+
|
|
|
+ map_entries = kstrdup(page, GFP_KERNEL);
|
|
|
+ if (!map_entries)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ INIT_LIST_HEAD(&lba_list);
|
|
|
+ while ((ptr = strsep(&map_entries, "\n")) != NULL) {
|
|
|
+ if (!*ptr)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (num == 0) {
|
|
|
+ if (sscanf(ptr, "%lu %lu\n",
|
|
|
+ &segment_size, &segment_mult) != 2) {
|
|
|
+ pr_err("Invalid line %d\n", num);
|
|
|
+ ret = -EINVAL;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ num++;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (sscanf(ptr, "%lu %lu", &start_lba, &end_lba) != 2) {
|
|
|
+ pr_err("Invalid line %d\n", num);
|
|
|
+ ret = -EINVAL;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ ptr = strchr(ptr, ' ');
|
|
|
+ if (!ptr) {
|
|
|
+ pr_err("Invalid line %d, missing end lba\n", num);
|
|
|
+ ret = -EINVAL;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ ptr++;
|
|
|
+ ptr = strchr(ptr, ' ');
|
|
|
+ if (!ptr) {
|
|
|
+ pr_err("Invalid line %d, missing state definitions\n",
|
|
|
+ num);
|
|
|
+ ret = -EINVAL;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ ptr++;
|
|
|
+ lba_map = core_alua_allocate_lba_map(&lba_list,
|
|
|
+ start_lba, end_lba);
|
|
|
+ if (IS_ERR(lba_map)) {
|
|
|
+ ret = PTR_ERR(lba_map);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ pg = 0;
|
|
|
+ while (sscanf(ptr, "%d:%c", &pg_id, &state) == 2) {
|
|
|
+ switch (state) {
|
|
|
+ case 'O':
|
|
|
+ alua_state = ALUA_ACCESS_STATE_ACTIVE_OPTIMIZED;
|
|
|
+ break;
|
|
|
+ case 'A':
|
|
|
+ alua_state = ALUA_ACCESS_STATE_ACTIVE_NON_OPTIMIZED;
|
|
|
+ break;
|
|
|
+ case 'S':
|
|
|
+ alua_state = ALUA_ACCESS_STATE_STANDBY;
|
|
|
+ break;
|
|
|
+ case 'U':
|
|
|
+ alua_state = ALUA_ACCESS_STATE_UNAVAILABLE;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ pr_err("Invalid ALUA state '%c'\n", state);
|
|
|
+ ret = -EINVAL;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = core_alua_allocate_lba_map_mem(lba_map,
|
|
|
+ pg_id, alua_state);
|
|
|
+ if (ret) {
|
|
|
+ pr_err("Invalid target descriptor %d:%c "
|
|
|
+ "at line %d\n",
|
|
|
+ pg_id, state, num);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ pg++;
|
|
|
+ ptr = strchr(ptr, ' ');
|
|
|
+ if (ptr)
|
|
|
+ ptr++;
|
|
|
+ else
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (pg_num == -1)
|
|
|
+ pg_num = pg;
|
|
|
+ else if (pg != pg_num) {
|
|
|
+ pr_err("Only %d from %d port groups definitions "
|
|
|
+ "at line %d\n", pg, pg_num, num);
|
|
|
+ ret = -EINVAL;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ num++;
|
|
|
+ }
|
|
|
+out:
|
|
|
+ if (ret) {
|
|
|
+ core_alua_free_lba_map(&lba_list);
|
|
|
+ count = ret;
|
|
|
+ } else
|
|
|
+ core_alua_set_lba_map(dev, &lba_list,
|
|
|
+ segment_size, segment_mult);
|
|
|
+ kfree(map_entries);
|
|
|
+ return count;
|
|
|
+}
|
|
|
+
|
|
|
+static struct target_core_configfs_attribute target_core_attr_dev_lba_map = {
|
|
|
+ .attr = { .ca_owner = THIS_MODULE,
|
|
|
+ .ca_name = "lba_map",
|
|
|
+ .ca_mode = S_IRUGO | S_IWUSR },
|
|
|
+ .show = target_core_show_dev_lba_map,
|
|
|
+ .store = target_core_store_dev_lba_map,
|
|
|
+};
|
|
|
+
|
|
|
static struct configfs_attribute *lio_core_dev_attrs[] = {
|
|
|
&target_core_attr_dev_info.attr,
|
|
|
&target_core_attr_dev_control.attr,
|
|
@@ -1748,6 +1918,7 @@ static struct configfs_attribute *lio_core_dev_attrs[] = {
|
|
|
&target_core_attr_dev_udev_path.attr,
|
|
|
&target_core_attr_dev_enable.attr,
|
|
|
&target_core_attr_dev_alua_lu_gp.attr,
|
|
|
+ &target_core_attr_dev_lba_map.attr,
|
|
|
NULL,
|
|
|
};
|
|
|
|