sysfs和debugfs接口

sysfs

sysfs虚拟文件系统是使用kset,kobject,ktype组成一个有层次结构的模型,我们可以通过这个虚拟文件系统查看当前平台中的所有设备。内核也可以通过sysfs向上层传递参数。

sysfs接口api

class_create

在/sys/class目录下创建class类目录,

parameter:THIS_MODULE , class_name

return: struct class 结构体

class_create(THIS_MODULE, "class_name");

kobject_create_and_add

在/sys目录下创建目录

parameter:class_name ,struct kobject *parent

struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)

sysfs_create_file

在kobj对应目录下创建attr对应属性文件

parameter: kobj 在kobj目录下创建, attr属性文件

static int sysfs_create_file(struct kobject *kobj, const struct attribute *attr);

struct attribute {
    const char      *name;
    umode_t         mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
    bool            ignore_lockdep:1;
    struct lock_class_key   *key;
    struct lock_class_key   skey;
#endif
};

device_create_file

/**
 * device_create_file - create sysfs attribute file for device.
 * @dev: device.
 * @attr: device attribute descriptor.
 */
int device_create_file(struct device *dev,
               const struct device_attribute *attr)
{
    int error = 0; 

    if (dev) {
        WARN(((attr->attr.mode & S_IWUGO) && !attr->store),
            "Attribute %s: write permission without 'store'\n",
            attr->attr.name);
        WARN(((attr->attr.mode & S_IRUGO) && !attr->show),
            "Attribute %s: read permission without 'show'\n",                                                   attr->attr.name);
        error = sysfs_create_file(&dev->kobj, &attr->attr);
    }    
    return error;
}

sysfs_create_group

sysfs_create_group – given a directory kobject, create an attribute group

kobj: The kobject to create the group on

grp: The attribute group to create

int sysfs_create_group(struct kobject *kobj,const struct attribute_group *grp)
{
    return internal_create_group(kobj, 0, grp);
}

sysfs_create_groups

sysfs_create_groups – given a directory kobject, create a bunch of attribute groups

kobj: The kobject to create the group on

groups: The attribute groups to create, NULL terminated

int sysfs_create_groups(struct kobject *kobj,
            const struct attribute_group **groups)
{
    int error = 0;
    int i;

    if (!groups)
        return 0;

    for (i = 0; groups[i]; i++) {
        error = sysfs_create_group(kobj, groups[i]);
        if (error) {
            while (--i >= 0)
                sysfs_remove_group(kobj, groups[i]);
            break;
        }
    }
    return error;
}

strcut attribute

struct attribute *demo_attr[] = {
    &demo_kobj_attr_show.attr,
    &demo_kobj_attr_store.attr,
    NULL, //必须要有
};

ssize_t demo_show(struct kobject *kobj,struct kobj_attribute *attr,char *ubuf)
{
    char info[] = "kernel show is called\n";
    return sprintf(ubuf,"%s",info);
}

ssize_t demo_store(struct kobject *kobj,struct kobj_attribute *attr,const char *ubuf,size_t count)
{
    strncpy(kbuf,ubuf,count);

    printk("recv from user %s\n",kbuf);
    return count;
}

#if 0
struct kobj_attribute demo_kobj_attr_show = {
    .attr = {
        .name = "show",//shou就是一个属性文件名
        .mode = S_IRUSR,
    },
    .show = demo_show,
};

struct kobj_attribute demo_kobj_attr_store = {
    .attr = {
        .name = "store",//也是一个属性文件名
        .mode = S_IWUSR,
    },
    .store = demo_store,
};
#else

//show和store是属性文件名
struct kobj_attribute demo_kobj_attr_show = __ATTR(show,S_IRUSR,demo_show,NULL);
struct kobj_attribute demo_kobj_attr_store = __ATTR(store,S_IWUSR,NULL,demo_store);
#endif

DEVICE_ATTR

#define DEVICE_ATTR(_name, _mode, _show, _store) \
        struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

代码实例

使用内核提供的实例代码:samples/kobject/kset-example.c

/*
 * Sample kset and ktype implementation
 *
 * Copyright (C) 2004-2007 Greg Kroah-Hartman 
 * Copyright (C) 2007 Novell Inc.
 *
 * Released under the GPL version 2 only.
 *
 */
#include 
#include 
#include 
#include 
#include 
#include 

/*
 * This module shows how to create a kset in sysfs called
 * /sys/kernel/kset-example
 * Then tree kobjects are created and assigned to this kset, "foo", "baz",
 * and "bar".  In those kobjects, attributes of the same name are also
 * created and if an integer is written to these files, it can be later
 * read out of it.
 */


/*
 * This is our "object" that we will create a few of and register them with
 * sysfs.
 */
struct foo_obj {
    struct kobject kobj;
    int foo;
    int baz;
    int bar;
};
#define to_foo_obj(x) container_of(x, struct foo_obj, kobj)

/* a custom attribute that works just for a struct foo_obj. */
struct foo_attribute {
    struct attribute attr;
    ssize_t (*show)(struct foo_obj *foo, struct foo_attribute *attr, char *buf);
    ssize_t (*store)(struct foo_obj *foo, struct foo_attribute *attr, const char *buf, size_t count);
};
#define to_foo_attr(x) container_of(x, struct foo_attribute, attr)

/*
 * The default show function that must be passed to sysfs.  This will be
 * called by sysfs for whenever a show function is called by the user on a
 * sysfs file associated with the kobjects we have registered.  We need to
 * transpose back from a "default" kobject to our custom struct foo_obj and
 * then call the show function for that specific object.
 */
static ssize_t foo_attr_show(struct kobject *kobj,
                 struct attribute *attr,
                 char *buf)
{
    struct foo_attribute *attribute;
    struct foo_obj *foo;

    attribute = to_foo_attr(attr);
    foo = to_foo_obj(kobj);

    if (!attribute->show)
        return -EIO;

    return attribute->show(foo, attribute, buf);
}

/*
 * Just like the default show function above, but this one is for when the
 * sysfs "store" is requested (when a value is written to a file.)
 */
