找回密码
 立即注册
收起左侧

求大神,帮忙编写这个PWM驱动的测试程序?

2
回复
6965
查看
[复制链接]
累计签到:47 天
连续签到:1 天
来源: 2016-9-28 17:07:18 显示全部楼层 |阅读模式
1Qter豆
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/completion.h>
#include <linux/workqueue.h>
#include <linux/list.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
#include <linux/cpufreq.h>
#include <linux/pwm/pwm.h>

static const char *REQUEST_SYSFS = "sysfs";
static LIST_HEAD(pwm_device_list);
static DEFINE_MUTEX(device_list_mutex);
static struct class pwm_class;
static struct workqueue_struct *pwm_handler_workqueue;

static int pwm_match_name(struct device *dev, void *name)
{
        return !strcmp(name, dev_name(dev));
}

static struct pwm_device *__pwm_request(struct pwm_device *p, const char *label)
{
        int ret;

        ret = test_and_set_bit(FLAG_REQUESTED, &p->flags);
        if (ret) {
                p = ERR_PTR(-EBUSY);
                goto done;
        }

        p->label = label;
        p->pid = current->pid;

        if (p->ops->request) {
                ret = p->ops->request(p);
                if (ret) {
                        p = ERR_PTR(ret);
                        clear_bit(FLAG_REQUESTED, &p->flags);
                        goto done;
                }
        }

done:
        return p;
}

static struct pwm_device *__pwm_request_byname(const char *name,
                                               const char *label)
{
        struct device *d;
        struct pwm_device *p;

        d = class_find_device(&pwm_class, NULL, (char *)name, pwm_match_name);
        if (!d) {
                p = ERR_PTR(-EINVAL);
                goto done;
        }
        if (IS_ERR(d)) {
                p = (struct pwm_device *)d;
                goto done;
        }

        p = __pwm_request(dev_get_drvdata(d), label);

done:
        return p;
}

struct pwm_device *pwm_request_byname(const char *name, const char *label)
{
        struct pwm_device *p;

        mutex_lock(&device_list_mutex);
        p = __pwm_request_byname(name, label);
        mutex_unlock(&device_list_mutex);
        return p;
}
EXPORT_SYMBOL(pwm_request_byname);

struct pwm_device *pwm_request(const char *bus_id, int id, const char *label)
{
        char name[256];
        int ret;

        if (id == -1)
                ret = scnprintf(name, sizeof name, "%s", bus_id);
        else
                ret = scnprintf(name, sizeof name, "%s:%d", bus_id, id);
        if (ret <= 0 || ret >= sizeof name)
                return ERR_PTR(-EINVAL);

        return pwm_request_byname(name, label);
}
EXPORT_SYMBOL(pwm_request);

void pwm_release(struct pwm_device *p)
{
        mutex_lock(&device_list_mutex);

        if (!test_and_clear_bit(FLAG_REQUESTED, &p->flags)) {
                pr_debug("%s pwm device is not requested!\n",
                                dev_name(p->dev));
                goto done;
        }

        pwm_stop(p);
        pwm_unsynchronize(p, NULL);
        pwm_set_handler(p, NULL, NULL);

        p->label = NULL;
        p->pid = -1;

        if (p->ops->release)
                p->ops->release(p);
done:
        mutex_unlock(&device_list_mutex);
}
EXPORT_SYMBOL(pwm_release);

unsigned long pwm_ns_to_ticks(struct pwm_device *p, unsigned long nsecs)
{
        unsigned long long ticks;

        ticks = nsecs;
        ticks *= p->tick_hz;
        do_div(ticks, 1000000000);
        return ticks;
}
EXPORT_SYMBOL(pwm_ns_to_ticks);

unsigned long pwm_ticks_to_ns(struct pwm_device *p, unsigned long ticks)
{
        unsigned long long ns;

        if (!p->tick_hz) {
                pr_debug("%s: frequency is zero\n", dev_name(p->dev));
                return 0;
        }

        ns = ticks;
        ns *= 1000000000UL;
        do_div(ns, p->tick_hz);
        return ns;
}
EXPORT_SYMBOL(pwm_ticks_to_ns);

