604 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * IBM ASM Service Processor Device Driver4 *5 * Copyright (C) IBM Corporation, 20046 *7 * Author: Max Asböck <amax@us.ibm.com>8 */9 10/*11 * Parts of this code are based on an article by Jonathan Corbet12 * that appeared in Linux Weekly News.13 */14 15 16/*17 * The IBMASM file virtual filesystem. It creates the following hierarchy18 * dynamically when mounted from user space:19 *20 * /ibmasm21 * |-- 022 * | |-- command23 * | |-- event24 * | |-- reverse_heartbeat25 * | `-- remote_video26 * | |-- depth27 * | |-- height28 * | `-- width29 * .30 * .31 * .32 * `-- n33 * |-- command34 * |-- event35 * |-- reverse_heartbeat36 * `-- remote_video37 * |-- depth38 * |-- height39 * `-- width40 *41 * For each service processor the following files are created:42 *43 * command: execute dot commands44 * write: execute a dot command on the service processor45 * read: return the result of a previously executed dot command46 *47 * events: listen for service processor events48 * read: sleep (interruptible) until an event occurs49 * write: wakeup sleeping event listener50 *51 * reverse_heartbeat: send a heartbeat to the service processor52 * read: sleep (interruptible) until the reverse heartbeat fails53 * write: wakeup sleeping heartbeat listener54 *55 * remote_video/width56 * remote_video/height57 * remote_video/width: control remote display settings58 * write: set value59 * read: read value60 */61 62#include <linux/fs.h>63#include <linux/fs_context.h>64#include <linux/pagemap.h>65#include <linux/slab.h>66#include <linux/uaccess.h>67#include <asm/io.h>68#include "ibmasm.h"69#include "remote.h"70#include "dot_command.h"71 72#define IBMASMFS_MAGIC 0x66726f6773 74static LIST_HEAD(service_processors);75 76static struct inode *ibmasmfs_make_inode(struct super_block *sb, int mode);77static void ibmasmfs_create_files (struct super_block *sb);78static int ibmasmfs_fill_super(struct super_block *sb, struct fs_context *fc);79 80static int ibmasmfs_get_tree(struct fs_context *fc)81{82 return get_tree_single(fc, ibmasmfs_fill_super);83}84 85static const struct fs_context_operations ibmasmfs_context_ops = {86 .get_tree = ibmasmfs_get_tree,87};88 89static int ibmasmfs_init_fs_context(struct fs_context *fc)90{91 fc->ops = &ibmasmfs_context_ops;92 return 0;93}94 95static const struct super_operations ibmasmfs_s_ops = {96 .statfs = simple_statfs,97 .drop_inode = generic_delete_inode,98};99 100static const struct file_operations *ibmasmfs_dir_ops = &simple_dir_operations;101 102static struct file_system_type ibmasmfs_type = {103 .owner = THIS_MODULE,104 .name = "ibmasmfs",105 .init_fs_context = ibmasmfs_init_fs_context,106 .kill_sb = kill_litter_super,107};108MODULE_ALIAS_FS("ibmasmfs");109 110static int ibmasmfs_fill_super(struct super_block *sb, struct fs_context *fc)111{112 struct inode *root;113 114 sb->s_blocksize = PAGE_SIZE;115 sb->s_blocksize_bits = PAGE_SHIFT;116 sb->s_magic = IBMASMFS_MAGIC;117 sb->s_op = &ibmasmfs_s_ops;118 sb->s_time_gran = 1;119 120 root = ibmasmfs_make_inode (sb, S_IFDIR | 0500);121 if (!root)122 return -ENOMEM;123 124 root->i_op = &simple_dir_inode_operations;125 root->i_fop = ibmasmfs_dir_ops;126 127 sb->s_root = d_make_root(root);128 if (!sb->s_root)129 return -ENOMEM;130 131 ibmasmfs_create_files(sb);132 return 0;133}134 135static struct inode *ibmasmfs_make_inode(struct super_block *sb, int mode)136{137 struct inode *ret = new_inode(sb);138 139 if (ret) {140 ret->i_ino = get_next_ino();141 ret->i_mode = mode;142 simple_inode_init_ts(ret);143 }144 return ret;145}146 147static struct dentry *ibmasmfs_create_file(struct dentry *parent,148 const char *name,149 const struct file_operations *fops,150 void *data,151 int mode)152{153 struct dentry *dentry;154 struct inode *inode;155 156 dentry = d_alloc_name(parent, name);157 if (!dentry)158 return NULL;159 160 inode = ibmasmfs_make_inode(parent->d_sb, S_IFREG | mode);161 if (!inode) {162 dput(dentry);163 return NULL;164 }165 166 inode->i_fop = fops;167 inode->i_private = data;168 169 d_add(dentry, inode);170 return dentry;171}172 173static struct dentry *ibmasmfs_create_dir(struct dentry *parent,174 const char *name)175{176 struct dentry *dentry;177 struct inode *inode;178 179 dentry = d_alloc_name(parent, name);180 if (!dentry)181 return NULL;182 183 inode = ibmasmfs_make_inode(parent->d_sb, S_IFDIR | 0500);184 if (!inode) {185 dput(dentry);186 return NULL;187 }188 189 inode->i_op = &simple_dir_inode_operations;190 inode->i_fop = ibmasmfs_dir_ops;191 192 d_add(dentry, inode);193 return dentry;194}195 196int ibmasmfs_register(void)197{198 return register_filesystem(&ibmasmfs_type);199}200 201void ibmasmfs_unregister(void)202{203 unregister_filesystem(&ibmasmfs_type);204}205 206void ibmasmfs_add_sp(struct service_processor *sp)207{208 list_add(&sp->node, &service_processors);209}210 211/* struct to save state between command file operations */212struct ibmasmfs_command_data {213 struct service_processor *sp;214 struct command *command;215};216 217/* struct to save state between event file operations */218struct ibmasmfs_event_data {219 struct service_processor *sp;220 struct event_reader reader;221 int active;222};223 224/* struct to save state between reverse heartbeat file operations */225struct ibmasmfs_heartbeat_data {226 struct service_processor *sp;227 struct reverse_heartbeat heartbeat;228 int active;229};230 231static int command_file_open(struct inode *inode, struct file *file)232{233 struct ibmasmfs_command_data *command_data;234 235 if (!inode->i_private)236 return -ENODEV;237 238 command_data = kmalloc(sizeof(struct ibmasmfs_command_data), GFP_KERNEL);239 if (!command_data)240 return -ENOMEM;241 242 command_data->command = NULL;243 command_data->sp = inode->i_private;244 file->private_data = command_data;245 return 0;246}247 248static int command_file_close(struct inode *inode, struct file *file)249{250 struct ibmasmfs_command_data *command_data = file->private_data;251 252 if (command_data->command)253 command_put(command_data->command);254 255 kfree(command_data);256 return 0;257}258 259static ssize_t command_file_read(struct file *file, char __user *buf, size_t count, loff_t *offset)260{261 struct ibmasmfs_command_data *command_data = file->private_data;262 struct command *cmd;263 int len;264 unsigned long flags;265 266 if (*offset < 0)267 return -EINVAL;268 if (count == 0 || count > IBMASM_CMD_MAX_BUFFER_SIZE)269 return 0;270 if (*offset != 0)271 return 0;272 273 spin_lock_irqsave(&command_data->sp->lock, flags);274 cmd = command_data->command;275 if (cmd == NULL) {276 spin_unlock_irqrestore(&command_data->sp->lock, flags);277 return 0;278 }279 command_data->command = NULL;280 spin_unlock_irqrestore(&command_data->sp->lock, flags);281 282 if (cmd->status != IBMASM_CMD_COMPLETE) {283 command_put(cmd);284 return -EIO;285 }286 len = min(count, cmd->buffer_size);287 if (copy_to_user(buf, cmd->buffer, len)) {288 command_put(cmd);289 return -EFAULT;290 }291 command_put(cmd);292 293 return len;294}295 296static ssize_t command_file_write(struct file *file, const char __user *ubuff, size_t count, loff_t *offset)297{298 struct ibmasmfs_command_data *command_data = file->private_data;299 struct command *cmd;300 unsigned long flags;301 302 if (*offset < 0)303 return -EINVAL;304 if (count == 0 || count > IBMASM_CMD_MAX_BUFFER_SIZE)305 return 0;306 if (*offset != 0)307 return 0;308 309 /* commands are executed sequentially, only one command at a time */310 if (command_data->command)311 return -EAGAIN;312 313 cmd = ibmasm_new_command(command_data->sp, count);314 if (!cmd)315 return -ENOMEM;316 317 if (copy_from_user(cmd->buffer, ubuff, count)) {318 command_put(cmd);319 return -EFAULT;320 }321 322 spin_lock_irqsave(&command_data->sp->lock, flags);323 if (command_data->command) {324 spin_unlock_irqrestore(&command_data->sp->lock, flags);325 command_put(cmd);326 return -EAGAIN;327 }328 command_data->command = cmd;329 spin_unlock_irqrestore(&command_data->sp->lock, flags);330 331 ibmasm_exec_command(command_data->sp, cmd);332 ibmasm_wait_for_response(cmd, get_dot_command_timeout(cmd->buffer));333 334 return count;335}336 337static int event_file_open(struct inode *inode, struct file *file)338{339 struct ibmasmfs_event_data *event_data;340 struct service_processor *sp;341 342 if (!inode->i_private)343 return -ENODEV;344 345 sp = inode->i_private;346 347 event_data = kmalloc(sizeof(struct ibmasmfs_event_data), GFP_KERNEL);348 if (!event_data)349 return -ENOMEM;350 351 ibmasm_event_reader_register(sp, &event_data->reader);352 353 event_data->sp = sp;354 event_data->active = 0;355 file->private_data = event_data;356 return 0;357}358 359static int event_file_close(struct inode *inode, struct file *file)360{361 struct ibmasmfs_event_data *event_data = file->private_data;362 363 ibmasm_event_reader_unregister(event_data->sp, &event_data->reader);364 kfree(event_data);365 return 0;366}367 368static ssize_t event_file_read(struct file *file, char __user *buf, size_t count, loff_t *offset)369{370 struct ibmasmfs_event_data *event_data = file->private_data;371 struct event_reader *reader = &event_data->reader;372 struct service_processor *sp = event_data->sp;373 int ret;374 unsigned long flags;375 376 if (*offset < 0)377 return -EINVAL;378 if (count == 0 || count > IBMASM_EVENT_MAX_SIZE)379 return 0;380 if (*offset != 0)381 return 0;382 383 spin_lock_irqsave(&sp->lock, flags);384 if (event_data->active) {385 spin_unlock_irqrestore(&sp->lock, flags);386 return -EBUSY;387 }388 event_data->active = 1;389 spin_unlock_irqrestore(&sp->lock, flags);390 391 ret = ibmasm_get_next_event(sp, reader);392 if (ret <= 0)393 goto out;394 395 if (count < reader->data_size) {396 ret = -EINVAL;397 goto out;398 }399 400 if (copy_to_user(buf, reader->data, reader->data_size)) {401 ret = -EFAULT;402 goto out;403 }404 ret = reader->data_size;405 406out:407 event_data->active = 0;408 return ret;409}410 411static ssize_t event_file_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)412{413 struct ibmasmfs_event_data *event_data = file->private_data;414 415 if (*offset < 0)416 return -EINVAL;417 if (count != 1)418 return 0;419 if (*offset != 0)420 return 0;421 422 ibmasm_cancel_next_event(&event_data->reader);423 return 0;424}425 426static int r_heartbeat_file_open(struct inode *inode, struct file *file)427{428 struct ibmasmfs_heartbeat_data *rhbeat;429 430 if (!inode->i_private)431 return -ENODEV;432 433 rhbeat = kmalloc(sizeof(struct ibmasmfs_heartbeat_data), GFP_KERNEL);434 if (!rhbeat)435 return -ENOMEM;436 437 rhbeat->sp = inode->i_private;438 rhbeat->active = 0;439 ibmasm_init_reverse_heartbeat(rhbeat->sp, &rhbeat->heartbeat);440 file->private_data = rhbeat;441 return 0;442}443 444static int r_heartbeat_file_close(struct inode *inode, struct file *file)445{446 struct ibmasmfs_heartbeat_data *rhbeat = file->private_data;447 448 kfree(rhbeat);449 return 0;450}451 452static ssize_t r_heartbeat_file_read(struct file *file, char __user *buf, size_t count, loff_t *offset)453{454 struct ibmasmfs_heartbeat_data *rhbeat = file->private_data;455 unsigned long flags;456 int result;457 458 if (*offset < 0)459 return -EINVAL;460 if (count == 0 || count > 1024)461 return 0;462 if (*offset != 0)463 return 0;464 465 /* allow only one reverse heartbeat per process */466 spin_lock_irqsave(&rhbeat->sp->lock, flags);467 if (rhbeat->active) {468 spin_unlock_irqrestore(&rhbeat->sp->lock, flags);469 return -EBUSY;470 }471 rhbeat->active = 1;472 spin_unlock_irqrestore(&rhbeat->sp->lock, flags);473 474 result = ibmasm_start_reverse_heartbeat(rhbeat->sp, &rhbeat->heartbeat);475 rhbeat->active = 0;476 477 return result;478}479 480static ssize_t r_heartbeat_file_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)481{482 struct ibmasmfs_heartbeat_data *rhbeat = file->private_data;483 484 if (*offset < 0)485 return -EINVAL;486 if (count != 1)487 return 0;488 if (*offset != 0)489 return 0;490 491 if (rhbeat->active)492 ibmasm_stop_reverse_heartbeat(&rhbeat->heartbeat);493 494 return 1;495}496 497static int remote_settings_file_close(struct inode *inode, struct file *file)498{499 return 0;500}501 502static ssize_t remote_settings_file_read(struct file *file, char __user *buf, size_t count, loff_t *offset)503{504 void __iomem *address = (void __iomem *)file->private_data;505 int len = 0;506 unsigned int value;507 char lbuf[20];508 509 value = readl(address);510 len = snprintf(lbuf, sizeof(lbuf), "%d\n", value);511 512 return simple_read_from_buffer(buf, count, offset, lbuf, len);513}514 515static ssize_t remote_settings_file_write(struct file *file, const char __user *ubuff, size_t count, loff_t *offset)516{517 void __iomem *address = (void __iomem *)file->private_data;518 char *buff;519 unsigned int value;520 521 if (*offset < 0)522 return -EINVAL;523 if (count == 0 || count > 1024)524 return 0;525 if (*offset != 0)526 return 0;527 528 buff = kzalloc (count + 1, GFP_KERNEL);529 if (!buff)530 return -ENOMEM;531 532 533 if (copy_from_user(buff, ubuff, count)) {534 kfree(buff);535 return -EFAULT;536 }537 538 value = simple_strtoul(buff, NULL, 10);539 writel(value, address);540 kfree(buff);541 542 return count;543}544 545static const struct file_operations command_fops = {546 .open = command_file_open,547 .release = command_file_close,548 .read = command_file_read,549 .write = command_file_write,550 .llseek = generic_file_llseek,551};552 553static const struct file_operations event_fops = {554 .open = event_file_open,555 .release = event_file_close,556 .read = event_file_read,557 .write = event_file_write,558 .llseek = generic_file_llseek,559};560 561static const struct file_operations r_heartbeat_fops = {562 .open = r_heartbeat_file_open,563 .release = r_heartbeat_file_close,564 .read = r_heartbeat_file_read,565 .write = r_heartbeat_file_write,566 .llseek = generic_file_llseek,567};568 569static const struct file_operations remote_settings_fops = {570 .open = simple_open,571 .release = remote_settings_file_close,572 .read = remote_settings_file_read,573 .write = remote_settings_file_write,574 .llseek = generic_file_llseek,575};576 577 578static void ibmasmfs_create_files (struct super_block *sb)579{580 struct list_head *entry;581 struct service_processor *sp;582 583 list_for_each(entry, &service_processors) {584 struct dentry *dir;585 struct dentry *remote_dir;586 sp = list_entry(entry, struct service_processor, node);587 dir = ibmasmfs_create_dir(sb->s_root, sp->dirname);588 if (!dir)589 continue;590 591 ibmasmfs_create_file(dir, "command", &command_fops, sp, S_IRUSR|S_IWUSR);592 ibmasmfs_create_file(dir, "event", &event_fops, sp, S_IRUSR|S_IWUSR);593 ibmasmfs_create_file(dir, "reverse_heartbeat", &r_heartbeat_fops, sp, S_IRUSR|S_IWUSR);594 595 remote_dir = ibmasmfs_create_dir(dir, "remote_video");596 if (!remote_dir)597 continue;598 599 ibmasmfs_create_file(remote_dir, "width", &remote_settings_fops, (void *)display_width(sp), S_IRUSR|S_IWUSR);600 ibmasmfs_create_file(remote_dir, "height", &remote_settings_fops, (void *)display_height(sp), S_IRUSR|S_IWUSR);601 ibmasmfs_create_file(remote_dir, "depth", &remote_settings_fops, (void *)display_depth(sp), S_IRUSR|S_IWUSR);602 }603}604