static ssize_t foo_attr_store(struct kobject *kobj,
                  struct attribute *attr,
                  const char *buf, size_t len)
{
    struct foo_attribute *attribute;
    struct foo_obj *foo;

    attribute = to_foo_attr(attr);
    foo = to_foo_obj(kobj);

    if (!attribute->store)
        return -EIO;

    return attribute->store(foo, attribute, buf, len);
}

/* Our custom sysfs_ops that we will associate with our ktype later on */
static const struct sysfs_ops foo_sysfs_ops = {
    .show = foo_attr_show,
    .store = foo_attr_store,
};

/*
 * The release function for our object.  This is REQUIRED by the kernel to
 * have.  We free the memory held in our object here.
 *
 * NEVER try to get away with just a "blank" release function to try to be
 * smarter than the kernel.  Turns out, no one ever is...
 */
static void foo_release(struct kobject *kobj)
{
    struct foo_obj *foo;

    foo = to_foo_obj(kobj);
    kfree(foo);
}

/*
 * The "foo" file where the .foo variable is read from and written to.
 */
static ssize_t foo_show(struct foo_obj *foo_obj, struct foo_attribute *attr,
            char *buf)
{
    return sprintf(buf, "%d\n", foo_obj->foo);
}

static ssize_t foo_store(struct foo_obj *foo_obj, struct foo_attribute *attr,
             const char *buf, size_t count)
{
    sscanf(buf, "%du", &foo_obj->foo);
    return count;
}

/* Sysfs attributes cannot be world-writable. */
static struct foo_attribute foo_attribute =
    __ATTR(foo, 0664, foo_show, foo_store);

/*
 * More complex function where we determine which variable is being accessed by
 * looking at the attribute for the "baz" and "bar" files.
 */
static ssize_t b_show(struct foo_obj *foo_obj, struct foo_attribute *attr,
              char *buf)
{
    int var;

    if (strcmp(attr->attr.name, "baz") == 0)
        var = foo_obj->baz;
    else
        var = foo_obj->bar;
    return sprintf(buf, "%d\n", var);
}

static ssize_t b_store(struct foo_obj *foo_obj, struct foo_attribute *attr,
               const char *buf, size_t count)
{
    int var;

    sscanf(buf, "%du", &var);
    if (strcmp(attr->attr.name, "baz") == 0)
        foo_obj->baz = var;
    else
        foo_obj->bar = var;
    return count;
}

static struct foo_attribute baz_attribute =
    __ATTR(baz, 0664, b_show, b_store);
static struct foo_attribute bar_attribute =
    __ATTR(bar, 0664, b_show, b_store);

/*
 * Create a group of attributes so that we can create and destroy them all
 * at once.
 */
static struct attribute *foo_default_attrs[] = {
    &foo_attribute.attr,
    &baz_attribute.attr,
    &bar_attribute.attr,
    NULL,   /* need to NULL terminate the list of attributes */
};

/*
 * Our own ktype for our kobjects.  Here we specify our sysfs ops, the
 * release function, and the set of default attributes we want created
 * whenever a kobject of this type is registered with the kernel.
 */
static struct kobj_type foo_ktype = {
    .sysfs_ops = &foo_sysfs_ops,
    .release = foo_release,
    .default_attrs = foo_default_attrs,
};

static struct kset *example_kset;
static struct foo_obj *foo_obj;
static struct foo_obj *bar_obj;
static struct foo_obj *baz_obj;

static struct foo_obj *create_foo_obj(const char *name)
{
    struct foo_obj *foo;
    int retval;

    /* allocate the memory for the whole object */
    foo = kzalloc(sizeof(*foo), GFP_KERNEL);
    if (!foo)
        return NULL;

    /*
     * As we have a kset for this kobject, we need to set it before calling
     * the kobject core.
     */
    //修改foo上层kset为example_kset目录
    foo->kobj.kset = example_kset;

    /*
     * Initialize and add the kobject to the kernel.  All the default files
     * will be created here.  As we have already specified a kset for this
     * kobject, we don't have to set a parent for the kobject, the kobject
     * will be placed beneath that kset automatically.
     */
    retval = kobject_init_and_add(&foo->kobj, &foo_ktype, NULL, "%s", name);
    if (retval) {
        kobject_put(&foo->kobj);
        return NULL;
    }

    /*
     * We are always responsible for sending the uevent that the kobject
     * was added to the system.
     */
    kobject_uevent(&foo->kobj, KOBJ_ADD);

    return foo;
}

static void destroy_foo_obj(struct foo_obj *foo)
{
    kobject_put(&foo->kobj);
}

static int __init example_init(void)
{
    /*
     * Create a kset with the name of "kset_example",
     * located under /sys/kernel/
     */
    //在/sys/kernel/下创建kset_example目录
    example_kset = kset_create_and_add("kset_example", NULL, kernel_kobj);
    if (!example_kset)
        return -ENOMEM;

    /*
     * Create three objects and register them with our kset
     */
    //在/sys/kernel/kset_example下创建foo、bar、baz三个文件夹
    foo_obj = create_foo_obj("foo");
    if (!foo_obj)
        goto foo_error;

    bar_obj = create_foo_obj("bar");
    if (!bar_obj)
        goto bar_error;

    baz_obj = create_foo_obj("baz");
    if (!baz_obj)
        goto baz_error;

    return 0;

baz_error:
    destroy_foo_obj(bar_obj);
bar_error:
    destroy_foo_obj(foo_obj);
foo_error:
    kset_unregister(example_kset);
    return -EINVAL;
}

static void __exit example_exit(void)
{
    destroy_foo_obj(baz_obj);
    destroy_foo_obj(bar_obj);
    destroy_foo_obj(foo_obj);
    kset_unregister(example_kset);
}

