Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | // SPDX-License-Identifier: GPL-2.0-only /* * ec_sys.c * * Copyright (C) 2010 SUSE Products GmbH/Novell * Author: * Thomas Renninger <trenn@suse.de> */ #include <linux/kernel.h> #include <linux/acpi.h> #include <linux/debugfs.h> #include <linux/module.h> #include <linux/uaccess.h> #include "internal.h" MODULE_AUTHOR("Thomas Renninger <trenn@suse.de>"); MODULE_DESCRIPTION("ACPI EC sysfs access driver"); MODULE_LICENSE("GPL"); static bool write_support; module_param_hw(write_support, bool, other, 0644); MODULE_PARM_DESC(write_support, "Dangerous, reboot and removal of battery may " "be needed."); #define EC_SPACE_SIZE 256 static struct dentry *acpi_ec_debugfs_dir; static ssize_t acpi_ec_read_io(struct file *f, char __user *buf, size_t count, loff_t *off) { /* Use this if support reading/writing multiple ECs exists in ec.c: * struct acpi_ec *ec = ((struct seq_file *)f->private_data)->private; */ unsigned int size = EC_SPACE_SIZE; loff_t init_off = *off; int err = 0; if (*off >= size) return 0; if (*off + count >= size) { size -= *off; count = size; } else size = count; while (size) { u8 byte_read; err = ec_read(*off, &byte_read); if (err) return err; if (put_user(byte_read, buf + *off - init_off)) { if (*off - init_off) return *off - init_off; /* partial read */ return -EFAULT; } *off += 1; size--; } return count; } static ssize_t acpi_ec_write_io(struct file *f, const char __user *buf, size_t count, loff_t *off) { /* Use this if support reading/writing multiple ECs exists in ec.c: * struct acpi_ec *ec = ((struct seq_file *)f->private_data)->private; */ unsigned int size = count; loff_t init_off = *off; int err = 0; if (!write_support) return -EINVAL; if (*off >= EC_SPACE_SIZE) return 0; if (*off + count >= EC_SPACE_SIZE) { size = EC_SPACE_SIZE - *off; count = size; } while (size) { u8 byte_write; if (get_user(byte_write, buf + *off - init_off)) { if (*off - init_off) return *off - init_off; /* partial write */ return -EFAULT; } err = ec_write(*off, byte_write); if (err) return err; *off += 1; size--; } return count; } static const struct file_operations acpi_ec_io_ops = { .owner = THIS_MODULE, .open = simple_open, .read = acpi_ec_read_io, .write = acpi_ec_write_io, .llseek = default_llseek, }; static void acpi_ec_add_debugfs(struct acpi_ec *ec, unsigned int ec_device_count) { struct dentry *dev_dir; char name[64]; umode_t mode = 0400; if (ec_device_count == 0) acpi_ec_debugfs_dir = debugfs_create_dir("ec", NULL); sprintf(name, "ec%u", ec_device_count); dev_dir = debugfs_create_dir(name, acpi_ec_debugfs_dir); debugfs_create_x32("gpe", 0444, dev_dir, &first_ec->gpe); debugfs_create_bool("use_global_lock", 0444, dev_dir, &first_ec->global_lock); if (write_support) mode = 0600; debugfs_create_file("io", mode, dev_dir, ec, &acpi_ec_io_ops); } static int __init acpi_ec_sys_init(void) { if (first_ec) acpi_ec_add_debugfs(first_ec, 0); return 0; } static void __exit acpi_ec_sys_exit(void) { debugfs_remove_recursive(acpi_ec_debugfs_dir); } module_init(acpi_ec_sys_init); module_exit(acpi_ec_sys_exit); |