static void pwm_config_ns_to_ticks(struct pwm_device *p, struct pwm_config *c)
{
        if (test_bit(PWM_CONFIG_PERIOD_NS, &c->config_mask)) {
                c->period_ticks = pwm_ns_to_ticks(p, c->period_ns);
                clear_bit(PWM_CONFIG_PERIOD_NS, &c->config_mask);
                set_bit(PWM_CONFIG_PERIOD_TICKS, &c->config_mask);
        }

        if (test_bit(PWM_CONFIG_DUTY_NS, &c->config_mask)) {
                c->duty_ticks = pwm_ns_to_ticks(p, c->duty_ns);
                clear_bit(PWM_CONFIG_DUTY_NS, &c->config_mask);
                set_bit(PWM_CONFIG_DUTY_TICKS, &c->config_mask);
        }
}

static void pwm_config_percent_to_ticks(struct pwm_device *p,
                                        struct pwm_config *c)
{
        if (test_bit(PWM_CONFIG_DUTY_PERCENT, &c->config_mask)) {
                if (test_bit(PWM_CONFIG_PERIOD_TICKS, &c->config_mask))
                        c->duty_ticks = c->period_ticks;
                else
                        c->duty_ticks = p->period_ticks;

                c->duty_ticks *= c->duty_percent;
                c->duty_ticks /= 100;
                clear_bit(PWM_CONFIG_DUTY_PERCENT, &c->config_mask);
                set_bit(PWM_CONFIG_DUTY_TICKS, &c->config_mask);
        }
}

int pwm_config_nosleep(struct pwm_device *p, struct pwm_config *c)
{
        if (!p->ops->config_nosleep)
                return -EINVAL;

        pwm_config_ns_to_ticks(p, c);
        pwm_config_percent_to_ticks(p, c);

        return p->ops->config_nosleep(p, c);
}
EXPORT_SYMBOL(pwm_config_nosleep);

int pwm_config(struct pwm_device *p, struct pwm_config *c)
{
        int ret = 0;

        pwm_config_ns_to_ticks(p, c);
        pwm_config_percent_to_ticks(p, c);

        switch (c->config_mask & (BIT(PWM_CONFIG_PERIOD_TICKS)
                                  | BIT(PWM_CONFIG_DUTY_TICKS))) {
        case BIT(PWM_CONFIG_PERIOD_TICKS):
                if (p->duty_ticks > c->period_ticks) {
                        ret = -EINVAL;
                        goto err;
                }
                break;
        case BIT(PWM_CONFIG_DUTY_TICKS):
                if (p->period_ticks < c->duty_ticks) {
                        ret = -EINVAL;
                        goto err;
                }
                break;
        case BIT(PWM_CONFIG_DUTY_TICKS) | BIT(PWM_CONFIG_PERIOD_TICKS):
                if (c->duty_ticks > c->period_ticks) {
                        ret = -EINVAL;
                        goto err;
                }
                break;
        default:
                break;
        }

err:
        dev_dbg(p->dev, "%s: config_mask %lu period_ticks %lu duty_ticks %lu"
                " polarity %d duty_ns %lu period_ns %lu duty_percent %d\n",
                __func__, c->config_mask, c->period_ticks, c->duty_ticks,
                c->polarity, c->duty_ns, c->period_ns, c->duty_percent);

        if (ret)
                return ret;
        spin_lock(&p->pwm_lock);
        ret = p->ops->config(p, c);
        spin_unlock(&p->pwm_lock);
        return ret;
}
EXPORT_SYMBOL(pwm_config);

int pwm_set_period_ns(struct pwm_device *p, unsigned long period_ns)
{
        struct pwm_config c = {
                .config_mask = BIT(PWM_CONFIG_PERIOD_TICKS),
                .period_ticks = pwm_ns_to_ticks(p, period_ns),
        };

        spin_lock(&p->pwm_lock);
        p->period_ns = period_ns;
        spin_unlock(&p->pwm_lock);
        return pwm_config(p, &c);
}
EXPORT_SYMBOL(pwm_set_period_ns);  //设置时间

unsigned long pwm_get_period_ns(struct pwm_device *p)
{
        return pwm_ticks_to_ns(p, p->period_ticks);
}
EXPORT_SYMBOL(pwm_get_period_ns);