module_init(example_init);
module_exit(example_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Greg Kroah-Hartman ");


debugfs

DebugFS,顾名思义,是一种用于内核调试的虚拟文件系统,内核开发者通过debugfs和用户空间交换数据。通常情况下,最常用的内核调试手段是printk。但printk并不是所有情况都好用,比如打印的数据可能过多,我们真正关心的数据在大量的输出里不是那么一目了然;或者我们在调试时可能需要修改某些内核变量,这种情况下printk就无能为力,而如果为了修改某个值重新编译内核或者驱动又过于低效,此时就需要一个临时的文件系统可以把我们需要关心的数据映射到用户空间。

debugfs接口api

debugfs_create_dir

/**
 * debugfs_create_dir - create a directory in the debugfs filesystem
 * @name: a pointer to a string containing the name of the directory to
 *        create.
 * @parent: a pointer to the parent dentry for this file.  This should be a
 *          directory dentry if set.  If this parameter is NULL, then the
 *          directory will be created in the root of the debugfs filesystem.
 *
 * This function creates a directory in debugfs with the given name.
 *
 * This function will return a pointer to a dentry if it succeeds.  This
 * pointer must be passed to the debugfs_remove() function when the file is
 * to be removed (no automatic cleanup happens if your module is unloaded,
 * you are responsible here.)  If an error occurs, %NULL will be returned.
 *
 * If debugfs is not enabled in the kernel, the value -%ENODEV will be
 * returned.
 */
struct dentry *debugfs_create_dir(const char *name, struct dentry *parent)                                                                                              
{
    return __create_file(name, S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO,
                   parent, NULL, NULL);
}
EXPORT_SYMBOL_GPL(debugfs_create_dir);


debugfs_create_file

/**                                                                       
 * debugfs_create_file - create a file in the debugfs filesystem                                  
 * @name: a pointer to a string containing the name of the file to create.
 * @mode: the permission that the file should have.
 * @parent: a pointer to the parent dentry for this file.  This should be a
 *          directory dentry if set.  If this parameter is NULL, then the
 *          file will be created in the root of the debugfs filesystem.
 * @data: a pointer to something that the caller will want to get to later 
 *        on.  The inode.i_private pointer will point to this value on     
 *        the open() call. 
 * @fops: a pointer to a struct file_operations that should be used for
 *        this file.
 *
 * This is the basic "create a file" function for debugfs.  It allows for a
 * wide range of flexibility in creating a file, or a directory (if you want
 * to create a directory, the debugfs_create_dir() function is
 * recommended to be used instead.)
 * 
 * This function will return a pointer to a dentry if it succeeds.  This
 * pointer must be passed to the debugfs_remove() function when the file is
 * to be removed (no automatic cleanup happens if your module is unloaded,
 * you are responsible here.)  If an error occurs, %NULL will be returned.
 *
 * If debugfs is not enabled in the kernel, the value -%ENODEV will be
 * returned.
 */
struct dentry *debugfs_create_file(const char *name, umode_t mode, 
            struct dentry *parent, void *data, const struct file_operations *fops)                       
{
    switch (mode & S_IFMT) {     
    case S_IFREG:  
    case 0:     
        break;     
    default:   
        BUG();      
    }
    return __create_file(name, mode, parent, data, fops);    
} 
EXPORT_SYMBOL_GPL(debugfs_create_file);  


代码实例

下方代码的效果:在/sys/kernel/debug下创建bm_battery_debug目录,以及在此目录下创建bm_battery_vadc文件,并提供了show接口,用来查询电池的电压adc和温度adc值。

/*
 * Power supply driver for the aw3215 emulator
 *
 * Copyright (C) 2008 Google, Inc.
 * Copyright (C) 2012 Intel, Inc.
 * Copyright (C) 2013 Intel, Inc.
 * Author: Mike Lockwood 
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include "../../include/bmtfeatures.h"

#include 
#include 

#include 
#include 

#define CUTOFF_CUR_LEVEL 2
#define ADC_BUFF_LEN 100
#define PERCENT_BUFF_LEN 100

static int adc_buff[ADC_BUFF_LEN] = {0};
static int percent_buff[PERCENT_BUFF_LEN] = {0};
static bool charge_full_mask=false;
static int bat_get_chg_cap_percent(int64_t bat_vol);
static int bat_get_dischg_cap_percent(int64_t bat_vol);
static int64_t batt_avg_adc(int64_t bat_voltage);
static int batt_avg_percent(int percent);
static int get_bm_ftm(void);
static unsigned char retry_cnt = 0;
static unsigned char init_temperature_config_OK = 0;
static unsigned char init_bm_ftm_OK = 0;
static int bm_ftm_mode = 0;

// r600 battery capacity table 
//index is the percent% of battery capacity ,so index 0 is 0, index 101 is +inf
//value is in mV
//this battery's nominal capacity is:2000mAh,step is 20mAh
#if 0
static int r700_chg_bat_voltage[102]=
{
    0,
    3309,3318,3327,3336,3345,3354,3363,3372,3381,3390,
    3399,3408,3417,3426,3435,3444,3453,3462,3471,3480,
    3489,3498,3507,3516,3525,3534,3543,3552,3561,3570,
    3579,3588,3597,3606,3615,3624,3633,3642,3651,3660,
    3669,3678,3687,3696,3705,3714,3723,3732,3741,3750,
    3759,3768,3777,3786,3795,3804,3813,3822,3831,3840,
    3849,3858,3867,3876,3885,3894,3903,3912,3921,3930,
    3939,3948,3957,3966,3975,3984,3993,4002,4011,4020,
    4029,4038,4047,4056,4065,4074,4083,4092,4101,4110,
    4119,4128,4137,4146,4155,4164,4173,4182,4191,4200,
    LONG_MAX
};
static int r700_dischg_bat_voltage[102]=
{
    0,
    3259,3268,3277,3286,3295,3304,3313,3322,3331,3340,
    3349,3358,3367,3376,3385,3394,3403,3412,3421,3430,
    3439,3448,3457,3466,3475,3484,3493,3502,3511,3520,
    3529,3538,3547,3556,3565,3574,3583,3592,3601,3610,
    3619,3628,3637,3646,3655,3664,3673,3682,3691,3700,
    3709,3718,3727,3736,3745,3754,3763,3772,3781,3790,
    3799,3808,3817,3826,3835,3844,3853,3862,3871,3880,
    3889,3898,3907,3916,3925,3934,3943,3952,3961,3970,
    3979,3988,3997,4006,4015,4024,4033,4042,4051,4060,
    4069,4078,4087,4096,4105,4114,4123,4132,4141,4150,
    LONG_MAX
};
#else
static int r700_chg_bat_voltage[102]=
{
    0,
    3300,3320,3340,3360,3380,3400,3420,3440,3460,3480,
    3500,3520,3540,3560,3580,3600,3620,3640,3660,3680,
    3700,3706,3712,3718,3724,3730,3736,3742,3748,3754,
    3760,3766,3772,3778,3784,3790,3796,3802,3808,3814,
    3820,3826,3832,3838,3844,3850,3856,3862,3868,3874,
    3880,3886,3892,3898,3904,3910,3916,3922,3928,3934,
    3940,3946,3952,3958,3964,3970,3976,3982,3988,3994,
    4000,4006,4012,4018,4024,4030,4036,4042,4048,4054,
    4060,4066,4072,4078,4081,4084,4087,4090,4095,4110,
    4119,4128,4137,4146,4155,4164,4173,4182,4191,4200,
    LONG_MAX
};
static int r700_dischg_bat_voltage[102]=
{
    0,
    3250,3270,3290,3310,3330,3350,3370,3390,3410,3430,
    3450,3470,3490,3510,3530,3550,3570,3590,3610,3630,
    3650,3656,3662,3668,3674,3680,3686,3692,3698,3704,
    3710,3716,3722,3728,3734,3740,3746,3852,3858,3764,
    3770,3776,3782,3788,3794,3800,3806,3812,3818,3824,
    3830,3836,3842,3848,3854,3860,3866,3872,3878,3884,
    3890,3896,3902,3908,3914,3920,3926,3932,3938,3944,
    3950,3956,3962,3968,3974,3980,3986,3992,3998,4004,
    4010,4016,4022,4028,4031,4034,4037,4040,4045,4060,
    4069,4078,4087,4096,4105,4114,4123,4132,4141,4150,
    LONG_MAX
};
#endif

#define SHUT_DOWN_VOLTAGE 3300

struct aw3215_battery_data {
    int ctrl_gpio;
    int stat_gpio;
    int battery_temperature_vadc;// battery temperature vadc
    int64_t battery_capacity_vadc; // battery capacity vadc
    int cur_percent;
    int old_percent;
    bool online;
    bool old_online;
    bool ac_or_usb;
    bool old_ac_or_usb;
    bool full_changed;
    bool first;
    int status;
    int health;
    int old_health;
    int present;
    int old_present;
    spinlock_t lock;
    struct qpnp_vadc_chip *vadc;
    enum qpnp_vadc_channels adc_channel;
    struct workqueue_struct *volt_adc_queue;
    struct delayed_work volt_adc_work;
    struct power_supply battery;
    struct power_supply ac;
    struct notifier_block aw_notifier;
    struct timer_list timer;
    struct dentry *bm_aw3215_debugfs;
};
static struct aw3215_battery_data *battery_data;

static void set_cutoff_cur_func(unsigned long var);

/* add emergency hot temperature */
struct bm_temperature_me_type {
    int te_status;
    int te_range;
    void (*bm_temperature_cb)(int);
};

enum{
    BM_BATTERY_TEMP_CLASS_EMERGENCY_HOT,
    BM_BATTERY_TEMP_CLASS_HOT,
    BM_BATTERY_TEMP_CLASS_GOOD,
    BM_BATTERY_TEMP_CLASS_COLD,
};

static void bm_temp_emergency_hot_callback(int result_adc);
static void bm_temp_hot_callback(int result_adc);
static void bm_temp_good_callback(int result_adc);
static void bm_temp_cold_callback(int result_adc);

static struct bm_temperature_me_type bm_temperature_me[] = {
    {POWER_SUPPLY_HEALTH_OVERHEAT, TEMP_EMERGENCY_HOT_VOLTAGE, bm_temp_emergency_hot_callback},
    {POWER_SUPPLY_HEALTH_OVERHEAT, TEMP_HOT_VOLTAGE, bm_temp_hot_callback},
    {POWER_SUPPLY_HEALTH_GOOD, TEMP_COLD_VOLTAGE, bm_temp_good_callback},
    {POWER_SUPPLY_HEALTH_COLD, TEMP_OVER_COLD_VOLTAGE, bm_temp_cold_callback},
};

#define BM_BATTERY_TEMPERATURE_CONFIG_FILE "/etc/backup/bm_temp_cfg"
/* add end */

extern int aw_register_client(struct notifier_block *nb);
extern int aw_unregister_client(struct notifier_block *nb);

/* add debugfs support for aw3215_battery */
#define BM_AW3215_DEBUGFS_DIR "bm_battery_debug"
#define BM_AW3215_DEBUGFS_BATTERY_FILE "bm_battery_vadc"
static int bm_aw3215_debugfs_open(struct inode *node, struct file *file);
static int bm_aw3215_debugfs_show(struct seq_file *s, void *what);
/* add end */

static int get_bm_ftm(void)
{
    struct file *filep = NULL;
    char buf[3]={0};
    loff_t pos = 0;
    int ret = 0;
    mm_segment_t old_fs;
    old_fs = get_fs();
    //printk("----enter get bm_ftm\n");
    filep=filp_open("/etc/backup/bmftm",O_CREAT | O_RDWR,0);
    if(IS_ERR(filep)){
        printk("error- filp_open /etc/backup/bmftm filed!!!\n");
        return -1;
    }
    set_fs(KERNEL_DS);

    vfs_read(filep, buf, sizeof(buf), &pos);
    filp_close(filep, NULL);

    init_bm_ftm_OK = 1;
    set_fs(old_fs);

    ret = memcmp(buf,"1",1);    
    if(!ret){
        printk("----enter get bm_ftm 1\n");
        return 1;
    }
    else{
        printk("----enter get bm_ftm 0\n");
        return 0;
    }
}
static void init_temperature_config(void)
{
    struct file *filep;
    char buf[100] = {0};
    loff_t pos = 0;
    mm_segment_t old_fs;
    int e_cfg = 0, h_cfg = 0, g_cfg = 0, c_cfg = 0;

    old_fs = get_fs();
    filep = filp_open(BM_BATTERY_TEMPERATURE_CONFIG_FILE, O_RDONLY, 0);
    if(IS_ERR(filep)){
        //printk("aw3215_charger: error- filp_open %s filed!!!maybe do not configed or file system not inited.\n", BM_BATTERY_TEMPERATURE_CONFIG_FILE);
        return ;
    }

    set_fs(KERNEL_DS);

    vfs_read(filep, buf, sizeof(buf), &pos);
    filp_close(filep, NULL);

    if (strchr(buf, ','))
        sscanf(buf, "%d,%d,%d,%d", &e_cfg, &h_cfg, &g_cfg, &c_cfg);
    else {
        pr_err("aw3215_battery15_charger: %s, error, get config temperture str:%s\n", __func__, buf);
        set_fs(old_fs);
        return ;
    }
    pr_info("aw3215_charger: %s, config temperature:%d,%d,%d,%d\n", __func__, e_cfg, h_cfg, g_cfg, c_cfg);

    set_fs(old_fs);

    if (e_cfg)
        bm_temperature_me[BM_BATTERY_TEMP_CLASS_EMERGENCY_HOT].te_range = e_cfg;
    if (h_cfg)
        bm_temperature_me[BM_BATTERY_TEMP_CLASS_HOT].te_range = h_cfg;
    if (g_cfg)
        bm_temperature_me[BM_BATTERY_TEMP_CLASS_GOOD].te_range = g_cfg;
    if (c_cfg)
        bm_temperature_me[BM_BATTERY_TEMP_CLASS_COLD].te_range = c_cfg;

    init_temperature_config_OK = 1;

    return ;
}
static int init_bm_ftm_mode(void)
{
    return get_bm_ftm();
}
#define BM_TEMPERTURE_HOT_RETRY 50
static void bm_temp_emergency_hot_callback(int result_adc)
{
    if(!bm_ftm_mode) {
        pr_emerg("get_tmp_adc_val result_adc = %d\nemergency battery hot!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n",result_adc);
        kernel_power_off();
    }
    else
        pr_emerg("get_tmp_adc_val result_adc = %d\nemergency battery hot, but it is now ftm mode 1.!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n",result_adc);
    return ;
}
static void bm_temp_hot_callback(int result_adc)
{
    if(!bm_ftm_mode) {
        pr_emerg("get_tmp_adc_val result_adc = %d\nbattery hot!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n",result_adc);
        if (retry_cnt > BM_TEMPERTURE_HOT_RETRY) {
            kernel_power_off();
        }
        retry_cnt++;
    }
    else
        pr_emerg("get_tmp_adc_val result_adc = %d\nbattery hot, but it its now ftm mode 1.!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n",result_adc);
    return ;
}
static void bm_temp_good_callback(int result_adc)
{
    retry_cnt = 0;
    return ;
}
static void bm_temp_cold_callback(int result_adc)
{
    retry_cnt = 0;
#if 0
    if(!bm_ftm_mode) {
        pr_emerg("get_tmp_adc_val RESULT_UNSUP_HOST_adc = %d\nbattery cold!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n",result_adc);
                kernel_power_off();
    }
    else
        pr_emerg("get_tmp_adc_val result_adc = %d\nbattery colde, but it is now ftm mode 1.!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n",result_adc);
#endif
    return ;
}

static int get_tmp_adc_val(struct aw3215_battery_data *data)
{
    int32_t  rc;
    struct qpnp_vadc_result adc_result;
    int result_adc,a;
    long sum=0;

    int num = sizeof(bm_temperature_me) / sizeof(struct bm_temperature_me_type);
    int i = 0;

    //printk("get_tmp_adc_val start...\n");
    if(!data->vadc)
        return 0 ;
    for(a=0;a < 6;a++)
    {
        rc = qpnp_vadc_read(data->vadc,P_MUX6_1_1,&adc_result);
        if (rc) {
            printk("error- reading tmp adc channel  rc = %d\n", rc);
            return rc;
        }
        sum += (int)adc_result.physical;
    }
    result_adc = (int)(sum/6);
    //printk("get_tmp_adc_val battery_temperature_vadc = %d\n",result_adc); 
#if 0
    switch(result_adc){
#ifdef RAISE_HOT_TMP
    case 0 ... CPG_TEMP_HOT_VOLTAGE:
#else
    case 0 ... TEMP_HOT_VOLTAGE:
#endif         
        data->health = POWER_SUPPLY_HEALTH_OVERHEAT; 
        data->present = 1;      
        if(get_bm_ftm())
            kernel_power_off();
        break ;
#ifdef RAISE_HOT_TMP
    case CPG_TEMP_HOT_VOLTAGE + 1 ... TEMP_COLD_VOLTAGE:
#else
    case TEMP_HOT_VOLTAGE + 1 ... TEMP_COLD_VOLTAGE:
#endif 
        data->health = POWER_SUPPLY_HEALTH_GOOD ;
        data->present = 1;      
        break ;
    case TEMP_COLD_VOLTAGE + 1 ... TEMP_OVER_COLD_VOLTAGE:
        data->health = POWER_SUPPLY_HEALTH_COLD;
        data->present = 1;
        if(get_bm_ftm())
            kernel_power_off();
        break ;
    default:
        data->health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE ;
        data->present = 0;
        break ;
    }
#else
    for (i = 0; i < num; i++)
    {
        if(result_adc < bm_temperature_me[i].te_range){
            break;
        }
    }
    printk("result_adc = %d\n",result_adc);
    printk("bm_ftm_mode = %d\n",bm_ftm_mode);
    if(i != num){
        printk("bm_temperature_me[%d].te_status = %d\n", i, bm_temperature_me[i].te_status);
        data->health = bm_temperature_me[i].te_status;
        data->present = 1;
        if (bm_temperature_me[i].bm_temperature_cb)
            bm_temperature_me[i].bm_temperature_cb(result_adc);
    }else{
        printk("power supply health unspec failed!\n");
        data->health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
        data->present = 0;
    }
#endif
//  if(data->old_health != data->health || data->old_present != data->present)                      
//  power_supply_changed(&data->battery);   


  // data->old_health = data->health;
    //data->old_present = data->present;
    //printk("get_tmp_adc_val battery_temperature_vadc = %d\n",result_adc); 
    //printk("get_tmp_adc_val end...\n");
    return result_adc;
}
static int r700_get_battery_voltage(struct aw3215_battery_data *data,enum qpnp_vadc_channels channel)
{
    int32_t  rc;
    struct qpnp_vadc_result adc_result;
    int percent,a;
    int64_t bat_voltage =0;// in uV
    int64_t r700_bat_voltage =0;
    //printk("r700_get_battery_voltage start...\n");
    if(!data->vadc)
        return 0 ;
    for(a=0;a < 6;a++)
    {
        rc = qpnp_vadc_read(data->vadc,channel,&adc_result);
        if (rc) 
        {
            printk("error- reading voltage adc channel =%d,rc = %d\n",channel, rc);
            return rc;
        }
        bat_voltage += adc_result.physical;
    }

    do_div(bat_voltage,6000);
    r700_bat_voltage = batt_avg_adc(bat_voltage);
    battery_data->battery_capacity_vadc = r700_bat_voltage;
    //printk("get_battery_voltage bat_voltage = %lld\n",r700_bat_voltage);

#if 1
    if(data->online)
    {
        data->full_changed = gpio_get_value(data->stat_gpio);
        if(data->full_changed){
            percent = 100;  
        }
        else{
            percent = bat_get_chg_cap_percent(r700_bat_voltage);
            //printk("bat_get_chg_cap_percent = %d\n",percent);
        }
    }
    else
    {
        percent = bat_get_dischg_cap_percent(r700_bat_voltage);
        //printk("bat_get_dischg_cap_percent = %d\n",percent);
    }
#endif
    //  percent = bat_get_chg_cap_percent(r700_bat_voltage);
    //r700_percent = batt_avg_percent(percent);
    //if(percent < 2)
    if(r700_bat_voltage < SHUT_DOWN_VOLTAGE)
    {
        kernel_power_off();
    }
    //printk("r700_get_battery_voltage bat_voltage = %lld\n",r700_bat_voltage);
    //printk("r700_get_percent = %d\n",percent);
    return percent;
}

static int aw3215_ac_get_property(struct power_supply *psy,
        enum power_supply_property psp,
        union power_supply_propval *val)
{
    struct aw3215_battery_data *data = container_of(psy,struct aw3215_battery_data, ac);
    int ret = 0;

    switch (psp) {
    case POWER_SUPPLY_PROP_ONLINE:
        if(data->online)
        {
            if(data->ac_or_usb)
                val->intval = 0;
            else
                val->intval = 1;
        }else{
            val->intval = 0;
            //charge_full_mask=false;
        }
        //printk("ac get----------------------------ONLINE property intval = %d \n",val->intval);
        break;
    case POWER_SUPPLY_PROP_TYPE:
        if(data->ac_or_usb)
            //val->strval = "ac";
            val->intval = 3;
        else
            //val->strval = "usb";
            val->intval = 4;
        //      printk("ac get --------------------------- POWER_SUPPLY_PROP_TYPE %d \n",val->intval);
        break;
    default:
        ret = -EINVAL;
        break;
    }
    return ret;
}


static int aw3215_battery_get_property(struct power_supply *psy,
        enum power_supply_property psp,
        union power_supply_propval *val)
{
    struct aw3215_battery_data *data = container_of(psy,struct aw3215_battery_data, battery);
    int ret = 0;

    switch (psp) {
    case POWER_SUPPLY_PROP_STATUS:
        val->intval = data->status;//aw3215_BATTERY_READ(data, BATTERY_STATUS);
        //printk("battery get status property intval = %d \n",data->status);
        break;
    case POWER_SUPPLY_PROP_HEALTH:
        val->intval = data->health;//aw3215_BATTERY_READ(data, BATTERY_HEALTH);
        //printk("battery get health property intval = %d \n",val->intval);
        break;
    case POWER_SUPPLY_PROP_PRESENT:
        val->intval = data->present;
        //printk("battery get present property intval = %d \n",val->intval);
        break;
    case POWER_SUPPLY_PROP_TECHNOLOGY:
        val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
        //printk("battery get technology property intval = %d \n",val->intval);
        break;
    case POWER_SUPPLY_PROP_CAPACITY:
        val->intval = data->cur_percent;
        //printk("battery get capacity property intval = %d \n",val->intval);
        break;
    default:
        ret = -EINVAL;
        break;
    }

    return ret;
}

static enum power_supply_property aw3215_battery_props[] = {
    POWER_SUPPLY_PROP_STATUS,
    POWER_SUPPLY_PROP_HEALTH,
    POWER_SUPPLY_PROP_PRESENT,
    POWER_SUPPLY_PROP_TECHNOLOGY,
    POWER_SUPPLY_PROP_CAPACITY,
};

static enum power_supply_property aw3215_ac_props[] = {
    POWER_SUPPLY_PROP_ONLINE,
    POWER_SUPPLY_PROP_TYPE,
};


static void set_cutoff_using_func(unsigned long var)
{   
    int i;
    gpio_set_value(battery_data->ctrl_gpio, 0);
    msleep(20);
    for (i=0;ictrl_gpio, 0);
        udelay(2);
        gpio_set_value(battery_data->ctrl_gpio, 1);
        udelay(2);
    }
    //printk("set_cutoff_using_func var = %ld\n",var );
}

