162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * dcssblk.c -- the S/390 block driver for dcss memory 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Authors: Carsten Otte, Stefan Weinhuber, Gerald Schaefer 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#define KMSG_COMPONENT "dcssblk" 962306a36Sopenharmony_ci#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/moduleparam.h> 1362306a36Sopenharmony_ci#include <linux/ctype.h> 1462306a36Sopenharmony_ci#include <linux/errno.h> 1562306a36Sopenharmony_ci#include <linux/init.h> 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci#include <linux/blkdev.h> 1862306a36Sopenharmony_ci#include <linux/completion.h> 1962306a36Sopenharmony_ci#include <linux/interrupt.h> 2062306a36Sopenharmony_ci#include <linux/pfn_t.h> 2162306a36Sopenharmony_ci#include <linux/uio.h> 2262306a36Sopenharmony_ci#include <linux/dax.h> 2362306a36Sopenharmony_ci#include <linux/io.h> 2462306a36Sopenharmony_ci#include <asm/extmem.h> 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#define DCSSBLK_NAME "dcssblk" 2762306a36Sopenharmony_ci#define DCSSBLK_MINORS_PER_DISK 1 2862306a36Sopenharmony_ci#define DCSSBLK_PARM_LEN 400 2962306a36Sopenharmony_ci#define DCSS_BUS_ID_SIZE 20 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistatic int dcssblk_open(struct gendisk *disk, blk_mode_t mode); 3262306a36Sopenharmony_cistatic void dcssblk_release(struct gendisk *disk); 3362306a36Sopenharmony_cistatic void dcssblk_submit_bio(struct bio *bio); 3462306a36Sopenharmony_cistatic long dcssblk_dax_direct_access(struct dax_device *dax_dev, pgoff_t pgoff, 3562306a36Sopenharmony_ci long nr_pages, enum dax_access_mode mode, void **kaddr, 3662306a36Sopenharmony_ci pfn_t *pfn); 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic char dcssblk_segments[DCSSBLK_PARM_LEN] = "\0"; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistatic int dcssblk_major; 4162306a36Sopenharmony_cistatic const struct block_device_operations dcssblk_devops = { 4262306a36Sopenharmony_ci .owner = THIS_MODULE, 4362306a36Sopenharmony_ci .submit_bio = dcssblk_submit_bio, 4462306a36Sopenharmony_ci .open = dcssblk_open, 4562306a36Sopenharmony_ci .release = dcssblk_release, 4662306a36Sopenharmony_ci}; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic int dcssblk_dax_zero_page_range(struct dax_device *dax_dev, 4962306a36Sopenharmony_ci pgoff_t pgoff, size_t nr_pages) 5062306a36Sopenharmony_ci{ 5162306a36Sopenharmony_ci long rc; 5262306a36Sopenharmony_ci void *kaddr; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci rc = dax_direct_access(dax_dev, pgoff, nr_pages, DAX_ACCESS, 5562306a36Sopenharmony_ci &kaddr, NULL); 5662306a36Sopenharmony_ci if (rc < 0) 5762306a36Sopenharmony_ci return dax_mem2blk_err(rc); 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci memset(kaddr, 0, nr_pages << PAGE_SHIFT); 6062306a36Sopenharmony_ci dax_flush(dax_dev, kaddr, nr_pages << PAGE_SHIFT); 6162306a36Sopenharmony_ci return 0; 6262306a36Sopenharmony_ci} 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_cistatic const struct dax_operations dcssblk_dax_ops = { 6562306a36Sopenharmony_ci .direct_access = dcssblk_dax_direct_access, 6662306a36Sopenharmony_ci .zero_page_range = dcssblk_dax_zero_page_range, 6762306a36Sopenharmony_ci}; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistruct dcssblk_dev_info { 7062306a36Sopenharmony_ci struct list_head lh; 7162306a36Sopenharmony_ci struct device dev; 7262306a36Sopenharmony_ci char segment_name[DCSS_BUS_ID_SIZE]; 7362306a36Sopenharmony_ci atomic_t use_count; 7462306a36Sopenharmony_ci struct gendisk *gd; 7562306a36Sopenharmony_ci unsigned long start; 7662306a36Sopenharmony_ci unsigned long end; 7762306a36Sopenharmony_ci int segment_type; 7862306a36Sopenharmony_ci unsigned char save_pending; 7962306a36Sopenharmony_ci unsigned char is_shared; 8062306a36Sopenharmony_ci int num_of_segments; 8162306a36Sopenharmony_ci struct list_head seg_list; 8262306a36Sopenharmony_ci struct dax_device *dax_dev; 8362306a36Sopenharmony_ci}; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistruct segment_info { 8662306a36Sopenharmony_ci struct list_head lh; 8762306a36Sopenharmony_ci char segment_name[DCSS_BUS_ID_SIZE]; 8862306a36Sopenharmony_ci unsigned long start; 8962306a36Sopenharmony_ci unsigned long end; 9062306a36Sopenharmony_ci int segment_type; 9162306a36Sopenharmony_ci}; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_cistatic ssize_t dcssblk_add_store(struct device * dev, struct device_attribute *attr, const char * buf, 9462306a36Sopenharmony_ci size_t count); 9562306a36Sopenharmony_cistatic ssize_t dcssblk_remove_store(struct device * dev, struct device_attribute *attr, const char * buf, 9662306a36Sopenharmony_ci size_t count); 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistatic DEVICE_ATTR(add, S_IWUSR, NULL, dcssblk_add_store); 9962306a36Sopenharmony_cistatic DEVICE_ATTR(remove, S_IWUSR, NULL, dcssblk_remove_store); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_cistatic struct device *dcssblk_root_dev; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic LIST_HEAD(dcssblk_devices); 10462306a36Sopenharmony_cistatic struct rw_semaphore dcssblk_devices_sem; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci/* 10762306a36Sopenharmony_ci * release function for segment device. 10862306a36Sopenharmony_ci */ 10962306a36Sopenharmony_cistatic void 11062306a36Sopenharmony_cidcssblk_release_segment(struct device *dev) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci struct dcssblk_dev_info *dev_info; 11362306a36Sopenharmony_ci struct segment_info *entry, *temp; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci dev_info = container_of(dev, struct dcssblk_dev_info, dev); 11662306a36Sopenharmony_ci list_for_each_entry_safe(entry, temp, &dev_info->seg_list, lh) { 11762306a36Sopenharmony_ci list_del(&entry->lh); 11862306a36Sopenharmony_ci kfree(entry); 11962306a36Sopenharmony_ci } 12062306a36Sopenharmony_ci kfree(dev_info); 12162306a36Sopenharmony_ci module_put(THIS_MODULE); 12262306a36Sopenharmony_ci} 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci/* 12562306a36Sopenharmony_ci * get a minor number. needs to be called with 12662306a36Sopenharmony_ci * down_write(&dcssblk_devices_sem) and the 12762306a36Sopenharmony_ci * device needs to be enqueued before the semaphore is 12862306a36Sopenharmony_ci * freed. 12962306a36Sopenharmony_ci */ 13062306a36Sopenharmony_cistatic int 13162306a36Sopenharmony_cidcssblk_assign_free_minor(struct dcssblk_dev_info *dev_info) 13262306a36Sopenharmony_ci{ 13362306a36Sopenharmony_ci int minor, found; 13462306a36Sopenharmony_ci struct dcssblk_dev_info *entry; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci if (dev_info == NULL) 13762306a36Sopenharmony_ci return -EINVAL; 13862306a36Sopenharmony_ci for (minor = 0; minor < (1<<MINORBITS); minor++) { 13962306a36Sopenharmony_ci found = 0; 14062306a36Sopenharmony_ci // test if minor available 14162306a36Sopenharmony_ci list_for_each_entry(entry, &dcssblk_devices, lh) 14262306a36Sopenharmony_ci if (minor == entry->gd->first_minor) 14362306a36Sopenharmony_ci found++; 14462306a36Sopenharmony_ci if (!found) break; // got unused minor 14562306a36Sopenharmony_ci } 14662306a36Sopenharmony_ci if (found) 14762306a36Sopenharmony_ci return -EBUSY; 14862306a36Sopenharmony_ci dev_info->gd->first_minor = minor; 14962306a36Sopenharmony_ci return 0; 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci/* 15362306a36Sopenharmony_ci * get the struct dcssblk_dev_info from dcssblk_devices 15462306a36Sopenharmony_ci * for the given name. 15562306a36Sopenharmony_ci * down_read(&dcssblk_devices_sem) must be held. 15662306a36Sopenharmony_ci */ 15762306a36Sopenharmony_cistatic struct dcssblk_dev_info * 15862306a36Sopenharmony_cidcssblk_get_device_by_name(char *name) 15962306a36Sopenharmony_ci{ 16062306a36Sopenharmony_ci struct dcssblk_dev_info *entry; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci list_for_each_entry(entry, &dcssblk_devices, lh) { 16362306a36Sopenharmony_ci if (!strcmp(name, entry->segment_name)) { 16462306a36Sopenharmony_ci return entry; 16562306a36Sopenharmony_ci } 16662306a36Sopenharmony_ci } 16762306a36Sopenharmony_ci return NULL; 16862306a36Sopenharmony_ci} 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci/* 17162306a36Sopenharmony_ci * get the struct segment_info from seg_list 17262306a36Sopenharmony_ci * for the given name. 17362306a36Sopenharmony_ci * down_read(&dcssblk_devices_sem) must be held. 17462306a36Sopenharmony_ci */ 17562306a36Sopenharmony_cistatic struct segment_info * 17662306a36Sopenharmony_cidcssblk_get_segment_by_name(char *name) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci struct dcssblk_dev_info *dev_info; 17962306a36Sopenharmony_ci struct segment_info *entry; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci list_for_each_entry(dev_info, &dcssblk_devices, lh) { 18262306a36Sopenharmony_ci list_for_each_entry(entry, &dev_info->seg_list, lh) { 18362306a36Sopenharmony_ci if (!strcmp(name, entry->segment_name)) 18462306a36Sopenharmony_ci return entry; 18562306a36Sopenharmony_ci } 18662306a36Sopenharmony_ci } 18762306a36Sopenharmony_ci return NULL; 18862306a36Sopenharmony_ci} 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci/* 19162306a36Sopenharmony_ci * get the highest address of the multi-segment block. 19262306a36Sopenharmony_ci */ 19362306a36Sopenharmony_cistatic unsigned long 19462306a36Sopenharmony_cidcssblk_find_highest_addr(struct dcssblk_dev_info *dev_info) 19562306a36Sopenharmony_ci{ 19662306a36Sopenharmony_ci unsigned long highest_addr; 19762306a36Sopenharmony_ci struct segment_info *entry; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci highest_addr = 0; 20062306a36Sopenharmony_ci list_for_each_entry(entry, &dev_info->seg_list, lh) { 20162306a36Sopenharmony_ci if (highest_addr < entry->end) 20262306a36Sopenharmony_ci highest_addr = entry->end; 20362306a36Sopenharmony_ci } 20462306a36Sopenharmony_ci return highest_addr; 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci/* 20862306a36Sopenharmony_ci * get the lowest address of the multi-segment block. 20962306a36Sopenharmony_ci */ 21062306a36Sopenharmony_cistatic unsigned long 21162306a36Sopenharmony_cidcssblk_find_lowest_addr(struct dcssblk_dev_info *dev_info) 21262306a36Sopenharmony_ci{ 21362306a36Sopenharmony_ci int set_first; 21462306a36Sopenharmony_ci unsigned long lowest_addr; 21562306a36Sopenharmony_ci struct segment_info *entry; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci set_first = 0; 21862306a36Sopenharmony_ci lowest_addr = 0; 21962306a36Sopenharmony_ci list_for_each_entry(entry, &dev_info->seg_list, lh) { 22062306a36Sopenharmony_ci if (set_first == 0) { 22162306a36Sopenharmony_ci lowest_addr = entry->start; 22262306a36Sopenharmony_ci set_first = 1; 22362306a36Sopenharmony_ci } else { 22462306a36Sopenharmony_ci if (lowest_addr > entry->start) 22562306a36Sopenharmony_ci lowest_addr = entry->start; 22662306a36Sopenharmony_ci } 22762306a36Sopenharmony_ci } 22862306a36Sopenharmony_ci return lowest_addr; 22962306a36Sopenharmony_ci} 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci/* 23262306a36Sopenharmony_ci * Check continuity of segments. 23362306a36Sopenharmony_ci */ 23462306a36Sopenharmony_cistatic int 23562306a36Sopenharmony_cidcssblk_is_continuous(struct dcssblk_dev_info *dev_info) 23662306a36Sopenharmony_ci{ 23762306a36Sopenharmony_ci int i, j, rc; 23862306a36Sopenharmony_ci struct segment_info *sort_list, *entry, temp; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci if (dev_info->num_of_segments <= 1) 24162306a36Sopenharmony_ci return 0; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci sort_list = kcalloc(dev_info->num_of_segments, 24462306a36Sopenharmony_ci sizeof(struct segment_info), 24562306a36Sopenharmony_ci GFP_KERNEL); 24662306a36Sopenharmony_ci if (sort_list == NULL) 24762306a36Sopenharmony_ci return -ENOMEM; 24862306a36Sopenharmony_ci i = 0; 24962306a36Sopenharmony_ci list_for_each_entry(entry, &dev_info->seg_list, lh) { 25062306a36Sopenharmony_ci memcpy(&sort_list[i], entry, sizeof(struct segment_info)); 25162306a36Sopenharmony_ci i++; 25262306a36Sopenharmony_ci } 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci /* sort segments */ 25562306a36Sopenharmony_ci for (i = 0; i < dev_info->num_of_segments; i++) 25662306a36Sopenharmony_ci for (j = 0; j < dev_info->num_of_segments; j++) 25762306a36Sopenharmony_ci if (sort_list[j].start > sort_list[i].start) { 25862306a36Sopenharmony_ci memcpy(&temp, &sort_list[i], 25962306a36Sopenharmony_ci sizeof(struct segment_info)); 26062306a36Sopenharmony_ci memcpy(&sort_list[i], &sort_list[j], 26162306a36Sopenharmony_ci sizeof(struct segment_info)); 26262306a36Sopenharmony_ci memcpy(&sort_list[j], &temp, 26362306a36Sopenharmony_ci sizeof(struct segment_info)); 26462306a36Sopenharmony_ci } 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci /* check continuity */ 26762306a36Sopenharmony_ci for (i = 0; i < dev_info->num_of_segments - 1; i++) { 26862306a36Sopenharmony_ci if ((sort_list[i].end + 1) != sort_list[i+1].start) { 26962306a36Sopenharmony_ci pr_err("Adjacent DCSSs %s and %s are not " 27062306a36Sopenharmony_ci "contiguous\n", sort_list[i].segment_name, 27162306a36Sopenharmony_ci sort_list[i+1].segment_name); 27262306a36Sopenharmony_ci rc = -EINVAL; 27362306a36Sopenharmony_ci goto out; 27462306a36Sopenharmony_ci } 27562306a36Sopenharmony_ci /* EN and EW are allowed in a block device */ 27662306a36Sopenharmony_ci if (sort_list[i].segment_type != sort_list[i+1].segment_type) { 27762306a36Sopenharmony_ci if (!(sort_list[i].segment_type & SEGMENT_EXCLUSIVE) || 27862306a36Sopenharmony_ci (sort_list[i].segment_type == SEG_TYPE_ER) || 27962306a36Sopenharmony_ci !(sort_list[i+1].segment_type & 28062306a36Sopenharmony_ci SEGMENT_EXCLUSIVE) || 28162306a36Sopenharmony_ci (sort_list[i+1].segment_type == SEG_TYPE_ER)) { 28262306a36Sopenharmony_ci pr_err("DCSS %s and DCSS %s have " 28362306a36Sopenharmony_ci "incompatible types\n", 28462306a36Sopenharmony_ci sort_list[i].segment_name, 28562306a36Sopenharmony_ci sort_list[i+1].segment_name); 28662306a36Sopenharmony_ci rc = -EINVAL; 28762306a36Sopenharmony_ci goto out; 28862306a36Sopenharmony_ci } 28962306a36Sopenharmony_ci } 29062306a36Sopenharmony_ci } 29162306a36Sopenharmony_ci rc = 0; 29262306a36Sopenharmony_ciout: 29362306a36Sopenharmony_ci kfree(sort_list); 29462306a36Sopenharmony_ci return rc; 29562306a36Sopenharmony_ci} 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci/* 29862306a36Sopenharmony_ci * Load a segment 29962306a36Sopenharmony_ci */ 30062306a36Sopenharmony_cistatic int 30162306a36Sopenharmony_cidcssblk_load_segment(char *name, struct segment_info **seg_info) 30262306a36Sopenharmony_ci{ 30362306a36Sopenharmony_ci int rc; 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci /* already loaded? */ 30662306a36Sopenharmony_ci down_read(&dcssblk_devices_sem); 30762306a36Sopenharmony_ci *seg_info = dcssblk_get_segment_by_name(name); 30862306a36Sopenharmony_ci up_read(&dcssblk_devices_sem); 30962306a36Sopenharmony_ci if (*seg_info != NULL) 31062306a36Sopenharmony_ci return -EEXIST; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci /* get a struct segment_info */ 31362306a36Sopenharmony_ci *seg_info = kzalloc(sizeof(struct segment_info), GFP_KERNEL); 31462306a36Sopenharmony_ci if (*seg_info == NULL) 31562306a36Sopenharmony_ci return -ENOMEM; 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci strcpy((*seg_info)->segment_name, name); 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci /* load the segment */ 32062306a36Sopenharmony_ci rc = segment_load(name, SEGMENT_SHARED, 32162306a36Sopenharmony_ci &(*seg_info)->start, &(*seg_info)->end); 32262306a36Sopenharmony_ci if (rc < 0) { 32362306a36Sopenharmony_ci segment_warning(rc, (*seg_info)->segment_name); 32462306a36Sopenharmony_ci kfree(*seg_info); 32562306a36Sopenharmony_ci } else { 32662306a36Sopenharmony_ci INIT_LIST_HEAD(&(*seg_info)->lh); 32762306a36Sopenharmony_ci (*seg_info)->segment_type = rc; 32862306a36Sopenharmony_ci } 32962306a36Sopenharmony_ci return rc; 33062306a36Sopenharmony_ci} 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci/* 33362306a36Sopenharmony_ci * device attribute for switching shared/nonshared (exclusive) 33462306a36Sopenharmony_ci * operation (show + store) 33562306a36Sopenharmony_ci */ 33662306a36Sopenharmony_cistatic ssize_t 33762306a36Sopenharmony_cidcssblk_shared_show(struct device *dev, struct device_attribute *attr, char *buf) 33862306a36Sopenharmony_ci{ 33962306a36Sopenharmony_ci struct dcssblk_dev_info *dev_info; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci dev_info = container_of(dev, struct dcssblk_dev_info, dev); 34262306a36Sopenharmony_ci return sprintf(buf, dev_info->is_shared ? "1\n" : "0\n"); 34362306a36Sopenharmony_ci} 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_cistatic ssize_t 34662306a36Sopenharmony_cidcssblk_shared_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count) 34762306a36Sopenharmony_ci{ 34862306a36Sopenharmony_ci struct dcssblk_dev_info *dev_info; 34962306a36Sopenharmony_ci struct segment_info *entry, *temp; 35062306a36Sopenharmony_ci int rc; 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0')) 35362306a36Sopenharmony_ci return -EINVAL; 35462306a36Sopenharmony_ci down_write(&dcssblk_devices_sem); 35562306a36Sopenharmony_ci dev_info = container_of(dev, struct dcssblk_dev_info, dev); 35662306a36Sopenharmony_ci if (atomic_read(&dev_info->use_count)) { 35762306a36Sopenharmony_ci rc = -EBUSY; 35862306a36Sopenharmony_ci goto out; 35962306a36Sopenharmony_ci } 36062306a36Sopenharmony_ci if (inbuf[0] == '1') { 36162306a36Sopenharmony_ci /* reload segments in shared mode */ 36262306a36Sopenharmony_ci list_for_each_entry(entry, &dev_info->seg_list, lh) { 36362306a36Sopenharmony_ci rc = segment_modify_shared(entry->segment_name, 36462306a36Sopenharmony_ci SEGMENT_SHARED); 36562306a36Sopenharmony_ci if (rc < 0) { 36662306a36Sopenharmony_ci BUG_ON(rc == -EINVAL); 36762306a36Sopenharmony_ci if (rc != -EAGAIN) 36862306a36Sopenharmony_ci goto removeseg; 36962306a36Sopenharmony_ci } 37062306a36Sopenharmony_ci } 37162306a36Sopenharmony_ci dev_info->is_shared = 1; 37262306a36Sopenharmony_ci switch (dev_info->segment_type) { 37362306a36Sopenharmony_ci case SEG_TYPE_SR: 37462306a36Sopenharmony_ci case SEG_TYPE_ER: 37562306a36Sopenharmony_ci case SEG_TYPE_SC: 37662306a36Sopenharmony_ci set_disk_ro(dev_info->gd, 1); 37762306a36Sopenharmony_ci } 37862306a36Sopenharmony_ci } else if (inbuf[0] == '0') { 37962306a36Sopenharmony_ci /* reload segments in exclusive mode */ 38062306a36Sopenharmony_ci if (dev_info->segment_type == SEG_TYPE_SC) { 38162306a36Sopenharmony_ci pr_err("DCSS %s is of type SC and cannot be " 38262306a36Sopenharmony_ci "loaded as exclusive-writable\n", 38362306a36Sopenharmony_ci dev_info->segment_name); 38462306a36Sopenharmony_ci rc = -EINVAL; 38562306a36Sopenharmony_ci goto out; 38662306a36Sopenharmony_ci } 38762306a36Sopenharmony_ci list_for_each_entry(entry, &dev_info->seg_list, lh) { 38862306a36Sopenharmony_ci rc = segment_modify_shared(entry->segment_name, 38962306a36Sopenharmony_ci SEGMENT_EXCLUSIVE); 39062306a36Sopenharmony_ci if (rc < 0) { 39162306a36Sopenharmony_ci BUG_ON(rc == -EINVAL); 39262306a36Sopenharmony_ci if (rc != -EAGAIN) 39362306a36Sopenharmony_ci goto removeseg; 39462306a36Sopenharmony_ci } 39562306a36Sopenharmony_ci } 39662306a36Sopenharmony_ci dev_info->is_shared = 0; 39762306a36Sopenharmony_ci set_disk_ro(dev_info->gd, 0); 39862306a36Sopenharmony_ci } else { 39962306a36Sopenharmony_ci rc = -EINVAL; 40062306a36Sopenharmony_ci goto out; 40162306a36Sopenharmony_ci } 40262306a36Sopenharmony_ci rc = count; 40362306a36Sopenharmony_ci goto out; 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ciremoveseg: 40662306a36Sopenharmony_ci pr_err("DCSS device %s is removed after a failed access mode " 40762306a36Sopenharmony_ci "change\n", dev_info->segment_name); 40862306a36Sopenharmony_ci temp = entry; 40962306a36Sopenharmony_ci list_for_each_entry(entry, &dev_info->seg_list, lh) { 41062306a36Sopenharmony_ci if (entry != temp) 41162306a36Sopenharmony_ci segment_unload(entry->segment_name); 41262306a36Sopenharmony_ci } 41362306a36Sopenharmony_ci list_del(&dev_info->lh); 41462306a36Sopenharmony_ci up_write(&dcssblk_devices_sem); 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci dax_remove_host(dev_info->gd); 41762306a36Sopenharmony_ci kill_dax(dev_info->dax_dev); 41862306a36Sopenharmony_ci put_dax(dev_info->dax_dev); 41962306a36Sopenharmony_ci del_gendisk(dev_info->gd); 42062306a36Sopenharmony_ci put_disk(dev_info->gd); 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci if (device_remove_file_self(dev, attr)) { 42362306a36Sopenharmony_ci device_unregister(dev); 42462306a36Sopenharmony_ci put_device(dev); 42562306a36Sopenharmony_ci } 42662306a36Sopenharmony_ci return rc; 42762306a36Sopenharmony_ciout: 42862306a36Sopenharmony_ci up_write(&dcssblk_devices_sem); 42962306a36Sopenharmony_ci return rc; 43062306a36Sopenharmony_ci} 43162306a36Sopenharmony_cistatic DEVICE_ATTR(shared, S_IWUSR | S_IRUSR, dcssblk_shared_show, 43262306a36Sopenharmony_ci dcssblk_shared_store); 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci/* 43562306a36Sopenharmony_ci * device attribute for save operation on current copy 43662306a36Sopenharmony_ci * of the segment. If the segment is busy, saving will 43762306a36Sopenharmony_ci * become pending until it gets released, which can be 43862306a36Sopenharmony_ci * undone by storing a non-true value to this entry. 43962306a36Sopenharmony_ci * (show + store) 44062306a36Sopenharmony_ci */ 44162306a36Sopenharmony_cistatic ssize_t 44262306a36Sopenharmony_cidcssblk_save_show(struct device *dev, struct device_attribute *attr, char *buf) 44362306a36Sopenharmony_ci{ 44462306a36Sopenharmony_ci struct dcssblk_dev_info *dev_info; 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci dev_info = container_of(dev, struct dcssblk_dev_info, dev); 44762306a36Sopenharmony_ci return sprintf(buf, dev_info->save_pending ? "1\n" : "0\n"); 44862306a36Sopenharmony_ci} 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_cistatic ssize_t 45162306a36Sopenharmony_cidcssblk_save_store(struct device *dev, struct device_attribute *attr, const char *inbuf, size_t count) 45262306a36Sopenharmony_ci{ 45362306a36Sopenharmony_ci struct dcssblk_dev_info *dev_info; 45462306a36Sopenharmony_ci struct segment_info *entry; 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0')) 45762306a36Sopenharmony_ci return -EINVAL; 45862306a36Sopenharmony_ci dev_info = container_of(dev, struct dcssblk_dev_info, dev); 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci down_write(&dcssblk_devices_sem); 46162306a36Sopenharmony_ci if (inbuf[0] == '1') { 46262306a36Sopenharmony_ci if (atomic_read(&dev_info->use_count) == 0) { 46362306a36Sopenharmony_ci // device is idle => we save immediately 46462306a36Sopenharmony_ci pr_info("All DCSSs that map to device %s are " 46562306a36Sopenharmony_ci "saved\n", dev_info->segment_name); 46662306a36Sopenharmony_ci list_for_each_entry(entry, &dev_info->seg_list, lh) { 46762306a36Sopenharmony_ci if (entry->segment_type == SEG_TYPE_EN || 46862306a36Sopenharmony_ci entry->segment_type == SEG_TYPE_SN) 46962306a36Sopenharmony_ci pr_warn("DCSS %s is of type SN or EN" 47062306a36Sopenharmony_ci " and cannot be saved\n", 47162306a36Sopenharmony_ci entry->segment_name); 47262306a36Sopenharmony_ci else 47362306a36Sopenharmony_ci segment_save(entry->segment_name); 47462306a36Sopenharmony_ci } 47562306a36Sopenharmony_ci } else { 47662306a36Sopenharmony_ci // device is busy => we save it when it becomes 47762306a36Sopenharmony_ci // idle in dcssblk_release 47862306a36Sopenharmony_ci pr_info("Device %s is in use, its DCSSs will be " 47962306a36Sopenharmony_ci "saved when it becomes idle\n", 48062306a36Sopenharmony_ci dev_info->segment_name); 48162306a36Sopenharmony_ci dev_info->save_pending = 1; 48262306a36Sopenharmony_ci } 48362306a36Sopenharmony_ci } else if (inbuf[0] == '0') { 48462306a36Sopenharmony_ci if (dev_info->save_pending) { 48562306a36Sopenharmony_ci // device is busy & the user wants to undo his save 48662306a36Sopenharmony_ci // request 48762306a36Sopenharmony_ci dev_info->save_pending = 0; 48862306a36Sopenharmony_ci pr_info("A pending save request for device %s " 48962306a36Sopenharmony_ci "has been canceled\n", 49062306a36Sopenharmony_ci dev_info->segment_name); 49162306a36Sopenharmony_ci } 49262306a36Sopenharmony_ci } else { 49362306a36Sopenharmony_ci up_write(&dcssblk_devices_sem); 49462306a36Sopenharmony_ci return -EINVAL; 49562306a36Sopenharmony_ci } 49662306a36Sopenharmony_ci up_write(&dcssblk_devices_sem); 49762306a36Sopenharmony_ci return count; 49862306a36Sopenharmony_ci} 49962306a36Sopenharmony_cistatic DEVICE_ATTR(save, S_IWUSR | S_IRUSR, dcssblk_save_show, 50062306a36Sopenharmony_ci dcssblk_save_store); 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci/* 50362306a36Sopenharmony_ci * device attribute for showing all segments in a device 50462306a36Sopenharmony_ci */ 50562306a36Sopenharmony_cistatic ssize_t 50662306a36Sopenharmony_cidcssblk_seglist_show(struct device *dev, struct device_attribute *attr, 50762306a36Sopenharmony_ci char *buf) 50862306a36Sopenharmony_ci{ 50962306a36Sopenharmony_ci int i; 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci struct dcssblk_dev_info *dev_info; 51262306a36Sopenharmony_ci struct segment_info *entry; 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci down_read(&dcssblk_devices_sem); 51562306a36Sopenharmony_ci dev_info = container_of(dev, struct dcssblk_dev_info, dev); 51662306a36Sopenharmony_ci i = 0; 51762306a36Sopenharmony_ci buf[0] = '\0'; 51862306a36Sopenharmony_ci list_for_each_entry(entry, &dev_info->seg_list, lh) { 51962306a36Sopenharmony_ci strcpy(&buf[i], entry->segment_name); 52062306a36Sopenharmony_ci i += strlen(entry->segment_name); 52162306a36Sopenharmony_ci buf[i] = '\n'; 52262306a36Sopenharmony_ci i++; 52362306a36Sopenharmony_ci } 52462306a36Sopenharmony_ci up_read(&dcssblk_devices_sem); 52562306a36Sopenharmony_ci return i; 52662306a36Sopenharmony_ci} 52762306a36Sopenharmony_cistatic DEVICE_ATTR(seglist, S_IRUSR, dcssblk_seglist_show, NULL); 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_cistatic struct attribute *dcssblk_dev_attrs[] = { 53062306a36Sopenharmony_ci &dev_attr_shared.attr, 53162306a36Sopenharmony_ci &dev_attr_save.attr, 53262306a36Sopenharmony_ci &dev_attr_seglist.attr, 53362306a36Sopenharmony_ci NULL, 53462306a36Sopenharmony_ci}; 53562306a36Sopenharmony_cistatic struct attribute_group dcssblk_dev_attr_group = { 53662306a36Sopenharmony_ci .attrs = dcssblk_dev_attrs, 53762306a36Sopenharmony_ci}; 53862306a36Sopenharmony_cistatic const struct attribute_group *dcssblk_dev_attr_groups[] = { 53962306a36Sopenharmony_ci &dcssblk_dev_attr_group, 54062306a36Sopenharmony_ci NULL, 54162306a36Sopenharmony_ci}; 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci/* 54462306a36Sopenharmony_ci * device attribute for adding devices 54562306a36Sopenharmony_ci */ 54662306a36Sopenharmony_cistatic ssize_t 54762306a36Sopenharmony_cidcssblk_add_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) 54862306a36Sopenharmony_ci{ 54962306a36Sopenharmony_ci int rc, i, j, num_of_segments; 55062306a36Sopenharmony_ci struct dcssblk_dev_info *dev_info; 55162306a36Sopenharmony_ci struct segment_info *seg_info, *temp; 55262306a36Sopenharmony_ci char *local_buf; 55362306a36Sopenharmony_ci unsigned long seg_byte_size; 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci dev_info = NULL; 55662306a36Sopenharmony_ci seg_info = NULL; 55762306a36Sopenharmony_ci if (dev != dcssblk_root_dev) { 55862306a36Sopenharmony_ci rc = -EINVAL; 55962306a36Sopenharmony_ci goto out_nobuf; 56062306a36Sopenharmony_ci } 56162306a36Sopenharmony_ci if ((count < 1) || (buf[0] == '\0') || (buf[0] == '\n')) { 56262306a36Sopenharmony_ci rc = -ENAMETOOLONG; 56362306a36Sopenharmony_ci goto out_nobuf; 56462306a36Sopenharmony_ci } 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci local_buf = kmalloc(count + 1, GFP_KERNEL); 56762306a36Sopenharmony_ci if (local_buf == NULL) { 56862306a36Sopenharmony_ci rc = -ENOMEM; 56962306a36Sopenharmony_ci goto out_nobuf; 57062306a36Sopenharmony_ci } 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci /* 57362306a36Sopenharmony_ci * parse input 57462306a36Sopenharmony_ci */ 57562306a36Sopenharmony_ci num_of_segments = 0; 57662306a36Sopenharmony_ci for (i = 0; (i < count && (buf[i] != '\0') && (buf[i] != '\n')); i++) { 57762306a36Sopenharmony_ci for (j = i; j < count && 57862306a36Sopenharmony_ci (buf[j] != ':') && 57962306a36Sopenharmony_ci (buf[j] != '\0') && 58062306a36Sopenharmony_ci (buf[j] != '\n'); j++) { 58162306a36Sopenharmony_ci local_buf[j-i] = toupper(buf[j]); 58262306a36Sopenharmony_ci } 58362306a36Sopenharmony_ci local_buf[j-i] = '\0'; 58462306a36Sopenharmony_ci if (((j - i) == 0) || ((j - i) > 8)) { 58562306a36Sopenharmony_ci rc = -ENAMETOOLONG; 58662306a36Sopenharmony_ci goto seg_list_del; 58762306a36Sopenharmony_ci } 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci rc = dcssblk_load_segment(local_buf, &seg_info); 59062306a36Sopenharmony_ci if (rc < 0) 59162306a36Sopenharmony_ci goto seg_list_del; 59262306a36Sopenharmony_ci /* 59362306a36Sopenharmony_ci * get a struct dcssblk_dev_info 59462306a36Sopenharmony_ci */ 59562306a36Sopenharmony_ci if (num_of_segments == 0) { 59662306a36Sopenharmony_ci dev_info = kzalloc(sizeof(struct dcssblk_dev_info), 59762306a36Sopenharmony_ci GFP_KERNEL); 59862306a36Sopenharmony_ci if (dev_info == NULL) { 59962306a36Sopenharmony_ci rc = -ENOMEM; 60062306a36Sopenharmony_ci goto out; 60162306a36Sopenharmony_ci } 60262306a36Sopenharmony_ci strcpy(dev_info->segment_name, local_buf); 60362306a36Sopenharmony_ci dev_info->segment_type = seg_info->segment_type; 60462306a36Sopenharmony_ci INIT_LIST_HEAD(&dev_info->seg_list); 60562306a36Sopenharmony_ci } 60662306a36Sopenharmony_ci list_add_tail(&seg_info->lh, &dev_info->seg_list); 60762306a36Sopenharmony_ci num_of_segments++; 60862306a36Sopenharmony_ci i = j; 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci if ((buf[j] == '\0') || (buf[j] == '\n')) 61162306a36Sopenharmony_ci break; 61262306a36Sopenharmony_ci } 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci /* no trailing colon at the end of the input */ 61562306a36Sopenharmony_ci if ((i > 0) && (buf[i-1] == ':')) { 61662306a36Sopenharmony_ci rc = -ENAMETOOLONG; 61762306a36Sopenharmony_ci goto seg_list_del; 61862306a36Sopenharmony_ci } 61962306a36Sopenharmony_ci strscpy(local_buf, buf, i + 1); 62062306a36Sopenharmony_ci dev_info->num_of_segments = num_of_segments; 62162306a36Sopenharmony_ci rc = dcssblk_is_continuous(dev_info); 62262306a36Sopenharmony_ci if (rc < 0) 62362306a36Sopenharmony_ci goto seg_list_del; 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci dev_info->start = dcssblk_find_lowest_addr(dev_info); 62662306a36Sopenharmony_ci dev_info->end = dcssblk_find_highest_addr(dev_info); 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci dev_set_name(&dev_info->dev, "%s", dev_info->segment_name); 62962306a36Sopenharmony_ci dev_info->dev.release = dcssblk_release_segment; 63062306a36Sopenharmony_ci dev_info->dev.groups = dcssblk_dev_attr_groups; 63162306a36Sopenharmony_ci INIT_LIST_HEAD(&dev_info->lh); 63262306a36Sopenharmony_ci dev_info->gd = blk_alloc_disk(NUMA_NO_NODE); 63362306a36Sopenharmony_ci if (dev_info->gd == NULL) { 63462306a36Sopenharmony_ci rc = -ENOMEM; 63562306a36Sopenharmony_ci goto seg_list_del; 63662306a36Sopenharmony_ci } 63762306a36Sopenharmony_ci dev_info->gd->major = dcssblk_major; 63862306a36Sopenharmony_ci dev_info->gd->minors = DCSSBLK_MINORS_PER_DISK; 63962306a36Sopenharmony_ci dev_info->gd->fops = &dcssblk_devops; 64062306a36Sopenharmony_ci dev_info->gd->private_data = dev_info; 64162306a36Sopenharmony_ci dev_info->gd->flags |= GENHD_FL_NO_PART; 64262306a36Sopenharmony_ci blk_queue_logical_block_size(dev_info->gd->queue, 4096); 64362306a36Sopenharmony_ci blk_queue_flag_set(QUEUE_FLAG_DAX, dev_info->gd->queue); 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci seg_byte_size = (dev_info->end - dev_info->start + 1); 64662306a36Sopenharmony_ci set_capacity(dev_info->gd, seg_byte_size >> 9); // size in sectors 64762306a36Sopenharmony_ci pr_info("Loaded %s with total size %lu bytes and capacity %lu " 64862306a36Sopenharmony_ci "sectors\n", local_buf, seg_byte_size, seg_byte_size >> 9); 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci dev_info->save_pending = 0; 65162306a36Sopenharmony_ci dev_info->is_shared = 1; 65262306a36Sopenharmony_ci dev_info->dev.parent = dcssblk_root_dev; 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci /* 65562306a36Sopenharmony_ci *get minor, add to list 65662306a36Sopenharmony_ci */ 65762306a36Sopenharmony_ci down_write(&dcssblk_devices_sem); 65862306a36Sopenharmony_ci if (dcssblk_get_segment_by_name(local_buf)) { 65962306a36Sopenharmony_ci rc = -EEXIST; 66062306a36Sopenharmony_ci goto release_gd; 66162306a36Sopenharmony_ci } 66262306a36Sopenharmony_ci rc = dcssblk_assign_free_minor(dev_info); 66362306a36Sopenharmony_ci if (rc) 66462306a36Sopenharmony_ci goto release_gd; 66562306a36Sopenharmony_ci sprintf(dev_info->gd->disk_name, "dcssblk%d", 66662306a36Sopenharmony_ci dev_info->gd->first_minor); 66762306a36Sopenharmony_ci list_add_tail(&dev_info->lh, &dcssblk_devices); 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_ci if (!try_module_get(THIS_MODULE)) { 67062306a36Sopenharmony_ci rc = -ENODEV; 67162306a36Sopenharmony_ci goto dev_list_del; 67262306a36Sopenharmony_ci } 67362306a36Sopenharmony_ci /* 67462306a36Sopenharmony_ci * register the device 67562306a36Sopenharmony_ci */ 67662306a36Sopenharmony_ci rc = device_register(&dev_info->dev); 67762306a36Sopenharmony_ci if (rc) 67862306a36Sopenharmony_ci goto put_dev; 67962306a36Sopenharmony_ci 68062306a36Sopenharmony_ci dev_info->dax_dev = alloc_dax(dev_info, &dcssblk_dax_ops); 68162306a36Sopenharmony_ci if (IS_ERR(dev_info->dax_dev)) { 68262306a36Sopenharmony_ci rc = PTR_ERR(dev_info->dax_dev); 68362306a36Sopenharmony_ci dev_info->dax_dev = NULL; 68462306a36Sopenharmony_ci goto put_dev; 68562306a36Sopenharmony_ci } 68662306a36Sopenharmony_ci set_dax_synchronous(dev_info->dax_dev); 68762306a36Sopenharmony_ci rc = dax_add_host(dev_info->dax_dev, dev_info->gd); 68862306a36Sopenharmony_ci if (rc) 68962306a36Sopenharmony_ci goto out_dax; 69062306a36Sopenharmony_ci 69162306a36Sopenharmony_ci get_device(&dev_info->dev); 69262306a36Sopenharmony_ci rc = device_add_disk(&dev_info->dev, dev_info->gd, NULL); 69362306a36Sopenharmony_ci if (rc) 69462306a36Sopenharmony_ci goto out_dax_host; 69562306a36Sopenharmony_ci 69662306a36Sopenharmony_ci switch (dev_info->segment_type) { 69762306a36Sopenharmony_ci case SEG_TYPE_SR: 69862306a36Sopenharmony_ci case SEG_TYPE_ER: 69962306a36Sopenharmony_ci case SEG_TYPE_SC: 70062306a36Sopenharmony_ci set_disk_ro(dev_info->gd,1); 70162306a36Sopenharmony_ci break; 70262306a36Sopenharmony_ci default: 70362306a36Sopenharmony_ci set_disk_ro(dev_info->gd,0); 70462306a36Sopenharmony_ci break; 70562306a36Sopenharmony_ci } 70662306a36Sopenharmony_ci up_write(&dcssblk_devices_sem); 70762306a36Sopenharmony_ci rc = count; 70862306a36Sopenharmony_ci goto out; 70962306a36Sopenharmony_ci 71062306a36Sopenharmony_ciout_dax_host: 71162306a36Sopenharmony_ci put_device(&dev_info->dev); 71262306a36Sopenharmony_ci dax_remove_host(dev_info->gd); 71362306a36Sopenharmony_ciout_dax: 71462306a36Sopenharmony_ci kill_dax(dev_info->dax_dev); 71562306a36Sopenharmony_ci put_dax(dev_info->dax_dev); 71662306a36Sopenharmony_ciput_dev: 71762306a36Sopenharmony_ci list_del(&dev_info->lh); 71862306a36Sopenharmony_ci put_disk(dev_info->gd); 71962306a36Sopenharmony_ci list_for_each_entry(seg_info, &dev_info->seg_list, lh) { 72062306a36Sopenharmony_ci segment_unload(seg_info->segment_name); 72162306a36Sopenharmony_ci } 72262306a36Sopenharmony_ci put_device(&dev_info->dev); 72362306a36Sopenharmony_ci up_write(&dcssblk_devices_sem); 72462306a36Sopenharmony_ci goto out; 72562306a36Sopenharmony_cidev_list_del: 72662306a36Sopenharmony_ci list_del(&dev_info->lh); 72762306a36Sopenharmony_cirelease_gd: 72862306a36Sopenharmony_ci put_disk(dev_info->gd); 72962306a36Sopenharmony_ci up_write(&dcssblk_devices_sem); 73062306a36Sopenharmony_ciseg_list_del: 73162306a36Sopenharmony_ci if (dev_info == NULL) 73262306a36Sopenharmony_ci goto out; 73362306a36Sopenharmony_ci list_for_each_entry_safe(seg_info, temp, &dev_info->seg_list, lh) { 73462306a36Sopenharmony_ci list_del(&seg_info->lh); 73562306a36Sopenharmony_ci segment_unload(seg_info->segment_name); 73662306a36Sopenharmony_ci kfree(seg_info); 73762306a36Sopenharmony_ci } 73862306a36Sopenharmony_ci kfree(dev_info); 73962306a36Sopenharmony_ciout: 74062306a36Sopenharmony_ci kfree(local_buf); 74162306a36Sopenharmony_ciout_nobuf: 74262306a36Sopenharmony_ci return rc; 74362306a36Sopenharmony_ci} 74462306a36Sopenharmony_ci 74562306a36Sopenharmony_ci/* 74662306a36Sopenharmony_ci * device attribute for removing devices 74762306a36Sopenharmony_ci */ 74862306a36Sopenharmony_cistatic ssize_t 74962306a36Sopenharmony_cidcssblk_remove_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) 75062306a36Sopenharmony_ci{ 75162306a36Sopenharmony_ci struct dcssblk_dev_info *dev_info; 75262306a36Sopenharmony_ci struct segment_info *entry; 75362306a36Sopenharmony_ci int rc, i; 75462306a36Sopenharmony_ci char *local_buf; 75562306a36Sopenharmony_ci 75662306a36Sopenharmony_ci if (dev != dcssblk_root_dev) { 75762306a36Sopenharmony_ci return -EINVAL; 75862306a36Sopenharmony_ci } 75962306a36Sopenharmony_ci local_buf = kmalloc(count + 1, GFP_KERNEL); 76062306a36Sopenharmony_ci if (local_buf == NULL) { 76162306a36Sopenharmony_ci return -ENOMEM; 76262306a36Sopenharmony_ci } 76362306a36Sopenharmony_ci /* 76462306a36Sopenharmony_ci * parse input 76562306a36Sopenharmony_ci */ 76662306a36Sopenharmony_ci for (i = 0; (i < count && (*(buf+i)!='\0') && (*(buf+i)!='\n')); i++) { 76762306a36Sopenharmony_ci local_buf[i] = toupper(buf[i]); 76862306a36Sopenharmony_ci } 76962306a36Sopenharmony_ci local_buf[i] = '\0'; 77062306a36Sopenharmony_ci if ((i == 0) || (i > 8)) { 77162306a36Sopenharmony_ci rc = -ENAMETOOLONG; 77262306a36Sopenharmony_ci goto out_buf; 77362306a36Sopenharmony_ci } 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_ci down_write(&dcssblk_devices_sem); 77662306a36Sopenharmony_ci dev_info = dcssblk_get_device_by_name(local_buf); 77762306a36Sopenharmony_ci if (dev_info == NULL) { 77862306a36Sopenharmony_ci up_write(&dcssblk_devices_sem); 77962306a36Sopenharmony_ci pr_warn("Device %s cannot be removed because it is not a known device\n", 78062306a36Sopenharmony_ci local_buf); 78162306a36Sopenharmony_ci rc = -ENODEV; 78262306a36Sopenharmony_ci goto out_buf; 78362306a36Sopenharmony_ci } 78462306a36Sopenharmony_ci if (atomic_read(&dev_info->use_count) != 0) { 78562306a36Sopenharmony_ci up_write(&dcssblk_devices_sem); 78662306a36Sopenharmony_ci pr_warn("Device %s cannot be removed while it is in use\n", 78762306a36Sopenharmony_ci local_buf); 78862306a36Sopenharmony_ci rc = -EBUSY; 78962306a36Sopenharmony_ci goto out_buf; 79062306a36Sopenharmony_ci } 79162306a36Sopenharmony_ci 79262306a36Sopenharmony_ci list_del(&dev_info->lh); 79362306a36Sopenharmony_ci /* unload all related segments */ 79462306a36Sopenharmony_ci list_for_each_entry(entry, &dev_info->seg_list, lh) 79562306a36Sopenharmony_ci segment_unload(entry->segment_name); 79662306a36Sopenharmony_ci up_write(&dcssblk_devices_sem); 79762306a36Sopenharmony_ci 79862306a36Sopenharmony_ci dax_remove_host(dev_info->gd); 79962306a36Sopenharmony_ci kill_dax(dev_info->dax_dev); 80062306a36Sopenharmony_ci put_dax(dev_info->dax_dev); 80162306a36Sopenharmony_ci del_gendisk(dev_info->gd); 80262306a36Sopenharmony_ci put_disk(dev_info->gd); 80362306a36Sopenharmony_ci 80462306a36Sopenharmony_ci device_unregister(&dev_info->dev); 80562306a36Sopenharmony_ci put_device(&dev_info->dev); 80662306a36Sopenharmony_ci 80762306a36Sopenharmony_ci rc = count; 80862306a36Sopenharmony_ciout_buf: 80962306a36Sopenharmony_ci kfree(local_buf); 81062306a36Sopenharmony_ci return rc; 81162306a36Sopenharmony_ci} 81262306a36Sopenharmony_ci 81362306a36Sopenharmony_cistatic int 81462306a36Sopenharmony_cidcssblk_open(struct gendisk *disk, blk_mode_t mode) 81562306a36Sopenharmony_ci{ 81662306a36Sopenharmony_ci struct dcssblk_dev_info *dev_info = disk->private_data; 81762306a36Sopenharmony_ci int rc; 81862306a36Sopenharmony_ci 81962306a36Sopenharmony_ci if (NULL == dev_info) { 82062306a36Sopenharmony_ci rc = -ENODEV; 82162306a36Sopenharmony_ci goto out; 82262306a36Sopenharmony_ci } 82362306a36Sopenharmony_ci atomic_inc(&dev_info->use_count); 82462306a36Sopenharmony_ci rc = 0; 82562306a36Sopenharmony_ciout: 82662306a36Sopenharmony_ci return rc; 82762306a36Sopenharmony_ci} 82862306a36Sopenharmony_ci 82962306a36Sopenharmony_cistatic void 83062306a36Sopenharmony_cidcssblk_release(struct gendisk *disk) 83162306a36Sopenharmony_ci{ 83262306a36Sopenharmony_ci struct dcssblk_dev_info *dev_info = disk->private_data; 83362306a36Sopenharmony_ci struct segment_info *entry; 83462306a36Sopenharmony_ci 83562306a36Sopenharmony_ci if (!dev_info) { 83662306a36Sopenharmony_ci WARN_ON(1); 83762306a36Sopenharmony_ci return; 83862306a36Sopenharmony_ci } 83962306a36Sopenharmony_ci down_write(&dcssblk_devices_sem); 84062306a36Sopenharmony_ci if (atomic_dec_and_test(&dev_info->use_count) 84162306a36Sopenharmony_ci && (dev_info->save_pending)) { 84262306a36Sopenharmony_ci pr_info("Device %s has become idle and is being saved " 84362306a36Sopenharmony_ci "now\n", dev_info->segment_name); 84462306a36Sopenharmony_ci list_for_each_entry(entry, &dev_info->seg_list, lh) { 84562306a36Sopenharmony_ci if (entry->segment_type == SEG_TYPE_EN || 84662306a36Sopenharmony_ci entry->segment_type == SEG_TYPE_SN) 84762306a36Sopenharmony_ci pr_warn("DCSS %s is of type SN or EN and cannot" 84862306a36Sopenharmony_ci " be saved\n", entry->segment_name); 84962306a36Sopenharmony_ci else 85062306a36Sopenharmony_ci segment_save(entry->segment_name); 85162306a36Sopenharmony_ci } 85262306a36Sopenharmony_ci dev_info->save_pending = 0; 85362306a36Sopenharmony_ci } 85462306a36Sopenharmony_ci up_write(&dcssblk_devices_sem); 85562306a36Sopenharmony_ci} 85662306a36Sopenharmony_ci 85762306a36Sopenharmony_cistatic void 85862306a36Sopenharmony_cidcssblk_submit_bio(struct bio *bio) 85962306a36Sopenharmony_ci{ 86062306a36Sopenharmony_ci struct dcssblk_dev_info *dev_info; 86162306a36Sopenharmony_ci struct bio_vec bvec; 86262306a36Sopenharmony_ci struct bvec_iter iter; 86362306a36Sopenharmony_ci unsigned long index; 86462306a36Sopenharmony_ci void *page_addr; 86562306a36Sopenharmony_ci unsigned long source_addr; 86662306a36Sopenharmony_ci unsigned long bytes_done; 86762306a36Sopenharmony_ci 86862306a36Sopenharmony_ci bytes_done = 0; 86962306a36Sopenharmony_ci dev_info = bio->bi_bdev->bd_disk->private_data; 87062306a36Sopenharmony_ci if (dev_info == NULL) 87162306a36Sopenharmony_ci goto fail; 87262306a36Sopenharmony_ci if (!IS_ALIGNED(bio->bi_iter.bi_sector, 8) || 87362306a36Sopenharmony_ci !IS_ALIGNED(bio->bi_iter.bi_size, PAGE_SIZE)) 87462306a36Sopenharmony_ci /* Request is not page-aligned. */ 87562306a36Sopenharmony_ci goto fail; 87662306a36Sopenharmony_ci /* verify data transfer direction */ 87762306a36Sopenharmony_ci if (dev_info->is_shared) { 87862306a36Sopenharmony_ci switch (dev_info->segment_type) { 87962306a36Sopenharmony_ci case SEG_TYPE_SR: 88062306a36Sopenharmony_ci case SEG_TYPE_ER: 88162306a36Sopenharmony_ci case SEG_TYPE_SC: 88262306a36Sopenharmony_ci /* cannot write to these segments */ 88362306a36Sopenharmony_ci if (bio_data_dir(bio) == WRITE) { 88462306a36Sopenharmony_ci pr_warn("Writing to %s failed because it is a read-only device\n", 88562306a36Sopenharmony_ci dev_name(&dev_info->dev)); 88662306a36Sopenharmony_ci goto fail; 88762306a36Sopenharmony_ci } 88862306a36Sopenharmony_ci } 88962306a36Sopenharmony_ci } 89062306a36Sopenharmony_ci 89162306a36Sopenharmony_ci index = (bio->bi_iter.bi_sector >> 3); 89262306a36Sopenharmony_ci bio_for_each_segment(bvec, bio, iter) { 89362306a36Sopenharmony_ci page_addr = bvec_virt(&bvec); 89462306a36Sopenharmony_ci source_addr = dev_info->start + (index<<12) + bytes_done; 89562306a36Sopenharmony_ci if (unlikely(!IS_ALIGNED((unsigned long)page_addr, PAGE_SIZE) || 89662306a36Sopenharmony_ci !IS_ALIGNED(bvec.bv_len, PAGE_SIZE))) 89762306a36Sopenharmony_ci // More paranoia. 89862306a36Sopenharmony_ci goto fail; 89962306a36Sopenharmony_ci if (bio_data_dir(bio) == READ) 90062306a36Sopenharmony_ci memcpy(page_addr, __va(source_addr), bvec.bv_len); 90162306a36Sopenharmony_ci else 90262306a36Sopenharmony_ci memcpy(__va(source_addr), page_addr, bvec.bv_len); 90362306a36Sopenharmony_ci bytes_done += bvec.bv_len; 90462306a36Sopenharmony_ci } 90562306a36Sopenharmony_ci bio_endio(bio); 90662306a36Sopenharmony_ci return; 90762306a36Sopenharmony_cifail: 90862306a36Sopenharmony_ci bio_io_error(bio); 90962306a36Sopenharmony_ci} 91062306a36Sopenharmony_ci 91162306a36Sopenharmony_cistatic long 91262306a36Sopenharmony_ci__dcssblk_direct_access(struct dcssblk_dev_info *dev_info, pgoff_t pgoff, 91362306a36Sopenharmony_ci long nr_pages, void **kaddr, pfn_t *pfn) 91462306a36Sopenharmony_ci{ 91562306a36Sopenharmony_ci resource_size_t offset = pgoff * PAGE_SIZE; 91662306a36Sopenharmony_ci unsigned long dev_sz; 91762306a36Sopenharmony_ci 91862306a36Sopenharmony_ci dev_sz = dev_info->end - dev_info->start + 1; 91962306a36Sopenharmony_ci if (kaddr) 92062306a36Sopenharmony_ci *kaddr = (void *) dev_info->start + offset; 92162306a36Sopenharmony_ci if (pfn) 92262306a36Sopenharmony_ci *pfn = __pfn_to_pfn_t(PFN_DOWN(dev_info->start + offset), 92362306a36Sopenharmony_ci PFN_DEV|PFN_SPECIAL); 92462306a36Sopenharmony_ci 92562306a36Sopenharmony_ci return (dev_sz - offset) / PAGE_SIZE; 92662306a36Sopenharmony_ci} 92762306a36Sopenharmony_ci 92862306a36Sopenharmony_cistatic long 92962306a36Sopenharmony_cidcssblk_dax_direct_access(struct dax_device *dax_dev, pgoff_t pgoff, 93062306a36Sopenharmony_ci long nr_pages, enum dax_access_mode mode, void **kaddr, 93162306a36Sopenharmony_ci pfn_t *pfn) 93262306a36Sopenharmony_ci{ 93362306a36Sopenharmony_ci struct dcssblk_dev_info *dev_info = dax_get_private(dax_dev); 93462306a36Sopenharmony_ci 93562306a36Sopenharmony_ci return __dcssblk_direct_access(dev_info, pgoff, nr_pages, kaddr, pfn); 93662306a36Sopenharmony_ci} 93762306a36Sopenharmony_ci 93862306a36Sopenharmony_cistatic void 93962306a36Sopenharmony_cidcssblk_check_params(void) 94062306a36Sopenharmony_ci{ 94162306a36Sopenharmony_ci int rc, i, j, k; 94262306a36Sopenharmony_ci char buf[DCSSBLK_PARM_LEN + 1]; 94362306a36Sopenharmony_ci struct dcssblk_dev_info *dev_info; 94462306a36Sopenharmony_ci 94562306a36Sopenharmony_ci for (i = 0; (i < DCSSBLK_PARM_LEN) && (dcssblk_segments[i] != '\0'); 94662306a36Sopenharmony_ci i++) { 94762306a36Sopenharmony_ci for (j = i; (j < DCSSBLK_PARM_LEN) && 94862306a36Sopenharmony_ci (dcssblk_segments[j] != ',') && 94962306a36Sopenharmony_ci (dcssblk_segments[j] != '\0') && 95062306a36Sopenharmony_ci (dcssblk_segments[j] != '('); j++) 95162306a36Sopenharmony_ci { 95262306a36Sopenharmony_ci buf[j-i] = dcssblk_segments[j]; 95362306a36Sopenharmony_ci } 95462306a36Sopenharmony_ci buf[j-i] = '\0'; 95562306a36Sopenharmony_ci rc = dcssblk_add_store(dcssblk_root_dev, NULL, buf, j-i); 95662306a36Sopenharmony_ci if ((rc >= 0) && (dcssblk_segments[j] == '(')) { 95762306a36Sopenharmony_ci for (k = 0; (buf[k] != ':') && (buf[k] != '\0'); k++) 95862306a36Sopenharmony_ci buf[k] = toupper(buf[k]); 95962306a36Sopenharmony_ci buf[k] = '\0'; 96062306a36Sopenharmony_ci if (!strncmp(&dcssblk_segments[j], "(local)", 7)) { 96162306a36Sopenharmony_ci down_read(&dcssblk_devices_sem); 96262306a36Sopenharmony_ci dev_info = dcssblk_get_device_by_name(buf); 96362306a36Sopenharmony_ci up_read(&dcssblk_devices_sem); 96462306a36Sopenharmony_ci if (dev_info) 96562306a36Sopenharmony_ci dcssblk_shared_store(&dev_info->dev, 96662306a36Sopenharmony_ci NULL, "0\n", 2); 96762306a36Sopenharmony_ci } 96862306a36Sopenharmony_ci } 96962306a36Sopenharmony_ci while ((dcssblk_segments[j] != ',') && 97062306a36Sopenharmony_ci (dcssblk_segments[j] != '\0')) 97162306a36Sopenharmony_ci { 97262306a36Sopenharmony_ci j++; 97362306a36Sopenharmony_ci } 97462306a36Sopenharmony_ci if (dcssblk_segments[j] == '\0') 97562306a36Sopenharmony_ci break; 97662306a36Sopenharmony_ci i = j; 97762306a36Sopenharmony_ci } 97862306a36Sopenharmony_ci} 97962306a36Sopenharmony_ci 98062306a36Sopenharmony_ci/* 98162306a36Sopenharmony_ci * The init/exit functions. 98262306a36Sopenharmony_ci */ 98362306a36Sopenharmony_cistatic void __exit 98462306a36Sopenharmony_cidcssblk_exit(void) 98562306a36Sopenharmony_ci{ 98662306a36Sopenharmony_ci root_device_unregister(dcssblk_root_dev); 98762306a36Sopenharmony_ci unregister_blkdev(dcssblk_major, DCSSBLK_NAME); 98862306a36Sopenharmony_ci} 98962306a36Sopenharmony_ci 99062306a36Sopenharmony_cistatic int __init 99162306a36Sopenharmony_cidcssblk_init(void) 99262306a36Sopenharmony_ci{ 99362306a36Sopenharmony_ci int rc; 99462306a36Sopenharmony_ci 99562306a36Sopenharmony_ci dcssblk_root_dev = root_device_register("dcssblk"); 99662306a36Sopenharmony_ci if (IS_ERR(dcssblk_root_dev)) 99762306a36Sopenharmony_ci return PTR_ERR(dcssblk_root_dev); 99862306a36Sopenharmony_ci rc = device_create_file(dcssblk_root_dev, &dev_attr_add); 99962306a36Sopenharmony_ci if (rc) 100062306a36Sopenharmony_ci goto out_root; 100162306a36Sopenharmony_ci rc = device_create_file(dcssblk_root_dev, &dev_attr_remove); 100262306a36Sopenharmony_ci if (rc) 100362306a36Sopenharmony_ci goto out_root; 100462306a36Sopenharmony_ci rc = register_blkdev(0, DCSSBLK_NAME); 100562306a36Sopenharmony_ci if (rc < 0) 100662306a36Sopenharmony_ci goto out_root; 100762306a36Sopenharmony_ci dcssblk_major = rc; 100862306a36Sopenharmony_ci init_rwsem(&dcssblk_devices_sem); 100962306a36Sopenharmony_ci 101062306a36Sopenharmony_ci dcssblk_check_params(); 101162306a36Sopenharmony_ci return 0; 101262306a36Sopenharmony_ci 101362306a36Sopenharmony_ciout_root: 101462306a36Sopenharmony_ci root_device_unregister(dcssblk_root_dev); 101562306a36Sopenharmony_ci 101662306a36Sopenharmony_ci return rc; 101762306a36Sopenharmony_ci} 101862306a36Sopenharmony_ci 101962306a36Sopenharmony_cimodule_init(dcssblk_init); 102062306a36Sopenharmony_cimodule_exit(dcssblk_exit); 102162306a36Sopenharmony_ci 102262306a36Sopenharmony_cimodule_param_string(segments, dcssblk_segments, DCSSBLK_PARM_LEN, 0444); 102362306a36Sopenharmony_ciMODULE_PARM_DESC(segments, "Name of DCSS segment(s) to be loaded, " 102462306a36Sopenharmony_ci "comma-separated list, names in each set separated " 102562306a36Sopenharmony_ci "by commas are separated by colons, each set contains " 102662306a36Sopenharmony_ci "names of contiguous segments and each name max. 8 chars.\n" 102762306a36Sopenharmony_ci "Adding \"(local)\" to the end of each set equals echoing 0 " 102862306a36Sopenharmony_ci "to /sys/devices/dcssblk/<device name>/shared after loading " 102962306a36Sopenharmony_ci "the contiguous segments - \n" 103062306a36Sopenharmony_ci "e.g. segments=\"mydcss1,mydcss2:mydcss3,mydcss4(local)\""); 103162306a36Sopenharmony_ci 103262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 1033