int pwm_set_frequency(struct pwm_device *p, unsigned long freq)  //设置频率
{
        struct pwm_config c;

        if (!freq)
                return -EINVAL;

        c.config_mask = BIT(PWM_CONFIG_PERIOD_TICKS),
        c.period_ticks = pwm_ns_to_ticks(p, (NSEC_PER_SEC / freq)),
        spin_lock(&p->pwm_lock);
        p->period_ns = NSEC_PER_SEC / freq;
        spin_unlock(&p->pwm_lock);
        return pwm_config(p, &c);
}
EXPORT_SYMBOL(pwm_set_frequency);  

unsigned long pwm_get_frequency(struct pwm_device *p)
{
        unsigned long period_ns;

         period_ns = pwm_ticks_to_ns(p, p->period_ticks);

        if (!period_ns) {
                pr_debug("%s: frequency is zero\n", dev_name(p->dev));
                return 0;
        }

        return        NSEC_PER_SEC / period_ns;
}
EXPORT_SYMBOL(pwm_get_frequency);

int pwm_set_period_ticks(struct pwm_device *p,
                                        unsigned long ticks)
{
        struct pwm_config c = {
                .config_mask = BIT(PWM_CONFIG_PERIOD_TICKS),
                .period_ticks = ticks,
        };

        spin_lock(&p->pwm_lock);
        p->period_ns = pwm_ticks_to_ns(p, ticks);
        spin_unlock(&p->pwm_lock);
        return pwm_config(p, &c);
}
EXPORT_SYMBOL(pwm_set_period_ticks);

int pwm_set_duty_ns(struct pwm_device *p, unsigned long duty_ns)  //设置占空比
{
        struct pwm_config c = {
                .config_mask = BIT(PWM_CONFIG_DUTY_TICKS),
                .duty_ticks = pwm_ns_to_ticks(p, duty_ns),
        };
        spin_lock(&p->pwm_lock);
        p->duty_ns = duty_ns;
        spin_unlock(&p->pwm_lock);
        return pwm_config(p, &c);
}
EXPORT_SYMBOL(pwm_set_duty_ns);

unsigned long pwm_get_duty_ns(struct pwm_device *p)
{
        return pwm_ticks_to_ns(p, p->duty_ticks);
}
EXPORT_SYMBOL(pwm_get_duty_ns);

int pwm_set_duty_percent(struct pwm_device *p, int percent)  //设置占空比的百分比
{
        struct pwm_config c = {
                .config_mask = BIT(PWM_CONFIG_DUTY_PERCENT),
                .duty_percent = percent,
        };

        spin_lock(&p->pwm_lock);
        p->duty_ns = p->period_ns * percent;
        p->duty_ns /= 100;
        spin_unlock(&p->pwm_lock);
        return pwm_config(p, &c);
}
EXPORT_SYMBOL(pwm_set_duty_percent);

unsigned long pwm_get_duty_percent(struct pwm_device *p)
{
        unsigned long long duty_percent;

        if (!p->period_ns) {
                pr_debug("%s: frequency is zero\n", dev_name(p->dev));
                return 0;
        }

        duty_percent = pwm_ticks_to_ns(p, p->duty_ticks);
        duty_percent *= 100;
        do_div(duty_percent, p->period_ns);
        return duty_percent;
}
EXPORT_SYMBOL(pwm_get_duty_percent);

int pwm_set_duty_ticks(struct pwm_device *p,
                                        unsigned long ticks)
{
        struct pwm_config c = {
                .config_mask = BIT(PWM_CONFIG_DUTY_TICKS),
                .duty_ticks = ticks,
        };

        spin_lock(&p->pwm_lock);
        p->duty_ns = pwm_ticks_to_ns(p, ticks);
        spin_unlock(&p->pwm_lock);
        return pwm_config(p, &c);
}
EXPORT_SYMBOL(pwm_set_duty_ticks);

int pwm_set_polarity(struct pwm_device *p, int active_high)
{
        struct pwm_config c = {
                .config_mask = BIT(PWM_CONFIG_POLARITY),
                .polarity = active_high,
        };
        return pwm_config(p, &c);
}
EXPORT_SYMBOL(pwm_set_polarity);

int pwm_start(struct pwm_device *p)
{
        struct pwm_config c = {
                .config_mask = BIT(PWM_CONFIG_START),
        };
        return pwm_config(p, &c);
}
EXPORT_SYMBOL(pwm_start);