static void get_volt_val_func(struct work_struct *work)
{
    struct aw3215_battery_data *data = container_of(work,
            struct aw3215_battery_data, volt_adc_work.work);
    //printk("get_volt_val_func start...\n");
    data->adc_channel = VBAT_SNS;
    data->cur_percent =  r700_get_battery_voltage(data,data->adc_channel);
    if(data->first)
    {
        //printk("get_volt_val_func start data->first...\n");
        data->first = false;
        data->old_percent =  r700_get_battery_voltage(data,data->adc_channel);
        if(99old_percent)
            data->old_percent = data->cur_percent -1;
        if(1 >data->old_percent)
            data->old_percent = data->cur_percent +1;
    }
    if (!init_temperature_config_OK)
        init_temperature_config();
    if (!init_bm_ftm_OK)
        bm_ftm_mode = init_bm_ftm_mode();

    if((charge_full_mask)&&(data->cur_percent > 99)){
        data->cur_percent = 100;
        data->old_percent = 100;
        data->status =POWER_SUPPLY_STATUS_FULL; 
    }else{
        if(data->online)
        {
            //printk("get_volt_val_func start data->online...\n");
            data->full_changed = gpio_get_value(data->stat_gpio);
            if(data->full_changed){
                charge_full_mask=true;  
                //printk("-----------get_volt_val_func start data->online data->full_changed = ...\n");
            }
            set_cutoff_using_func(CUTOFF_CUR_LEVEL);
            data->status =POWER_SUPPLY_STATUS_CHARGING ;
            if(data->cur_percent >= 100){
                data->old_percent = 99;
                data->cur_percent = 99;
            }
            if(data->old_percent > data->cur_percent){
                data->cur_percent = data->old_percent;
            }else{
                data->old_percent = data->cur_percent;
            }   
        }else{
            //printk("get_volt_val_func start data->offline...\n");
            data->status = POWER_SUPPLY_STATUS_DISCHARGING;
            if(data->cur_percent > data->old_percent) 
                data->cur_percent = data->old_percent;
            else
                data->old_percent = data->cur_percent;
        }
    }

    power_supply_changed(&data->battery);   
    msleep(3000);
    data->battery_temperature_vadc =  get_tmp_adc_val(data);
    queue_delayed_work(data->volt_adc_queue,&data->volt_adc_work,3*HZ);
    //printk(">>>>>>data->old_percent = %d\n",data->old_percent);
    //printk(">>>>>>data->cur_percent = %d\n",data->cur_percent);
    //printk("get_volt_val_func end...\n");
}

