#include #include #include #include #include #include #include #include #include #include #define DEVICE_NAME "smart_battery" #define CLASS_NAME "battery_acpi_class" #define BATTERY_IOC_MAGIC 'B' #define BATTERY_IOC_GET_CAPACITY _IOR(BATTERY_IOC_MAGIC, 1, int) #define BATTERY_IOC_GET_VOLTAGE _IOR(BATTERY_IOC_MAGIC, 2, int) #define BATTERY_IOC_GET_STATUS _IOR(BATTERY_IOC_MAGIC, 3, int) #define BATTERY_IOC_GET_ENERGY_NOW _IOR(BATTERY_IOC_MAGIC, 4, int) #define BATTERY_IOC_GET_ENERGY_FULL _IOR(BATTERY_IOC_MAGIC, 5, int) #define BATTERY_IOC_GET_POWER _IOR(BATTERY_IOC_MAGIC, 6, int) #define BATTERY_IOC_GET_TECHNOLOGY _IOR(BATTERY_IOC_MAGIC, 7, int) #define BATTERY_IOC_GET_SERIAL _IOR(BATTERY_IOC_MAGIC, 8, char[16]) #define BATTERY_IOC_GET_PRESENT _IOR(BATTERY_IOC_MAGIC, 9, int) struct battery_acpi_device { struct cdev cdev; struct device *device; struct class *class; struct mutex lock; char battery_name[16]; /* 电池数据 */ int present; int capacity; int voltage; int status; int energy_now; int energy_full; int energy_full_design; int power_now; int technology; char serial[16]; unsigned int read_count; unsigned int ioctl_count; }; static struct battery_acpi_device *g_battery_dev; static const char* find_battery_name(void) { static char name[16]; struct power_supply *psy; int i; const char *battery_names[] = {"BAT4", "BAT0", "BAT1", "BAT2", "BAT3", "BATT"}; for (i = 0; i < ARRAY_SIZE(battery_names); i++) { psy = power_supply_get_by_name(battery_names[i]); if (psy) { if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY) { strcpy(name, battery_names[i]); power_supply_put(psy); pr_info("Found battery: %s\n", name); return name; } power_supply_put(psy); } } return NULL; } static int read_psy_property(const char *bat_name, enum power_supply_property prop, int *value) { struct power_supply *psy; union power_supply_propval val; int ret; psy = power_supply_get_by_name(bat_name); if (!psy) return -ENODEV; ret = power_supply_get_property(psy, prop, &val); if (ret == 0) *value = val.intval; power_supply_put(psy); return ret; } static int read_psy_property_string(const char *bat_name, enum power_supply_property prop, char *buf, size_t buf_size) { struct power_supply *psy; union power_supply_propval val; int ret; psy = power_supply_get_by_name(bat_name); if (!psy) return -ENODEV; ret = power_supply_get_property(psy, prop, &val); if (ret == 0 && val.strval) { strncpy(buf, val.strval, buf_size - 1); buf[buf_size - 1] = '\0'; } else { buf[0] = '\0'; } power_supply_put(psy); return ret; } static int charge_to_energy(int charge_uh, int voltage_uv) { /* energy (µWh) = charge (µAh) * voltage (µV) / 1000000 */ if (charge_uh <= 0 || voltage_uv <= 0) return 0; return ((long long)charge_uh * voltage_uv) / 1000000; } static void update_battery_data(struct battery_acpi_device *dev) { const char *bat_name; int ret; int charge_now = 0, charge_full = 0; int energy_now_direct = 0, energy_full_direct = 0; int voltage = 0; mutex_lock(&dev->lock); bat_name = find_battery_name(); if (!bat_name) { dev->present = 0; mutex_unlock(&dev->lock); return; } strcpy(dev->battery_name, bat_name); ret = read_psy_property(bat_name, POWER_SUPPLY_PROP_PRESENT, &dev->present); if (ret < 0) dev->present = 1; if (!dev->present) { mutex_unlock(&dev->lock); return; } read_psy_property(bat_name, POWER_SUPPLY_PROP_CAPACITY, &dev->capacity); read_psy_property(bat_name, POWER_SUPPLY_PROP_VOLTAGE_NOW, &dev->voltage); voltage = dev->voltage; read_psy_property(bat_name, POWER_SUPPLY_PROP_STATUS, &dev->status); ret = read_psy_property(bat_name, POWER_SUPPLY_PROP_ENERGY_NOW, &energy_now_direct); if (ret == 0 && energy_now_direct > 0) { dev->energy_now = energy_now_direct; pr_debug("Using ENERGY_NOW: %d µWh\n", dev->energy_now); } else { ret = read_psy_property(bat_name, POWER_SUPPLY_PROP_CHARGE_NOW, &charge_now); if (ret == 0 && charge_now > 0 && voltage > 0) { dev->energy_now = charge_to_energy(charge_now, voltage); pr_debug("Calculated from CHARGE_NOW: %d µAh * %d µV = %d µWh\n", charge_now, voltage, dev->energy_now); } else { dev->energy_now = -1; } } ret = read_psy_property(bat_name, POWER_SUPPLY_PROP_ENERGY_FULL, &energy_full_direct); if (ret == 0 && energy_full_direct > 0) { dev->energy_full = energy_full_direct; pr_debug("Using ENERGY_FULL: %d µWh\n", dev->energy_full); } else { ret = read_psy_property(bat_name, POWER_SUPPLY_PROP_CHARGE_FULL, &charge_full); if (ret == 0 && charge_full > 0 && voltage > 0) { dev->energy_full = charge_to_energy(charge_full, voltage); pr_debug("Calculated from CHARGE_FULL: %d µAh * %d µV = %d µWh\n", charge_full, voltage, dev->energy_full); } else { ret = read_psy_property(bat_name, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, &charge_full); if (ret == 0 && charge_full > 0 && voltage > 0) { dev->energy_full = charge_to_energy(charge_full, voltage); } else { dev->energy_full = -1; } } } ret = read_psy_property(bat_name, POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, &dev->energy_full_design); if (ret < 0 || dev->energy_full_design <= 0) { dev->energy_full_design = dev->energy_full; } ret = read_psy_property(bat_name, POWER_SUPPLY_PROP_POWER_NOW, &dev->power_now); if (ret < 0) { int current_now; ret = read_psy_property(bat_name, POWER_SUPPLY_PROP_CURRENT_NOW, ¤t_now); if (ret == 0 && voltage > 0) { /* power (µW) = current (µA) * voltage (µV) / 1000000 */ dev->power_now = ((long long)current_now * voltage) / 1000000; } else { dev->power_now = 0; } } read_psy_property(bat_name, POWER_SUPPLY_PROP_TECHNOLOGY, &dev->technology); read_psy_property_string(bat_name, POWER_SUPPLY_PROP_SERIAL_NUMBER, dev->serial, sizeof(dev->serial)); if (dev->serial[0] == '\0') strcpy(dev->serial, "N/A"); mutex_unlock(&dev->lock); pr_debug("Battery %s: capacity=%d%%, voltage=%d µV, energy_now=%d µWh, energy_full=%d µWh\n", dev->battery_name, dev->capacity, dev->voltage, dev->energy_now, dev->energy_full); } static const char* get_status_string(int status) { switch (status) { case POWER_SUPPLY_STATUS_CHARGING: return "Charging"; case POWER_SUPPLY_STATUS_DISCHARGING: return "Discharging"; case POWER_SUPPLY_STATUS_FULL: return "Full"; case POWER_SUPPLY_STATUS_NOT_CHARGING: return "Not charging"; default: return "Unknown"; } } static const char* get_technology_string(int tech) { switch (tech) { case POWER_SUPPLY_TECHNOLOGY_LION: return "Lithium-ion"; case POWER_SUPPLY_TECHNOLOGY_LIPO: return "Lithium-polymer"; case POWER_SUPPLY_TECHNOLOGY_NiMH: return "NiMH"; default: return "Unknown"; } } static int battery_open(struct inode *inode, struct file *file) { struct battery_acpi_device *dev = container_of(inode->i_cdev, struct battery_acpi_device, cdev); file->private_data = dev; update_battery_data(dev); return 0; } static int battery_release(struct inode *inode, struct file *file) { return 0; } static ssize_t battery_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos) { struct battery_acpi_device *dev = file->private_data; char info[512]; int len; if (*f_pos > 0) return 0; update_battery_data(dev); dev->read_count++; if (!dev->present) { len = snprintf(info, sizeof(info), "=== Battery Information ===\n" " Battery: NOT PRESENT\n" " Read Count: %u\n", dev->read_count); } else { len = snprintf(info, sizeof(info), "=== Battery Information ===\n" " Battery Name: %s\n" " Present: Yes\n" " Capacity: %d%%\n" " Voltage: %d.%03d V\n" " Status: %s\n" " Energy Now: %d.%02d Wh\n" " Energy Full: %d.%02d Wh\n" " Energy Full Design:%d.%02d Wh\n" " Power Now: %d.%02d W\n" " Technology: %s\n" " Serial Number: %s\n" " Read Count: %u\n", dev->battery_name, dev->capacity, dev->voltage / 1000000, (dev->voltage % 1000000) / 1000, get_status_string(dev->status), dev->energy_now * (dev->voltage / 1000000) / 1000000, ((dev->energy_now * (dev->voltage / 1000000)) % 1000000) / 10000, dev->energy_full * (dev->voltage / 1000000) / 1000000, ((dev->energy_full * (dev->voltage / 1000000)) % 1000000) / 10000, dev->energy_full_design * (dev->voltage / 1000000) / 1000000, ((dev->energy_full_design * (dev->voltage / 1000000)) % 1000000) / 10000, dev->power_now / 1000000, (dev->power_now % 1000000) / 10000, get_technology_string(dev->technology), dev->serial, dev->read_count); } if (len > count) len = count; if (copy_to_user(buf, info, len)) return -EFAULT; *f_pos += len; return len; } static long battery_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct battery_acpi_device *dev = file->private_data; void __user *argp = (void __user *)arg; update_battery_data(dev); dev->ioctl_count++; switch (cmd) { case BATTERY_IOC_GET_PRESENT: if (copy_to_user(argp, &dev->present, sizeof(dev->present))) return -EFAULT; break; case BATTERY_IOC_GET_CAPACITY: if (copy_to_user(argp, &dev->capacity, sizeof(dev->capacity))) return -EFAULT; break; case BATTERY_IOC_GET_VOLTAGE: if (copy_to_user(argp, &dev->voltage, sizeof(dev->voltage))) return -EFAULT; break; case BATTERY_IOC_GET_STATUS: if (copy_to_user(argp, &dev->status, sizeof(dev->status))) return -EFAULT; break; case BATTERY_IOC_GET_ENERGY_NOW: if (copy_to_user(argp, &dev->energy_now, sizeof(dev->energy_now))) return -EFAULT; break; case BATTERY_IOC_GET_ENERGY_FULL: if (copy_to_user(argp, &dev->energy_full, sizeof(dev->energy_full))) return -EFAULT; break; case BATTERY_IOC_GET_POWER: if (copy_to_user(argp, &dev->power_now, sizeof(dev->power_now))) return -EFAULT; break; case BATTERY_IOC_GET_TECHNOLOGY: if (copy_to_user(argp, &dev->technology, sizeof(dev->technology))) return -EFAULT; break; case BATTERY_IOC_GET_SERIAL: if (copy_to_user(argp, dev->serial, sizeof(dev->serial))) return -EFAULT; break; default: return -ENOTTY; } return 0; } static const struct file_operations battery_fops = { .owner = THIS_MODULE, .open = battery_open, .release = battery_release, .read = battery_read, .unlocked_ioctl = battery_ioctl, }; int battery_acpi_driver_init(void) { dev_t dev_num; int ret; struct battery_acpi_device *dev; pr_info("Initializing Battery Character Device Driver v2.2\n"); ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME); if (ret < 0) { pr_err("Failed to allocate device number\n"); return ret; } dev = kzalloc(sizeof(struct battery_acpi_device), GFP_KERNEL); if (!dev) { ret = -ENOMEM; goto err_alloc; } mutex_init(&dev->lock); dev->class = class_create(THIS_MODULE, CLASS_NAME); if (IS_ERR(dev->class)) { ret = PTR_ERR(dev->class); goto err_class; } dev->device = device_create(dev->class, NULL, dev_num, dev, DEVICE_NAME); if (IS_ERR(dev->device)) { ret = PTR_ERR(dev->device); goto err_device; } cdev_init(&dev->cdev, &battery_fops); dev->cdev.owner = THIS_MODULE; ret = cdev_add(&dev->cdev, dev_num, 1); if (ret < 0) { goto err_cdev; } g_battery_dev = dev; update_battery_data(dev); pr_info("Battery driver initialized: /dev/%s\n", DEVICE_NAME); return 0; err_cdev: device_destroy(dev->class, dev_num); err_device: class_destroy(dev->class); err_class: mutex_destroy(&dev->lock); kfree(dev); err_alloc: unregister_chrdev_region(dev_num, 1); return ret; } void battery_acpi_driver_exit(void) { dev_t dev_num = g_battery_dev->cdev.dev; cdev_del(&g_battery_dev->cdev); device_destroy(g_battery_dev->class, dev_num); class_destroy(g_battery_dev->class); mutex_destroy(&g_battery_dev->lock); kfree(g_battery_dev); unregister_chrdev_region(dev_num, 1); pr_info("Battery driver exited\n"); }