int pwm_stop(struct pwm_device *p)
{
        struct pwm_config c = {
                .config_mask = BIT(PWM_CONFIG_STOP),
        };
        return pwm_config(p, &c);
}
EXPORT_SYMBOL(pwm_stop);

int pwm_synchronize(struct pwm_device *p, struct pwm_device *to_p)  //同步单线程
{
        if (!p->ops->synchronize)
                return -EINVAL;

        return p->ops->synchronize(p, to_p);
}
EXPORT_SYMBOL(pwm_synchronize);

int pwm_unsynchronize(struct pwm_device *p, struct pwm_device *from_p)  //非同步
{
        if (!p->ops->unsynchronize)
                return -EINVAL;

        return p->ops->unsynchronize(p, from_p);
}
EXPORT_SYMBOL(pwm_unsynchronize);

static void pwm_handler(struct work_struct *w)
{
        struct pwm_device *p = container_of(w, struct pwm_device,
                                            handler_work);
        if (p->handler && p->handler(p, p->handler_data))
                pwm_stop(p);
}

static void __pwm_handler(struct pwm_device *p)
{
        queue_work(pwm_handler_workqueue, &p->handler_work);
}

int pwm_set_handler(struct pwm_device *p, pwm_handler_t handler, void *data)
{
        if (p->ops->set_callback) {
                p->handler_data = data;
                p->handler = handler;
                INIT_WORK(&p->handler_work, pwm_handler);
                return p->ops->set_callback(p, handler ? __pwm_callback : NULL);
        }
        return -EINVAL;
}
EXPORT_SYMBOL(pwm_set_handler);

static ssize_t pwm_run_show(struct device *dev,
                            struct device_attribute *attr,
                            char *buf)
{
        struct pwm_device *p = dev_get_drvdata(dev);
        return sprintf(buf, "%d\n", pwm_is_running(p));
}

static ssize_t pwm_run_store(struct device *dev,
                             struct device_attribute *attr,
                             const char *buf, size_t len)
{
        struct pwm_device *p = dev_get_drvdata(dev);
        int ret;

        if (sysfs_streq(buf, "1"))
                ret = pwm_start(p);
        else if (sysfs_streq(buf, "0"))
                ret = pwm_stop(p);
        else
                ret = -EINVAL;

        if (ret < 0)
                return ret;
        return len;
}
static DEVICE_ATTR(run, S_IRUGO | S_IWUSR, pwm_run_show, pwm_run_store);

static ssize_t pwm_tick_hz_show(struct device *dev,
                                struct device_attribute *attr,
                                char *buf)
{
        struct pwm_device *p = dev_get_drvdata(dev);
        return sprintf(buf, "%lu\n", p->tick_hz);
}
static DEVICE_ATTR(tick_hz, S_IRUGO, pwm_tick_hz_show, NULL);

static ssize_t pwm_duty_ns_show(struct device *dev,
                                struct device_attribute *attr,
                                char *buf)
{
        struct pwm_device *p = dev_get_drvdata(dev);
        return sprintf(buf, "%lu\n", pwm_get_duty_ns(p));
}

static ssize_t pwm_duty_ns_store(struct device *dev,
                                 struct device_attribute *attr,
                                 const char *buf, size_t len)
{
        unsigned long duty_ns;
        struct pwm_device *p = dev_get_drvdata(dev);
        int ret;

        if (!kstrtoul(buf, 10, &duty_ns)) {
                ret = pwm_set_duty_ns(p, duty_ns);

                if (ret < 0)
                        return ret;
        }

        return len;
}
static DEVICE_ATTR(duty_ns, S_IRUGO | S_IWUSR, pwm_duty_ns_show,
               pwm_duty_ns_store);

static ssize_t pwm_duty_percent_show(struct device *dev,
                                struct device_attribute *attr,
                                char *buf)
{
        struct pwm_device *p = dev_get_drvdata(dev);
        return sprintf(buf, "%lu\n", pwm_get_duty_percent(p));
}

static ssize_t pwm_duty_percent_store(struct device *dev,
                                 struct device_attribute *attr,
                                 const char *buf,
                                 size_t len)
{
        unsigned long duty_ns;
        struct pwm_device *p = dev_get_drvdata(dev);
        int ret;

        if (!kstrtoul(buf, 10, &duty_ns)) {
                ret = pwm_set_duty_percent(p, duty_ns);

                if (ret < 0)
                        return ret;
        }

        return len;
}