static int aw_notifier_callback(struct notifier_block *self,
        unsigned long event, void *data)
{
    //printk("aw_notifier_callback start ....\n");
    struct aw3215_battery_data *pdata =
        container_of(self, struct aw3215_battery_data, aw_notifier);
    int *blank;
    if  (event == 123 ) {
        blank =  data;
        pdata->online = true;
        if (*blank == 4){
            //printk("~~~~~~~~~~data = %ld blank= %d\n",event,*blank);
            pdata->ac_or_usb= true;

        }
        else if (*blank == 5){
            pdata->ac_or_usb= false;
            //printk("~~~~~~~~~~data = %ld blank= %d\n",event,*blank);
        }
        power_supply_changed(&pdata->ac);
    }else if(event == 456){
        //printk("~~~~~~~~~~data = %ld *\n",event);
        pdata->online = false;
        //charge_full_mask=false;   
        power_supply_changed(&pdata->ac);
    }

    pdata->old_online = pdata->online;
    pdata->old_ac_or_usb = pdata->ac_or_usb;
    //printk("~~~~~~~~~~data = %ld *\n",event);
    //printk("aw_notifier_callback end ....\n");
    return 0;
}

static int bm_aw3215_debugfs_open(struct inode *node, struct file *file)
{
    return single_open(file, bm_aw3215_debugfs_show, NULL);
}

