321 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Character device driver for writing z/VM *MONITOR service records.4 *5 * Copyright IBM Corp. 2006, 20096 *7 * Author(s): Melissa Howland <Melissa.Howland@us.ibm.com>8 */9 10#define KMSG_COMPONENT "monwriter"11#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt12 13#include <linux/module.h>14#include <linux/moduleparam.h>15#include <linux/init.h>16#include <linux/errno.h>17#include <linux/types.h>18#include <linux/kernel.h>19#include <linux/miscdevice.h>20#include <linux/ctype.h>21#include <linux/poll.h>22#include <linux/mutex.h>23#include <linux/slab.h>24#include <linux/uaccess.h>25#include <linux/io.h>26#include <asm/ebcdic.h>27#include <asm/appldata.h>28#include <asm/monwriter.h>29 30#define MONWRITE_MAX_DATALEN 401031 32static int mon_max_bufs = 255;33static int mon_buf_count;34 35struct mon_buf {36 struct list_head list;37 struct monwrite_hdr hdr;38 int diag_done;39 char *data;40};41 42struct mon_private {43 struct list_head list;44 struct monwrite_hdr hdr;45 size_t hdr_to_read;46 size_t data_to_read;47 struct mon_buf *current_buf;48 struct mutex thread_mutex;49};50 51/*52 * helper functions53 */54 55static int monwrite_diag(struct monwrite_hdr *myhdr, char *buffer, int fcn)56{57 struct appldata_parameter_list *parm_list;58 struct appldata_product_id *id;59 int rc;60 61 id = kmalloc(sizeof(*id), GFP_KERNEL);62 parm_list = kmalloc(sizeof(*parm_list), GFP_KERNEL);63 rc = -ENOMEM;64 if (!id || !parm_list)65 goto out;66 memcpy(id->prod_nr, "LNXAPPL", 7);67 id->prod_fn = myhdr->applid;68 id->record_nr = myhdr->record_num;69 id->version_nr = myhdr->version;70 id->release_nr = myhdr->release;71 id->mod_lvl = myhdr->mod_level;72 rc = appldata_asm(parm_list, id, fcn,73 (void *) buffer, myhdr->datalen);74 if (rc <= 0)75 goto out;76 pr_err("Writing monitor data failed with rc=%i\n", rc);77 rc = (rc == 5) ? -EPERM : -EINVAL;78out:79 kfree(id);80 kfree(parm_list);81 return rc;82}83 84static struct mon_buf *monwrite_find_hdr(struct mon_private *monpriv,85 struct monwrite_hdr *monhdr)86{87 struct mon_buf *entry, *next;88 89 list_for_each_entry_safe(entry, next, &monpriv->list, list)90 if ((entry->hdr.mon_function == monhdr->mon_function ||91 monhdr->mon_function == MONWRITE_STOP_INTERVAL) &&92 entry->hdr.applid == monhdr->applid &&93 entry->hdr.record_num == monhdr->record_num &&94 entry->hdr.version == monhdr->version &&95 entry->hdr.release == monhdr->release &&96 entry->hdr.mod_level == monhdr->mod_level)97 return entry;98 99 return NULL;100}101 102static int monwrite_new_hdr(struct mon_private *monpriv)103{104 struct monwrite_hdr *monhdr = &monpriv->hdr;105 struct mon_buf *monbuf;106 int rc = 0;107 108 if (monhdr->datalen > MONWRITE_MAX_DATALEN ||109 monhdr->mon_function > MONWRITE_START_CONFIG ||110 monhdr->hdrlen != sizeof(struct monwrite_hdr))111 return -EINVAL;112 monbuf = NULL;113 if (monhdr->mon_function != MONWRITE_GEN_EVENT)114 monbuf = monwrite_find_hdr(monpriv, monhdr);115 if (monbuf) {116 if (monhdr->mon_function == MONWRITE_STOP_INTERVAL) {117 monhdr->datalen = monbuf->hdr.datalen;118 rc = monwrite_diag(monhdr, monbuf->data,119 APPLDATA_STOP_REC);120 list_del(&monbuf->list);121 mon_buf_count--;122 kfree(monbuf->data);123 kfree(monbuf);124 monbuf = NULL;125 }126 } else if (monhdr->mon_function != MONWRITE_STOP_INTERVAL) {127 if (mon_buf_count >= mon_max_bufs)128 return -ENOSPC;129 monbuf = kzalloc(sizeof(struct mon_buf), GFP_KERNEL);130 if (!monbuf)131 return -ENOMEM;132 monbuf->data = kzalloc(monhdr->datalen,133 GFP_KERNEL | GFP_DMA);134 if (!monbuf->data) {135 kfree(monbuf);136 return -ENOMEM;137 }138 monbuf->hdr = *monhdr;139 list_add_tail(&monbuf->list, &monpriv->list);140 if (monhdr->mon_function != MONWRITE_GEN_EVENT)141 mon_buf_count++;142 }143 monpriv->current_buf = monbuf;144 return rc;145}146 147static int monwrite_new_data(struct mon_private *monpriv)148{149 struct monwrite_hdr *monhdr = &monpriv->hdr;150 struct mon_buf *monbuf = monpriv->current_buf;151 int rc = 0;152 153 switch (monhdr->mon_function) {154 case MONWRITE_START_INTERVAL:155 if (!monbuf->diag_done) {156 rc = monwrite_diag(monhdr, monbuf->data,157 APPLDATA_START_INTERVAL_REC);158 monbuf->diag_done = 1;159 }160 break;161 case MONWRITE_START_CONFIG:162 if (!monbuf->diag_done) {163 rc = monwrite_diag(monhdr, monbuf->data,164 APPLDATA_START_CONFIG_REC);165 monbuf->diag_done = 1;166 }167 break;168 case MONWRITE_GEN_EVENT:169 rc = monwrite_diag(monhdr, monbuf->data,170 APPLDATA_GEN_EVENT_REC);171 list_del(&monpriv->current_buf->list);172 kfree(monpriv->current_buf->data);173 kfree(monpriv->current_buf);174 monpriv->current_buf = NULL;175 break;176 default:177 /* monhdr->mon_function is checked in monwrite_new_hdr */178 BUG();179 }180 return rc;181}182 183/*184 * file operations185 */186 187static int monwrite_open(struct inode *inode, struct file *filp)188{189 struct mon_private *monpriv;190 191 monpriv = kzalloc(sizeof(struct mon_private), GFP_KERNEL);192 if (!monpriv)193 return -ENOMEM;194 INIT_LIST_HEAD(&monpriv->list);195 monpriv->hdr_to_read = sizeof(monpriv->hdr);196 mutex_init(&monpriv->thread_mutex);197 filp->private_data = monpriv;198 return nonseekable_open(inode, filp);199}200 201static int monwrite_close(struct inode *inode, struct file *filp)202{203 struct mon_private *monpriv = filp->private_data;204 struct mon_buf *entry, *next;205 206 list_for_each_entry_safe(entry, next, &monpriv->list, list) {207 if (entry->hdr.mon_function != MONWRITE_GEN_EVENT)208 monwrite_diag(&entry->hdr, entry->data,209 APPLDATA_STOP_REC);210 mon_buf_count--;211 list_del(&entry->list);212 kfree(entry->data);213 kfree(entry);214 }215 kfree(monpriv);216 return 0;217}218 219static ssize_t monwrite_write(struct file *filp, const char __user *data,220 size_t count, loff_t *ppos)221{222 struct mon_private *monpriv = filp->private_data;223 size_t len, written;224 void *to;225 int rc;226 227 mutex_lock(&monpriv->thread_mutex);228 for (written = 0; written < count; ) {229 if (monpriv->hdr_to_read) {230 len = min(count - written, monpriv->hdr_to_read);231 to = (char *) &monpriv->hdr +232 sizeof(monpriv->hdr) - monpriv->hdr_to_read;233 if (copy_from_user(to, data + written, len)) {234 rc = -EFAULT;235 goto out_error;236 }237 monpriv->hdr_to_read -= len;238 written += len;239 if (monpriv->hdr_to_read > 0)240 continue;241 rc = monwrite_new_hdr(monpriv);242 if (rc)243 goto out_error;244 monpriv->data_to_read = monpriv->current_buf ?245 monpriv->current_buf->hdr.datalen : 0;246 }247 248 if (monpriv->data_to_read) {249 len = min(count - written, monpriv->data_to_read);250 to = monpriv->current_buf->data +251 monpriv->hdr.datalen - monpriv->data_to_read;252 if (copy_from_user(to, data + written, len)) {253 rc = -EFAULT;254 goto out_error;255 }256 monpriv->data_to_read -= len;257 written += len;258 if (monpriv->data_to_read > 0)259 continue;260 rc = monwrite_new_data(monpriv);261 if (rc)262 goto out_error;263 }264 monpriv->hdr_to_read = sizeof(monpriv->hdr);265 }266 mutex_unlock(&monpriv->thread_mutex);267 return written;268 269out_error:270 monpriv->data_to_read = 0;271 monpriv->hdr_to_read = sizeof(struct monwrite_hdr);272 mutex_unlock(&monpriv->thread_mutex);273 return rc;274}275 276static const struct file_operations monwrite_fops = {277 .owner = THIS_MODULE,278 .open = &monwrite_open,279 .release = &monwrite_close,280 .write = &monwrite_write,281 .llseek = noop_llseek,282};283 284static struct miscdevice mon_dev = {285 .name = "monwriter",286 .fops = &monwrite_fops,287 .minor = MISC_DYNAMIC_MINOR,288};289 290/*291 * module init/exit292 */293 294static int __init mon_init(void)295{296 if (!MACHINE_IS_VM)297 return -ENODEV;298 /*299 * misc_register() has to be the last action in module_init(), because300 * file operations will be available right after this.301 */302 return misc_register(&mon_dev);303}304 305static void __exit mon_exit(void)306{307 misc_deregister(&mon_dev);308}309 310module_init(mon_init);311module_exit(mon_exit);312 313module_param_named(max_bufs, mon_max_bufs, int, 0644);314MODULE_PARM_DESC(max_bufs, "Maximum number of sample monitor data buffers "315 "that can be active at one time");316 317MODULE_AUTHOR("Melissa Howland <Melissa.Howland@us.ibm.com>");318MODULE_DESCRIPTION("Character device driver for writing z/VM "319 "APPLDATA monitor records.");320MODULE_LICENSE("GPL");321