static DEVICE_ATTR(duty_percent, S_IRUGO | S_IWUSR, pwm_duty_percent_show,
               pwm_duty_percent_store);

static ssize_t pwm_period_ns_show(struct device *dev,
                                  struct device_attribute *attr,
                                  char *buf)
{
        struct pwm_device *p = dev_get_drvdata(dev);
        return sprintf(buf, "%lu\n", pwm_get_period_ns(p));
}

static ssize_t pwm_period_ns_store(struct device *dev,
                                   struct device_attribute *attr,
                                   const char *buf, size_t len)
{
        unsigned long period_ns;
        struct pwm_device *p = dev_get_drvdata(dev);
        int ret;

        if (!kstrtoul(buf, 10, &period_ns)) {
                ret = pwm_set_period_ns(p, period_ns);

                if (ret < 0)
                        return ret;
        }

        return len;
}
static DEVICE_ATTR(period_ns, S_IRUGO | S_IWUSR, pwm_period_ns_show,
               pwm_period_ns_store);

static ssize_t pwm_period_freq_show(struct device *dev,
                                  struct device_attribute *attr,
                                  char *buf)
{
        struct pwm_device *p = dev_get_drvdata(dev);
        return sprintf(buf, "%lu\n", pwm_get_frequency(p));
}

static ssize_t pwm_period_freq_store(struct device *dev,
                                   struct device_attribute *attr,
                                   const char *buf,
                                   size_t len)
{
        unsigned long freq_hz;
        int ret;

        struct pwm_device *p = dev_get_drvdata(dev);
        if (!kstrtoul(buf, 10, &freq_hz)) {
                ret = pwm_set_frequency(p, freq_hz);

                if (ret < 0)
                        return ret;
        }
        return len;
}

static DEVICE_ATTR(period_freq, S_IRUGO | S_IWUSR, pwm_period_freq_show,  //方向
               pwm_period_freq_store);

static ssize_t pwm_polarity_show(struct device *dev,
                                 struct device_attribute *attr,
                                 char *buf)
{
        struct pwm_device *p = dev_get_drvdata(dev);
        return sprintf(buf, "%d\n", p->active_high ? 1 : 0);
}

static ssize_t pwm_polarity_store(struct device *dev,
                                  struct device_attribute *attr,
                                  const char *buf, size_t len)
{
        unsigned long polarity;
        struct pwm_device *p = dev_get_drvdata(dev);
        int ret;

        if (!kstrtoul(buf, 10, &polarity)) {
                ret = pwm_set_polarity(p, polarity);

                if (ret < 0)
                        return ret;
        }

        return len;
}
static DEVICE_ATTR(polarity, S_IRUGO | S_IWUSR, pwm_polarity_show,
               pwm_polarity_store);

static ssize_t pwm_request_show(struct device *dev,
                                struct device_attribute *attr,
                                char *buf)
{
        struct pwm_device *p = dev_get_drvdata(dev);
        int ret;

        ret = test_bit(FLAG_REQUESTED, &p->flags);

        if (ret)
                return sprintf(buf, "%s requested by %s\n",
                                dev_name(p->dev), p->label);
        else
                return sprintf(buf, "%s is free\n",
                                dev_name(p->dev));
}

static ssize_t pwm_request_store(struct device *dev,
                                 struct device_attribute *attr,
                                 const char *buf, size_t len)
{
        struct pwm_device *p = dev_get_drvdata(dev);
        unsigned long request;
        struct pwm_device *ret;

        if (!kstrtoul(buf, 10, &request)) {
                if (request) {
                        mutex_lock(&device_list_mutex);
                        ret = __pwm_request(p, REQUEST_SYSFS);
                        mutex_unlock(&device_list_mutex);

                        if (IS_ERR(ret))
                                return PTR_ERR(ret);
                } else
                        pwm_release(p);
        }

        return len;
}
static DEVICE_ATTR(request, S_IRUGO | S_IWUSR, pwm_request_show,
               pwm_request_store);

static const struct attribute *pwm_attrs[] = {
        &dev_attr_tick_hz.attr,
        &dev_attr_run.attr,
        &dev_attr_polarity.attr,
        &dev_attr_duty_ns.attr,
        &dev_attr_period_ns.attr,
        &dev_attr_request.attr,
        &dev_attr_duty_percent.attr,
        &dev_attr_period_freq.attr,
        NULL,
};