static int bm_aw3215_debugfs_show(struct seq_file *s, void *what)
{
//  printk("battery_data->battery_temperature_vadc = %d\n");
//  printk("battery_data->battery_capacity_vadc = %d\n");
    return seq_printf(s, "%d,%lld\n", battery_data->battery_temperature_vadc, battery_data->battery_capacity_vadc); // temperature vadc,capacity vadc.
}
static const struct file_operations fops_bm_aw3215_debugfs = {
    .open = bm_aw3215_debugfs_open,
    .read = seq_read,
    .llseek = seq_lseek,
    .release = single_release,
};
static int bm_aw3215_debug_fs_init(void)
{
    battery_data->bm_aw3215_debugfs = debugfs_create_dir(BM_AW3215_DEBUGFS_DIR, 0);

    if (!debugfs_create_file(BM_AW3215_DEBUGFS_BATTERY_FILE, S_IFREG | S_IRUGO, battery_data->bm_aw3215_debugfs, NULL, &fops_bm_aw3215_debugfs)){
        pr_err("aw3215_charger: %s, init failed.\n", __func__);
        return -1;
    }
    return 0;
}
static int aw3215_battery_probe(struct platform_device *pdev)
{
    int ret;
    //struct resource *r;
    struct aw3215_battery_data *data;
    struct device_node *np = pdev->dev.of_node;
    data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
    if (data == NULL)
        return -ENOMEM;
    spin_lock_init(&data->lock);

    data->battery.properties = aw3215_battery_props;
    data->battery.num_properties = ARRAY_SIZE(aw3215_battery_props);
    data->battery.get_property = aw3215_battery_get_property;
    data->battery.name = "battery";
    data->battery.type = POWER_SUPPLY_TYPE_BATTERY;

    data->ac.properties = aw3215_ac_props;
    data->ac.num_properties = ARRAY_SIZE(aw3215_ac_props);
    data->ac.get_property = aw3215_ac_get_property;
    data->ac.name = "ac";
    data->ac.type = POWER_SUPPLY_TYPE_MAINS;
    if (of_find_property(np, "qcom,chg-vadc", NULL)) {
        /* early for VADC get, defer probe if needed */
        data->vadc = qpnp_get_vadc(&pdev->dev, "chg");
        if (IS_ERR(data->vadc)) {
            ret = PTR_ERR(data->vadc);
            if (ret != -EPROBE_DEFER)
                pr_err("vadc property configured incorrectly\n");
            return ret;
        }
    }
    /* chg gpio info */
    data->ctrl_gpio = of_get_named_gpio(np,"mdm9607,battery-ctrl-gpio", 0);
    if (gpio_is_valid(data->ctrl_gpio)) {
        ret = gpio_request(data->ctrl_gpio, "aw9625_ctrl_gpio");
        if (ret < 0) {
            printk("failed to request GPIO %d, error %d\n",data->ctrl_gpio, ret);
            goto exit;
        }
        ret = gpio_direction_output(data->ctrl_gpio, 0);
        if (ret < 0) {
            printk("Failed to configure output direction for ctrlGPIO %d, error %d\n",data->ctrl_gpio, ret);
            goto exit_gpio;
        }       
    }


    data->stat_gpio = of_get_named_gpio(np,"mdm9607,battery-stat-gpio", 0);
    if (gpio_is_valid(data->stat_gpio)) {
        ret = gpio_request(data->stat_gpio, "aw9625_stat_gpio");
        if (ret < 0) {
            printk("failed to request GPIO %d, error %d\n",data->stat_gpio, ret);
            goto exit_gpio;
        }
        ret = gpio_direction_input(data->stat_gpio);
        if (ret < 0) {
            printk("Failed to configure input direction for GPIO %d, error %d\n",data->stat_gpio, ret);
            goto exit_stat_gpio;
        }
        printk("-----data->stat_gpio==%d-------\n",gpio_get_value(data->stat_gpio));
    }

    ret = power_supply_register(&pdev->dev, &data->ac);
    if (ret)
    {
        printk("register power supply ac failed \n");   
        goto exit_gpio;
    }
    //printk("register power supply ac success!!!\n");
    ret = power_supply_register(&pdev->dev, &data->battery);
    if (ret) {
        printk("register power supply battery failed \n");  
        goto exit_supply;
    }
    //printk("register power supply battery success!!!\n");
    data->online = false;
    data->first = true ;
    platform_set_drvdata(pdev, data);
    battery_data = data;
    INIT_DELAYED_WORK(&data->volt_adc_work,get_volt_val_func);
    data->volt_adc_queue = create_singlethread_workqueue("get_vlotage_value");
    queue_delayed_work(data->volt_adc_queue,&data->volt_adc_work,12*HZ);

    data->aw_notifier.notifier_call = aw_notifier_callback;
    ret = aw_register_client(&data->aw_notifier);
    if (ret)
        dev_err(&pdev->dev, "Unable to register aw_notifier: %d\n",ret);

    if(bm_aw3215_debug_fs_init() < 0)
        pr_err("aw3215_charger: init debugfs failed.\n");

    printk("aw3215 battery init probe is gone .\n");
    return 0;

exit_supply:
    power_supply_unregister(&data->ac);
    gpio_free(data->stat_gpio);
    gpio_free(data->ctrl_gpio);
exit_stat_gpio:
    if (gpio_is_valid(data->stat_gpio))
        gpio_free(data->stat_gpio);
exit_gpio:
    if (gpio_is_valid(data->ctrl_gpio))
        gpio_free(data->ctrl_gpio);
exit:
    kfree(data);

    return ret ;
}