static const struct attribute_group pwm_device_attr_group = {
        .attrs = (struct attribute **) pwm_attrs,
};

static struct class_attribute pwm_class_attrs[] = {
        __ATTR_NULL,
};

static struct class pwm_class = {
        .name = "pwm",
        .owner = THIS_MODULE,

        .class_attrs = pwm_class_attrs,
};

static int pwm_freq_transition_notifier_cb(struct notifier_block *nb,
                unsigned long val, void *data)
{
        struct pwm_device *p;

        p = container_of(nb, struct pwm_device, freq_transition);

        if (val == CPUFREQ_POSTCHANGE && pwm_is_requested(p))
                p->ops->freq_transition_notifier_cb(p);

        return 0;
}

static inline int pwm_cpufreq_notifier_register(struct pwm_device *p)   //注册
{
        p->freq_transition.notifier_call = pwm_freq_transition_notifier_cb;

        return cpufreq_register_notifier(&p->freq_transition,
                       CPUFREQ_TRANSITION_NOTIFIER);
}

int pwm_register_byname(struct pwm_device *p, struct device *parent,
                        const char *name)
{
        struct device *d;
        int ret;

        if (!p->ops || !p->ops->config)
                return -EINVAL;

        mutex_lock(&device_list_mutex);

        d = class_find_device(&pwm_class, NULL, (char *)name, pwm_match_name);
        if (d) {
                ret = -EEXIST;
                goto err_found_device;
        }

        p->dev = device_create(&pwm_class, parent, MKDEV(0, 0), NULL, name);
        if (IS_ERR(p->dev)) {
                ret = PTR_ERR(p->dev);
                goto err_device_create;
        }

        ret = sysfs_create_group(&p->dev->kobj, &pwm_device_attr_group);
        if (ret)
                goto err_create_group;

        dev_set_drvdata(p->dev, p);
        p->flags = BIT(FLAG_REGISTERED);

        ret = pwm_cpufreq_notifier_register(p);

        if (ret < 0)
                printk(KERN_ERR "Failed to add cpufreq notifier\n");

        spin_lock_init(&p->pwm_lock);
        goto done;

err_create_group:
        device_unregister(p->dev);
        p->flags = 0;

err_device_create:
err_found_device:
done:
        mutex_unlock(&device_list_mutex);

        return ret;
}
EXPORT_SYMBOL(pwm_register_byname);//登记注册

int pwm_register(struct pwm_device *p, struct device *parent, int id)
{
        int ret;
        char name[256];

        if (IS_ERR_OR_NULL(parent))
                return -EINVAL;

        if (id == -1)
                ret = scnprintf(name, sizeof name, "%s", dev_name(parent));
        else
                ret = scnprintf(name, sizeof name, "%s:%d",
                               dev_name(parent), id);
        if (ret <= 0 || ret >= sizeof name)
                return -EINVAL;

        return pwm_register_byname(p, parent, name);
}
EXPORT_SYMBOL(pwm_register);

int pwm_unregister(struct pwm_device *p)
{
        int ret = 0;

        mutex_lock(&device_list_mutex);

        if (pwm_is_running(p) || pwm_is_requested(p)) {
                ret = -EBUSY;
                goto done;
        }

        sysfs_remove_group(&p->dev->kobj, &pwm_device_attr_group);
        device_unregister(p->dev);
        p->flags = 0;

done:
        mutex_unlock(&device_list_mutex);

        return ret;
}
EXPORT_SYMBOL(pwm_unregister);

static int __init pwm_init(void)
{
        return class_register(&pwm_class);
}

static void __exit pwm_exit(void)
{
        class_unregister(&pwm_class);
}

#ifdef MODULE
module_init(pwm_init);
module_exit(pwm_exit);
MODULE_LICENSE("GPL");
#else
postcore_initcall(pwm_init);
#endif

回复

使用道具 举报

累计签到:597 天
连续签到:1 天
2016-9-28 17:45:31 显示全部楼层
也没有工程树,这样看晕了...
回复

使用道具 举报

累计签到:47 天
连续签到:1 天
2016-9-29 15:33:47 显示全部楼层
shasidaran 发表于 2016-9-28 17:45
也没有工程树,这样看晕了...

我看了老半天,也是木有看明白
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

公告
可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了