static int aw3215_battery_remove(struct platform_device *pdev)
{
    struct aw3215_battery_data *data = platform_get_drvdata(pdev);

    power_supply_unregister(&data->battery);
    power_supply_unregister(&data->ac);
    aw_unregister_client(&data->aw_notifier);
    del_timer(&data->timer);

    if(battery_data->bm_aw3215_debugfs)
        debugfs_remove_recursive(battery_data->bm_aw3215_debugfs);
    battery_data = NULL;
    return 0;
}
static struct of_device_id aw3215_match_table[] = {
    { .compatible = "awinic,aw3215",},
    { },
};
static struct platform_driver aw3215_battery_device = {
    .probe      = aw3215_battery_probe,
    .remove     = aw3215_battery_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "aw3215-battery",
        .of_match_table = aw3215_match_table,
    }
};
module_platform_driver(aw3215_battery_device);

/*
   get battery capacity in percent by battery voltage
   @bat_vol: voltage in mV
   @return val:in percent%
   */
static  int bat_get_chg_cap_percent(int64_t bat_vol)
{
    static int last_percent = 0;
    if( bat_vol < 0)
        bat_vol = 0;
    if (bat_vol > r700_chg_bat_voltage[100])
        bat_vol = r700_chg_bat_voltage[100];

    //calculate percent

    if( bat_vol < r700_chg_bat_voltage[last_percent])
    {
        while( bat_vol < r700_chg_bat_voltage[last_percent])
            last_percent--;
    }
    else if( bat_vol >= r700_chg_bat_voltage[last_percent+1])
    {
        last_percent++;
        while( r700_chg_bat_voltage[last_percent+1] <= bat_vol)
        {
            last_percent++;
        }
        if(last_percent >= 100)
            last_percent = 100;
    }
    //printk("bat_vol = %lld , r700_chg_bat_voltage = %d\n",bat_vol,last_percent);
    return last_percent;
}
static  int bat_get_dischg_cap_percent(int64_t bat_vol)
{
    static int last_percent = 0;
    if( bat_vol < 0)
        bat_vol = 0;
    if (bat_vol > r700_dischg_bat_voltage[100])
        bat_vol = r700_dischg_bat_voltage[100];

    //calculate percent

    if( bat_vol < r700_dischg_bat_voltage[last_percent])
    {
        while( bat_vol < r700_dischg_bat_voltage[last_percent])
            last_percent--;
    }
    else if( bat_vol >= r700_dischg_bat_voltage[last_percent+1])
    {
        last_percent++;
        while( r700_dischg_bat_voltage[last_percent+1] <= bat_vol)
        {
            last_percent++;
        }
        if(last_percent >= 100)
            last_percent = 100;
    }
    //printk("bat_vol = %lld , r700_dischg_bat_voltage = %d\n",bat_vol,last_percent);
    return last_percent;
}

static int64_t batt_avg_adc(int64_t bat_voltage)
{
    int i;
    int stop = 0;
    int64_t sum = 0;

    for (i=0; i= ADC_BUFF_LEN-1) {
        memmove(adc_buff, adc_buff+1, (ADC_BUFF_LEN-1)*sizeof(adc_buff[0]));
        adc_buff[ADC_BUFF_LEN-1] = 0;
    }
    do_div(sum,(i+1));
    return sum;
}
static int batt_avg_percent(int percent)
{
    int i;
    int stop = 0;
    int sum = 0;

    for (i=0; i= PERCENT_BUFF_LEN-1) {
        memmove(percent_buff, percent_buff+1, (PERCENT_BUFF_LEN-1)*sizeof(percent_buff[0]));
        percent_buff[PERCENT_BUFF_LEN-1] = 0;
    }

    return (sum/(i+1));
}

MODULE_AUTHOR("hanshuailockwood@android.com");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Battery driver for the aw3215 emulator");

剑气纵横三万里

“为什么要努力?” “想去的地方很远,想要的东西很贵,喜欢的人很优秀,父母的白发,朋友的约定,周围人的嘲笑,以及,天生傲骨。”

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