Skip to content
Snippets Groups Projects
dslmngr.c 64.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • Anjan Chanda's avatar
    Anjan Chanda committed
    /*
    
     * dslmngr.c - provides "dsl" UBUS object
    
    Anjan Chanda's avatar
    Anjan Chanda committed
     *
    
     * Copyright (C) 2019 iopsys Software Solutions AB. All rights reserved.
    
    Anjan Chanda's avatar
    Anjan Chanda committed
     *
    
     * Author: anjan.chanda@iopsys.eu
     *         yalu.zhang@iopsys.eu
    
    Anjan Chanda's avatar
    Anjan Chanda committed
     *
     * This program is free software; you can redistribute it and/or
     * modify it under the terms of the GNU General Public License
     * version 2 as published by the Free Software Foundation.
     *
     * 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.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program; if not, write to the Free Software
     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
     * 02110-1301 USA
     */
    
    #ifndef _GNU_SOURCE
    #	define _GNU_SOURCE // For asprintf()
    #endif
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    #include <stdio.h>
    #include <libubox/blobmsg.h>
    #include <libubox/blobmsg_json.h>
    #include <libubox/uloop.h>
    #include <libubox/ustream.h>
    #include <libubox/utils.h>
    #include <uci.h>
    
    Jani Juvan's avatar
    Jani Juvan committed
    #include <net/if.h>
    #include <easy/easy.h>
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    #include "dslmngr.h"
    
    
    #define DSL_OBJECT_LINE "line"
    #define DSL_OBJECT_CHANNEL "channel"
    
    struct value2text {
    	int value;
    	char *text;
    };
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    enum {
    
    	DSL_STATS_INTERVAL,
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	__DSL_STATS_MAX,
    };
    
    static const struct blobmsg_policy dsl_stats_policy[__DSL_STATS_MAX] = {
    
    	[DSL_STATS_INTERVAL] = { .name = "interval", .type = BLOBMSG_TYPE_STRING },
    
    enum {
    	DSL_CONFIGURE_XTSE,
    	DSL_CONFIGURE_VDSL2_PROFILES,
    	DSL_CONFIGURE_FAST_PROFILES,
    	DSL_CONFIGURE_ENABLE_DATA_GATHERING,
    	DSL_CONFIGURE_LIMIT_MASK,
    	DSL_CONFIGURE_US0_MASK,
    	__DSL_CONFIGURE_MAX
    };
    
    static const struct blobmsg_policy dsl_configure_policy[] = {
    	[DSL_CONFIGURE_XTSE] = { .name = "xtse", .type = BLOBMSG_TYPE_STRING },
    	[DSL_CONFIGURE_VDSL2_PROFILES] = { .name = "vdsl2_profiles", .type = BLOBMSG_TYPE_STRING },
    	[DSL_CONFIGURE_FAST_PROFILES] = { .name = "fast_profiles", .type = BLOBMSG_TYPE_STRING },
    	[DSL_CONFIGURE_ENABLE_DATA_GATHERING] = { .name = "data_gathering", .type = BLOBMSG_TYPE_BOOL },
    	[DSL_CONFIGURE_LIMIT_MASK] = { .name = "limit_mask", .type = BLOBMSG_TYPE_INT32 },
    	[DSL_CONFIGURE_US0_MASK] = { .name = "us0_mask", .type = BLOBMSG_TYPE_INT32 }
    };
    
    
    Yalu Zhang's avatar
    Yalu Zhang committed
    static const char *dsl_if_status_str(enum itf_status status)
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    {
    
    	case IF_UP: return "up";
    	case IF_DOWN: return "down";
    	case IF_DORMANT: return "dormant";
    
    	case IF_NOTPRESENT: return "not_present";
    	case IF_LLDOWN: return "lower_layer_down";
    
    	case IF_ERROR: return "error";
    	case IF_UNKNOWN:
    	default: return "unknown";
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	}
    
    static const char *dsl_link_status_str(enum dsl_link_status status)
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    {
    
    	switch (status) {
    
    	case LINK_UP: return "up";
    	case LINK_INITIALIZING: return "initializing";
    	case LINK_ESTABLISHING: return "establishing";
    
    	case LINK_NOSIGNAL: return "no_signal";
    
    	case LINK_DISABLED: return "disabled";
    	case LINK_ERROR: return "error";
    	default: return "unknown";
    
    static const char *dsl_mod_str(enum dsl_modtype mod)
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    {
    
    	switch (mod) {
    
    	case MOD_G_922_1_ANNEX_A: return "gdmt_annexa";
    	case MOD_G_922_1_ANNEX_B: return "gdmt_annexb";
    	case MOD_G_922_1_ANNEX_C: return "gdmt_annexc";
    
    	case MOD_T1_413: return "t1413";
    
    	case MOD_T1_413i2: return "t1413_i2";
    	case MOD_ETSI_101_388: return "etsi_101_388";
    
    	case MOD_G_992_2: return "glite";
    
    	case MOD_G_992_3_Annex_A: return "adsl2_annexa";
    	case MOD_G_992_3_Annex_B: return "adsl2_annexb";
    	case MOD_G_992_3_Annex_C: return "adsl2_annexc";
    	case MOD_G_992_3_Annex_I: return "adsl2_annexi";
    	case MOD_G_992_3_Annex_J: return "adsl2_annexj";
    	case MOD_G_992_3_Annex_L: return "adsl2_annexl";
    	case MOD_G_992_3_Annex_M: return "adsl2_annexm";
    	case MOD_G_992_4: return "splitterless_adsl2";
    	case MOD_G_992_5_Annex_A: return "adsl2p_annexa";
    	case MOD_G_992_5_Annex_B: return "adsl2p_annexb";
    	case MOD_G_992_5_Annex_C: return "adsl2p_annexc";
    	case MOD_G_992_5_Annex_I: return "adsl2p_annexi";
    	case MOD_G_992_5_Annex_J: return "adsl2p_annexj";
    	case MOD_G_992_5_Annex_M: return "adsl2p_annexm";
    
    	case MOD_G_993_1: return "vdsl";
    
    	case MOD_G_993_1_Annex_A: return "vdsl_annexa";
    	case MOD_G_993_2_Annex_A: return "vdsl2_annexa";
    	case MOD_G_993_2_Annex_B: return "vdsl2_annexb";
    	case MOD_G_993_2_Annex_C: return "vdsl2_annexc";
    
    	default: return "unknown";
    
    static const char *dsl_xtse_str(enum dsl_xtse_bit xtse)
    {
    	switch (xtse) {
    	/* Octet 1 - ADSL, i.e. g.dmt */
    	case T1_413: return dsl_mod_str(MOD_T1_413);
    	case ETSI_101_388: return dsl_mod_str(MOD_ETSI_101_388);
    	case G_992_1_POTS_NON_OVERLAPPED: return dsl_mod_str(MOD_G_922_1_ANNEX_A);
    	case G_992_1_POTS_OVERLAPPED: return dsl_mod_str(MOD_G_922_1_ANNEX_A);
    	case G_992_1_ISDN_NON_OVERLAPPED: return dsl_mod_str(MOD_G_922_1_ANNEX_B);
    	case G_992_1_ISDN_OVERLAPPED: return dsl_mod_str(MOD_G_922_1_ANNEX_B);
    	case G_992_1_TCM_ISDN_NON_OVERLAPPED: return dsl_mod_str(MOD_G_922_1_ANNEX_C);
    	case G_992_1_TCM_ISDN_OVERLAPPED: return dsl_mod_str(MOD_G_922_1_ANNEX_C);
    
    	/* Octet 2 - Splitter-less ADSL, i.e. g.lite */
    	case G_992_2_POTS_NON_OVERLAPPED: return dsl_mod_str(MOD_G_992_2);
    	case G_992_2_POTS_OVERLAPPED: return dsl_mod_str(MOD_G_992_2);
    	case G_992_2_TCM_ISDN_NON_OVERLAPPED: return dsl_mod_str(MOD_G_992_2);
    	case G_992_2_TCM_ISDN_OVERLAPPED: return dsl_mod_str(MOD_G_992_2);
    	/* Bits 13 - 16 are reserved */
    
    	/* Octet 3 - ADSL2 */
    	/* Bits 17 - 18 are reserved */
    	case G_992_3_POTS_NON_OVERLAPPED: return dsl_mod_str(MOD_G_992_3_Annex_A);
    	case G_992_3_POTS_OVERLAPPED: return dsl_mod_str(MOD_G_992_3_Annex_A);
    	case G_992_3_ISDN_NON_OVERLAPPED: return dsl_mod_str(MOD_G_992_3_Annex_B);
    	case G_992_3_ISDN_OVERLAPPED: return dsl_mod_str(MOD_G_992_3_Annex_B);
    	case G_992_3_TCM_ISDN_NON_OVERLAPPED: return dsl_mod_str(MOD_G_992_3_Annex_C);
    	case G_992_3_TCM_ISDN_OVERLAPPED: return dsl_mod_str(MOD_G_992_3_Annex_C);
    
    	/* Octet 4 - Splitter-less ADSL2 and ADSL2 */
    	case G_992_4_POTS_NON_OVERLAPPED: return dsl_mod_str(MOD_G_992_4);
    	case G_992_4_POTS_OVERLAPPED: return dsl_mod_str(MOD_G_992_4);
    	/* Bits 27 - 28 are reserved */
    	case G_992_3_ANNEX_I_NON_OVERLAPPED: return dsl_mod_str(MOD_G_992_3_Annex_I);
    	case G_992_3_ANNEX_I_OVERLAPPED: return dsl_mod_str(MOD_G_992_3_Annex_I);
    	case G_992_3_ANNEX_J_NON_OVERLAPPED: return dsl_mod_str(MOD_G_992_3_Annex_J);
    	case G_992_3_ANNEX_J_OVERLAPPED: return dsl_mod_str(MOD_G_992_3_Annex_J);
    
    
    	/* Octet 5 - Splitter-less ADSL2 and ADSL2 */
    	case G_992_4_ANNEX_I_NON_OVERLAPPED: return dsl_mod_str(MOD_G_992_4);
    	case G_992_4_ANNEX_I_OVERLAPPED: return dsl_mod_str(MOD_G_992_4);
    	case G_992_3_POTS_MODE_1: return dsl_mod_str(MOD_G_992_3_Annex_L);
    	case G_992_3_POTS_MODE_2: return dsl_mod_str(MOD_G_992_3_Annex_L);
    	case G_992_3_POTS_MODE_3: return dsl_mod_str(MOD_G_992_3_Annex_L);
    	case G_992_3_POTS_MODE_4: return dsl_mod_str(MOD_G_992_3_Annex_L);
    	case G_992_3_EXT_POTS_NON_OVERLAPPED: return dsl_mod_str(MOD_G_992_3_Annex_M);
    	case G_992_3_EXT_POTS_OVERLAPPED: return dsl_mod_str(MOD_G_992_3_Annex_M);
    
    	/* Octet 6 - ADSL2+ */
    	case G_992_5_POTS_NON_OVERLAPPED: return dsl_mod_str(MOD_G_992_5_Annex_A);
    	case G_992_5_POTS_OVERLAPPED: return dsl_mod_str(MOD_G_992_5_Annex_A);
    	case G_992_5_ISDN_NON_OVERLAPPED: return dsl_mod_str(MOD_G_992_5_Annex_B);
    	case G_992_5_ISDN_OVERLAPPED: return dsl_mod_str(MOD_G_992_5_Annex_B);
    	case G_992_5_TCM_ISDN_NON_OVERLAPPED: return dsl_mod_str(MOD_G_992_5_Annex_C);
    	case G_992_5_TCM_ISDN_OVERLAPPED: return dsl_mod_str(MOD_G_992_5_Annex_C);
    	case G_992_5_ANNEX_I_NON_OVERLAPPED: return dsl_mod_str(MOD_G_992_5_Annex_I);
    	case G_992_5_ANNEX_I_OVERLAPPED: return dsl_mod_str(MOD_G_992_5_Annex_I);
    
    	/* Octet 7 - ADSL2+ */
    	case G_992_5_ANNEX_J_NON_OVERLAPPED: return dsl_mod_str(MOD_G_992_5_Annex_J);
    	case G_992_5_ANNEX_J_OVERLAPPED: return dsl_mod_str(MOD_G_992_5_Annex_J);
    	case G_992_5_EXT_POTS_NON_OVERLAPPED: return dsl_mod_str(MOD_G_992_5_Annex_M);
    	case G_992_5_EXT_POTS_OVERLAPPED: return dsl_mod_str(MOD_G_992_5_Annex_M);
    	/* Bits 53 - 56 are reserved */
    
    	/* Octet 8 - VDSL2 */
    	case G_993_2_NORTH_AMERICA: return dsl_mod_str(MOD_G_993_2_Annex_A);
    	case G_993_2_EUROPE: return dsl_mod_str(MOD_G_993_2_Annex_B);
    	case G_993_2_JAPAN: return dsl_mod_str(MOD_G_993_2_Annex_C);
    	/* Bits 60 - 64 are reserved */
    
    	default:
    		return "unknown";
    	}
    }
    
    
    static const char *dsl_line_encoding_str(enum dsl_line_encoding encoding)
    {
    	switch (encoding) {
    
    	case LE_DMT: return "dmt";
    	case LE_CAP: return "cap";
    	case LE_2B1Q: return "2b1q";
    	case LE_43BT: return "43bt";
    	case LE_PAM: return "pam";
    	case LE_QAM: return "qam";
    	default: return "unknown";
    
    static const char *dsl_profile_str(enum dsl_profile profile)
    {
    
    	const char *str = dsl_get_string_value(vdsl2_profiles, profile);
    
    	if (!str)
    		return "unknown";
    
    	return str;
    };
    
    static const char *fast_profile_str(enum fast_profile profile)
    {
    	const char *str = dsl_get_string_value(fast_profiles, profile);
    
    	if (!str)
    		return "unknown";
    
    	return str;
    
    };
    
    static const char *dsl_power_state_str(enum dsl_power_state power_state)
    {
    	switch (power_state) {
    
    	case DSL_L0: return "l0";
    	case DSL_L1: return "l1";
    	case DSL_L2: return "l2";
    	case DSL_L3: return "l3";
    	case DSL_L4: return "l4";
    	default: return "unknown";
    
    	}
    };
    
    static void dsl_add_sequence_to_blob(const char *name, bool is_signed, size_t count,
    				const long *head, struct blob_buf *bb)
    {
    	void *array;
    	size_t i;
    
    	array = blobmsg_open_array(bb, name);
    	for (i = 0; i < count; i++) {
    		if (is_signed)
    
    			blobmsg_add_u32(bb, "", (uint32_t)head[i]);
    
    			blobmsg_add_u64(bb, "", (uint32_t)head[i]);
    
    	}
    	blobmsg_close_array(bb, array);
    }
    
    static void dsl_add_usds_to_blob(const char *name, bool is_signed, const long *head, struct blob_buf *bb)
    
    	void *table;
    	int i;
    
    	table = blobmsg_open_table(bb, name);
    	for (i = 0; i < 2; i++) {
    		if (is_signed)
    			blobmsg_add_u32(bb, i == 0 ? "us" : "ds", (uint32_t)head[i]);
    		else
    
    			blobmsg_add_u64(bb, i == 0 ? "us" : "ds", (uint32_t)head[i]);
    
    	}
    	blobmsg_close_table(bb, table);
    }
    
    static void dsl_add_int_to_blob(const char *name, long int value, struct blob_buf *bb)
    {
    	blobmsg_add_u32(bb, name, (uint32_t)value);
    
    static void dsl_status_line_to_blob(const struct dsl_line *line, struct blob_buf *bb)
    {
    	void *array;
    
    	unsigned long opt;
    
    	char str[64], *modes[ARRAY_SIZE(line->standard_used.xtse) * 8] = { NULL, };
    
    Yalu Zhang's avatar
    Yalu Zhang committed
    	enum itf_status if_status = line->status;
    
    	/*
    	 * Put most important information at the beginning
    	 */
    
    	if (if_status == IF_UP && line->link_status != LINK_UP) {
    		/* Some inconsistent status might be retrieved from the driver, i.e. interface status is
    		 * up and the link status is not up. In this case, we force reporting the interface's
    		 * status being down */
    		if_status = IF_DOWN;
    	}
    	blobmsg_add_string(bb, "status", dsl_if_status_str(if_status));
    
    	blobmsg_add_u8(bb, "upstream", line->upstream);
    
    	blobmsg_add_string(bb, "firmware_version", line->firmware_version);
    
    	blobmsg_add_string(bb, "link_status", dsl_link_status_str(line->link_status));
    
    	// standard_used
    	if (line->standard_used.use_xtse) {
    		array = blobmsg_open_array(bb, "xtse_used");
    		for (i = 0; i < ARRAY_SIZE(line->standard_used.xtse); i++) {
    			snprintf(str, sizeof(str), "%02x", line->standard_used.xtse[i]);
    			blobmsg_add_string(bb, "", str);
    		}
    		blobmsg_close_array(bb, array);
    
    
    		// For backward compatibility, provide the old format as well
    		for (i = T1_413; i <= G_993_2_JAPAN; i++) {
    			if (XTSE_BIT_GET(line->standard_used.xtse, i)) {
    				blobmsg_add_string(bb, "standard_used", dsl_xtse_str(i));
    				break;
    			}
    		}
    
    	} else {
    		for (opt = (unsigned long)MOD_G_922_1_ANNEX_A;
    			 opt <= (unsigned long)MOD_G_993_2_Annex_C;
    			 opt <<= 1) {
    			if (line->standard_used.mode & opt) {
    				blobmsg_add_string(bb, "standard_used", dsl_mod_str(opt));
    				break;
    			}
    		}
    	}
    
    	// current_profile
    	blobmsg_add_string(bb, "current_profile", dsl_profile_str(line->current_profile));
    	blobmsg_add_string(bb, "power_management_state", dsl_power_state_str(line->power_management_state));
    	// max_bit_rate
    	unsigned long rates[] = { line->max_bit_rate.us, line->max_bit_rate.ds };
    	dsl_add_usds_to_blob("max_bit_rate", false, rates, bb);
    
    	blobmsg_add_string(bb, "line_encoding", dsl_line_encoding_str(line->line_encoding));
    
    	// standards_supported
    	if (line->standard_supported.use_xtse) {
    		array = blobmsg_open_array(bb, "xtse");
    		for (i = 0; i < ARRAY_SIZE(line->standard_supported.xtse); i++) {
    			snprintf(str, sizeof(str), "%02x", line->standard_supported.xtse[i]);
    			blobmsg_add_string(bb, "", str);
    		}
    		blobmsg_close_array(bb, array);
    
    
    		// For backward compatibility, provide the old format as well
    		array = blobmsg_open_array(bb, "standards_supported");
    		for (i = T1_413, count = 0; i <= G_993_2_JAPAN; i++) {
    			if (XTSE_BIT_GET(line->standard_supported.xtse, i)) {
    				/* More than one XTSE bits can be mapped to the same old standard. So we need to filter
    				 * out the repeated ones. */
    				const char *mod = dsl_xtse_str(i);
    
    				for (j = 0; j < count; j++) {
    					if (strcmp(mod, modes[j]) == 0)
    						break;
    				}
    				if (j == count) {
    					// Not found
    
    					modes[count++] = (char *)mod;
    
    					blobmsg_add_string(bb, "", mod);
    				}
    			}
    		}
    		blobmsg_close_array(bb, array);
    
    	} else {
    		array = blobmsg_open_array(bb, "standards_supported");
    		for (opt = (unsigned long)MOD_G_922_1_ANNEX_A;
    			 opt <= (unsigned long)MOD_G_993_2_Annex_C;
    			 opt <<= 1) {
    			if (line->standard_supported.mode & opt)
    				blobmsg_add_string(bb, "", dsl_mod_str(opt));
    		}
    		blobmsg_close_array(bb, array);
    	}
    
    	// allowed_profiles
    
    	array = blobmsg_open_array(bb, "allowed_profiles");
    	for (opt = (unsigned long)VDSL2_8a; opt <= (unsigned long)VDSL2_35b; opt <<= 1) {
    		if (line->allowed_profiles & opt)
    			blobmsg_add_string(bb, "", dsl_profile_str(opt));
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	}
    
    	blobmsg_close_array(bb, array);
    
    	blobmsg_add_u32(bb, "success_failure_cause", line->success_failure_cause);
    
    	// upbokler_pb
    	dsl_add_sequence_to_blob("upbokler_pb", false, line->upbokler_pb.count,
    			(const long *)line->upbokler_pb.array, bb);
    
    	// rxthrsh_ds
    	dsl_add_sequence_to_blob("rxthrsh_ds", false, line->rxthrsh_ds.count,
    			(const long *)line->rxthrsh_ds.array, bb);
    
    	// act_ra_mode
    	unsigned long ra_modes[] = { line->act_ra_mode.us, line->act_ra_mode.ds };
    
    	dsl_add_usds_to_blob("act_ra_mode", false, ra_modes, bb);
    
    Yalu Zhang's avatar
    Yalu Zhang committed
    	blobmsg_add_u64(bb, "snr_mroc_us", line->snr_mroc.us);
    
    
    	// last_state_transmitted
    	unsigned long lst[] = { line->last_state_transmitted.us, line->last_state_transmitted.ds };
    
    	dsl_add_usds_to_blob("last_state_transmitted", false, lst, bb);
    
    	blobmsg_add_u64(bb, "us0_mask", line->us0_mask);
    
    
    	// trellis
    	long trellis[] = { line->trellis.us, line->trellis.ds };
    
    	dsl_add_usds_to_blob("trellis", true, trellis, bb);
    
    
    	// act_snr_mode
    	unsigned long snr_modes[] = { line->act_snr_mode.us, line->act_snr_mode.ds };
    
    	dsl_add_usds_to_blob("act_snr_mode", false, snr_modes, bb);
    
    	dsl_add_int_to_blob("line_number", line->line_number, bb);
    
    
    	// noise_margin
    	long margins[] = { line->noise_margin.us, line->noise_margin.ds };
    
    	dsl_add_usds_to_blob("noise_margin", true, margins, bb);
    
    
    	// snr_mpb_us
    	dsl_add_sequence_to_blob("snr_mpb_us", true, line->snr_mpb_us.count,
    			(const long *)line->snr_mpb_us.array, bb);
    
    	// snr_mpb_ds
    	dsl_add_sequence_to_blob("snr_mpb_ds", true, line->snr_mpb_ds.count,
    			(const long *)line->snr_mpb_ds.array, bb);
    
    	// attenuation
    	long attenuations[] = { line->attenuation.us, line->attenuation.ds };
    
    	dsl_add_usds_to_blob("attenuation", true, attenuations, bb);
    
    
    	// power
    	long powers[] = { line->power.us, line->power.ds };
    
    	dsl_add_usds_to_blob("power", true, powers, bb);
    
    
    	blobmsg_add_string(bb, "xtur_vendor", line->xtur_vendor);
    	blobmsg_add_string(bb, "xtur_country", line->xtur_country);
    	blobmsg_add_u64(bb, "xtur_ansi_std", line->xtur_ansi_std);
    	blobmsg_add_u64(bb, "xtur_ansi_rev", line->xtur_ansi_rev);
    
    	blobmsg_add_string(bb, "xtuc_vendor", line->xtuc_vendor);
    	blobmsg_add_string(bb, "xtuc_country", line->xtuc_country);
    	blobmsg_add_u64(bb, "xtuc_ansi_std", line->xtuc_ansi_std);
    	blobmsg_add_u64(bb, "xtuc_ansi_rev", line->xtuc_ansi_rev);
    }
    
    
    static const char *dsl_link_encap_str(enum dsl_link_encapsulation encap)
    {
    	switch (encap) {
    
    	case G_992_3_ANNEK_K_ATM: return "adsl2_atm";
    	case G_992_3_ANNEK_K_PTM: return "adsl2_ptm";
    	case G_993_2_ANNEK_K_ATM: return "vdsl2_atm";
    	case G_993_2_ANNEK_K_PTM: return "vdsl2_ptm";
    
    	case G_994_1_AUTO: return "auto";
    	default: return "unknown";
    
    static void dsl_status_channel_to_blob(const struct dsl_channel *channel, struct blob_buf *bb)
    {
    
    	void *array;
    	unsigned long opt;
    
    
    	blobmsg_add_string(bb, "status", dsl_if_status_str(channel->status));
    
    	blobmsg_add_string(bb, "link_encapsulation_used", dsl_link_encap_str(channel->link_encapsulation_used));
    
    	// curr_rate
    	unsigned long curr_rates[] = { channel->curr_rate.us, channel->curr_rate.ds };
    	dsl_add_usds_to_blob("curr_rate", false, curr_rates, bb);
    
    	// actndr
    	unsigned long act_rates[] = { channel->actndr.us, channel->actndr.ds };
    	dsl_add_usds_to_blob("actndr", false, act_rates, bb);
    
    	// link_encapsulation_supported
    
    	array = blobmsg_open_array(bb, "link_encapsulation_supported");
    	for (opt = (unsigned long)G_992_3_ANNEK_K_ATM; opt <= (unsigned long)G_994_1_AUTO; opt <<= 1) {
    		if (channel->link_encapsulation_supported & opt)
    			blobmsg_add_string(bb, "", dsl_link_encap_str(opt));
    	}
    	blobmsg_close_array(bb, array);
    
    	blobmsg_add_u64(bb, "lpath", channel->lpath);
    	blobmsg_add_u64(bb, "intlvdepth", channel->intlvdepth);
    	dsl_add_int_to_blob("intlvblock", channel->intlvblock, bb);
    	blobmsg_add_u64(bb, "actual_interleaving_delay", channel->actual_interleaving_delay);
    	dsl_add_int_to_blob("actinp", channel->actinp, bb);
    
    	blobmsg_add_u8(bb, "inpreport", channel->inpreport);
    
    	dsl_add_int_to_blob("nfec", channel->nfec, bb);
    	dsl_add_int_to_blob("rfec", channel->rfec, bb);
    	dsl_add_int_to_blob("lsymb", channel->lsymb, bb);
    
    	// actinprein
    	unsigned long act_inps[] = { channel->actinprein.us, channel->actinprein.ds };
    
    	dsl_add_usds_to_blob("actinprein", false, act_inps, bb);
    
    int dsl_status_all(struct ubus_context *ctx, struct ubus_object *obj,
    
    		struct ubus_request_data *req, const char *method, struct blob_attr *msg)
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    {
    
    	static struct blob_buf bb;
    	struct dsl_line line;
    	struct dsl_channel channel;
    
    	int retval = UBUS_STATUS_OK;
    
    	int i, max_line;
    	void *array_line, *array_chan, *table_line, *table_chan;
    
    	// Initialize the buffer
    
    	memset(&bb, 0, sizeof(bb));
    
    	array_line = blobmsg_open_array(&bb, DSL_OBJECT_LINE);
    	for (i = 0, max_line = dsl_get_line_number(); i < max_line; i++) {
    		if (xdsl_ops.get_line_info == NULL || (*xdsl_ops.get_line_info)(i, &line) != 0 ||
    			xdsl_ops.get_channel_info == NULL || (*xdsl_ops.get_channel_info)(i, &channel) != 0) {
    
    			retval = UBUS_STATUS_UNKNOWN_ERROR;
    			goto __ret;
    
    		// Line table
    		table_line = blobmsg_open_table(&bb, "");
    
    		// Line parameters
    
    		blobmsg_add_u32(&bb, "id", (unsigned int)i + 1);
    
    		dsl_status_line_to_blob(&line, &bb);
    
    		// Embed channel(s) inside a line in the format channel: [{},{}...]
    		array_chan = blobmsg_open_array(&bb, DSL_OBJECT_CHANNEL);
    		table_chan = blobmsg_open_table(&bb, "");
    		// Channel parameters
    
    		blobmsg_add_u32(&bb, "id", 1);
    
    		dsl_status_channel_to_blob(&channel, &bb);
    
    		blobmsg_close_table(&bb, table_chan);
    		blobmsg_close_array(&bb, array_chan);
    
    		blobmsg_close_table(&bb, table_line);
    
    	blobmsg_close_array(&bb, array_line);
    
    	// Send the reply
    	ubus_send_reply(ctx, req, bb.head);
    
    __ret:
    	blob_buf_free(&bb);
    	return retval;
    
    static struct value2text dsl_stats_types[] = {
    	{ DSL_STATS_TOTAL, "total" },
    	{ DSL_STATS_SHOWTIME, "showtime" },
    	{ DSL_STATS_LASTSHOWTIME, "lastshowtime" },
    	{ DSL_STATS_CURRENTDAY, "currentday" },
    	{ DSL_STATS_QUARTERHOUR, "quarterhour" }
    };
    
    Jani Juvan's avatar
    Jani Juvan committed
    static void dsl_if_stats_to_blob(const struct if_stats *stats, struct blob_buf *bb)
    {
    	blobmsg_add_u64(bb, "bytes_sent", stats->tx_bytes);
    	blobmsg_add_u64(bb, "bytes_received", stats->rx_bytes);
    	blobmsg_add_u64(bb, "packets_sent", stats->tx_packets);
    	blobmsg_add_u64(bb, "packets_received", stats->rx_packets);
    	blobmsg_add_u64(bb, "errors_sent", stats->tx_errors);
    	blobmsg_add_u64(bb, "errors_received", stats->rx_errors);
    	blobmsg_add_u64(bb, "discard_packets_sent", stats->tx_dropped);
    	blobmsg_add_u64(bb, "discard_packets_received", stats->rx_dropped);
    }
    
    
    static void dsl_stats_to_blob(const struct dsl_line_channel_stats *stats, struct blob_buf *bb)
    {
    	blobmsg_add_u64(bb, "total_start", stats->total_start);
    	blobmsg_add_u64(bb, "showtime_start", stats->showtime_start);
    	blobmsg_add_u64(bb, "last_showtime_start", stats->last_showtime_start);
    	blobmsg_add_u64(bb, "current_day_start", stats->current_day_start);
    	blobmsg_add_u64(bb, "quarter_hour_start", stats->quarter_hour_start);
    }
    
    static void dsl_stats_line_interval_to_blob(const struct dsl_line_stats_interval *stats, struct blob_buf *bb)
    {
    	blobmsg_add_u64(bb, "errored_secs", stats->errored_secs);
    	blobmsg_add_u64(bb, "severely_errored_secs", stats->severely_errored_secs);
    }
    
    static void dsl_stats_channel_interval_to_blob(const struct dsl_channel_stats_interval *stats, struct blob_buf *bb)
    {
    	blobmsg_add_u64(bb, "xtur_fec_errors", stats->xtur_fec_errors);
    	blobmsg_add_u64(bb, "xtuc_fec_errors", stats->xtuc_fec_errors);
    	blobmsg_add_u64(bb, "xtur_hec_errors", stats->xtur_hec_errors);
    	blobmsg_add_u64(bb, "xtuc_hec_errors", stats->xtuc_hec_errors);
    	blobmsg_add_u64(bb, "xtur_crc_errors", stats->xtur_crc_errors);
    	blobmsg_add_u64(bb, "xtuc_crc_errors", stats->xtuc_crc_errors);
    
    Jani Juvan's avatar
    Jani Juvan committed
    static int uci_get_ifname(char *ifname, size_t len, bool is_fast);
    
    
    int dsl_stats_all(struct ubus_context *ctx, struct ubus_object *obj,
    
    		struct ubus_request_data *req, const char *method, struct blob_attr *msg)
    {
    	int retval = UBUS_STATUS_OK;
    	static struct blob_buf bb;
    	enum dsl_stats_type type;
    
    Jani Juvan's avatar
    Jani Juvan committed
    	char ifname[IFNAMSIZ];
    
    	struct if_stats if_stats = {0};
    
    	struct dsl_line_channel_stats stats;
    	struct dsl_line_stats_interval line_stats_interval;
    	struct dsl_channel_stats_interval channel_stats_interval;
    	int i, j, max_line;
    	void *array_line, *array_chan, *table_line, *table_interval, *table_chan;
    
    	// Initialize the buffer
    	memset(&bb, 0, sizeof(bb));
    	blob_buf_init(&bb, 0);
    
    	array_line = blobmsg_open_array(&bb, DSL_OBJECT_LINE);
    	for (i = 0, max_line = dsl_get_line_number(); i < max_line; i++) {
    		if (xdsl_ops.get_line_stats == NULL || (*xdsl_ops.get_line_stats)(i, &stats) != 0) {
    			retval = UBUS_STATUS_UNKNOWN_ERROR;
    			goto __ret;
    		}
    
    		// Line table
    		table_line = blobmsg_open_table(&bb, "");
    
    		// Line statistics
    
    		blobmsg_add_u32(&bb, "id", (unsigned int)i + 1);
    
    		if (uci_get_ifname(ifname, sizeof(ifname), false) == 0)
    		    if_getstats(ifname, &if_stats);
    		dsl_if_stats_to_blob(&if_stats, &bb);
    
    		dsl_stats_to_blob(&stats, &bb);
    
    		// Line interval statistics
    		for (j = 0; j < ARRAY_SIZE(dsl_stats_types); j++) {
    			table_interval = blobmsg_open_table(&bb, dsl_stats_types[j].text);
    
    			if (xdsl_ops.get_line_stats_interval == NULL || (*xdsl_ops.get_line_stats_interval)
    				(i, dsl_stats_types[j].value, &line_stats_interval) != 0) {
    				retval = UBUS_STATUS_UNKNOWN_ERROR;
    				goto __ret;
    			}
    			dsl_stats_line_interval_to_blob(&line_stats_interval, &bb);
    
    			blobmsg_close_table(&bb, table_interval);
    		}
    
    		// Embed channel(s) inside a line in the format channel: [{},{}...]
    		array_chan = blobmsg_open_array(&bb, DSL_OBJECT_CHANNEL);
    		table_chan = blobmsg_open_table(&bb, "");
    
    		// Channel statistics
    
    		blobmsg_add_u32(&bb, "id", 1);
    
    		if (xdsl_ops.get_channel_stats == NULL || (*xdsl_ops.get_channel_stats)(i, &stats) != 0) {
    			retval = UBUS_STATUS_UNKNOWN_ERROR;
    			goto __ret;
    		}
    
    		if (uci_get_ifname(ifname, sizeof(ifname), false) == 0)
    		    if_getstats(ifname, &if_stats);
    		dsl_if_stats_to_blob(&if_stats, &bb);
    
    		dsl_stats_to_blob(&stats, &bb);
    
    		// Line interval statistics
    		for (j = 0; j < ARRAY_SIZE(dsl_stats_types); j++) {
    			table_interval = blobmsg_open_table(&bb, dsl_stats_types[j].text);
    
    			if (xdsl_ops.get_channel_stats_interval == NULL || (*xdsl_ops.get_channel_stats_interval)
    				(i, dsl_stats_types[j].value, &channel_stats_interval) != 0) {
    				retval = UBUS_STATUS_UNKNOWN_ERROR;
    				goto __ret;
    			}
    			dsl_stats_channel_interval_to_blob(&channel_stats_interval, &bb);
    
    			blobmsg_close_table(&bb, table_interval);
    		}
    
    		// Close the tables and arrays for the channel
    		blobmsg_close_table(&bb, table_chan);
    		blobmsg_close_array(&bb, array_chan);
    
    		// Close the table for one line
    		blobmsg_close_table(&bb, table_line);
    	}
    	blobmsg_close_array(&bb, array_line);
    
    	// Send the reply
    	ubus_send_reply(ctx, req, bb.head);
    
    __ret:
    	blob_buf_free(&bb);
    
    	return retval;
    }
    
    
    static struct ubus_method dsl_methods[] = {
    
    	{ .name = "status", .handler = dsl_status_all },
    	{ .name = "stats", .handler = dsl_stats_all }
    };
    
    static struct ubus_object_type dsl_type = UBUS_OBJECT_TYPE("dsl", dsl_methods);
    static struct ubus_object dsl_object = {
    
    	.name = "dsl",
    
    	.type = &dsl_type,
    	.methods = dsl_methods,
    	.n_methods = ARRAY_SIZE(dsl_methods),
    
    int dsl_line_status(struct ubus_context *ctx, struct ubus_object *obj,
    
    		struct ubus_request_data *req, const char *method, struct blob_attr *msg)
    {
    	static struct blob_buf bb;
    	struct dsl_line line;
    	int retval = UBUS_STATUS_OK;
    	int num = -1;
    
    	// Initialize the buffer
    	memset(&bb, 0, sizeof(bb));
    	blob_buf_init(&bb, 0);
    
    	// Get line status
    	sscanf(obj->name, "dsl.line.%d", &num);
    
    	num--;  // indexes start from 1 in ubus object
    
    
    	if (xdsl_ops.get_line_info == NULL || (*xdsl_ops.get_line_info)(num, &line) != 0) {
    		retval = UBUS_STATUS_UNKNOWN_ERROR;
    		goto __ret;
    	}
    	dsl_status_line_to_blob(&line, &bb);
    
    	// Send the reply
    	ubus_send_reply(ctx, req, bb.head);
    
    __ret:
    	blob_buf_free(&bb);
    	return retval;
    }
    
    
    static int validate_interval_type(struct blob_attr *msg, enum dsl_stats_type *type)
    
    {
    	struct blob_attr *tb[__DSL_STATS_MAX];
    
    
    	blobmsg_parse(dsl_stats_policy, __DSL_STATS_MAX, tb, blob_data(msg), blob_len(msg));
    	if (tb[DSL_STATS_INTERVAL]) {
    		const char *st = blobmsg_data(tb[DSL_STATS_INTERVAL]);
    
    		for (i = 0; i < ARRAY_SIZE(dsl_stats_types); i++) {
    			if (strcasecmp(st, dsl_stats_types[i].text) == 0) {
    
    				*type = dsl_stats_types[i].value;
    
    				break;
    			}
    		}
    
    		if (i >= ARRAY_SIZE(dsl_stats_types)) {
    			DSLMNGR_LOG(LOG_ERR, "Wrong argument for interval statistics type\n");
    			return UBUS_STATUS_INVALID_ARGUMENT;
    		}
    	}
    
    	return UBUS_STATUS_OK;
    }
    
    int dsl_line_stats(struct ubus_context *ctx, struct ubus_object *obj,
    		struct ubus_request_data *req, const char *method, struct blob_attr *msg)
    {
    	static struct blob_buf bb;
    	enum dsl_stats_type type = DSL_STATS_QUARTERHOUR + 1;
    	char ifname[IFNAMSIZ];
    	struct if_stats if_stats = {0};
    	struct dsl_line_channel_stats stats;
    	struct dsl_line_stats_interval line_stats_interval;
    	int retval = UBUS_STATUS_OK;
    	int num = -1;
    	int i;
    	void *table;
    
    	// Parse and validation check the interval type if any
    	if ((retval = validate_interval_type(msg, &type)) != UBUS_STATUS_OK) {
    		return retval;
    	}
    
    
    	// Initialize the buffer
    	memset(&bb, 0, sizeof(bb));
    	blob_buf_init(&bb, 0);
    
    	// Get line number
    	sscanf(obj->name, "dsl.line.%d", &num);
    
    	num--;  // indexes start from 1 in ubus object
    
    
    	// Get line interval statistics
    	if (type >= DSL_STATS_TOTAL && type <= DSL_STATS_QUARTERHOUR) {
    		if (xdsl_ops.get_line_stats_interval == NULL || (*xdsl_ops.get_line_stats_interval)
    			(num, type, &line_stats_interval) != 0) {
    			retval = UBUS_STATUS_UNKNOWN_ERROR;
    			goto __ret;
    		}
    		dsl_stats_line_interval_to_blob(&line_stats_interval, &bb);
    	} else {
    
    Jani Juvan's avatar
    Jani Juvan committed
    		// Get interface counters
    
    		if (uci_get_ifname(ifname, sizeof(ifname), false) == 0)
    			if_getstats(ifname, &if_stats);
    		dsl_if_stats_to_blob(&if_stats, &bb);
    
    		// Get line statistics
    		if (xdsl_ops.get_line_stats == NULL || (*xdsl_ops.get_line_stats)(num, &stats) != 0) {
    			retval = UBUS_STATUS_UNKNOWN_ERROR;
    			goto __ret;
    		}
    		dsl_stats_to_blob(&stats, &bb);
    
    		// Get all interval statistics
    
    		for (i = 0; i < ARRAY_SIZE(dsl_stats_types); i++) {
    			table = blobmsg_open_table(&bb, dsl_stats_types[i].text);
    
    
    			if (xdsl_ops.get_line_stats_interval == NULL || (*xdsl_ops.get_line_stats_interval)
    
    				(num, dsl_stats_types[i].value, &line_stats_interval) != 0) {
    
    				retval = UBUS_STATUS_UNKNOWN_ERROR;
    				goto __ret;
    			}
    			dsl_stats_line_interval_to_blob(&line_stats_interval, &bb);
    
    			blobmsg_close_table(&bb, table);
    		}
    	}
    
    	// Send the reply
    	ubus_send_reply(ctx, req, bb.head);
    
    __ret:
    	blob_buf_free(&bb);
    	return retval;
    }
    
    static struct ubus_method dsl_line_methods[] = {
    	{ .name = "status", .handler = dsl_line_status },
    
    	UBUS_METHOD("stats", dsl_line_stats, dsl_stats_policy),
    	UBUS_METHOD("configure", dsl_line_configure,  dsl_configure_policy)
    
    static struct ubus_object_type dsl_line_type = UBUS_OBJECT_TYPE("dsl.line", dsl_line_methods);
    
    int dsl_channel_status(struct ubus_context *ctx, struct ubus_object *obj,
    
    		struct ubus_request_data *req, const char *method, struct blob_attr *msg)
    {
    	static struct blob_buf bb;
    	struct dsl_channel channel;
    	int retval = UBUS_STATUS_OK;
    	int num = -1;
    
    	// Initialize the buffer
    	memset(&bb, 0, sizeof(bb));
    	blob_buf_init(&bb, 0);
    
    	// Get channel status
    	sscanf(obj->name, "dsl.channel.%d", &num);
    
    	num--;  // indexes start from 1 in ubus object
    
    
    	if (xdsl_ops.get_channel_info == NULL || (*xdsl_ops.get_channel_info)(num, &channel) != 0) {
    		retval = UBUS_STATUS_UNKNOWN_ERROR;
    		goto __ret;
    	}
    	dsl_status_channel_to_blob(&channel, &bb);
    
    	// Send the reply
    	ubus_send_reply(ctx, req, bb.head);
    
    __ret:
    	blob_buf_free(&bb);
    	return retval;
    }
    
    
    int dsl_channel_stats(struct ubus_context *ctx, struct ubus_object *obj,
    
    		struct ubus_request_data *req, const char *method, struct blob_attr *msg)
    {
    	static struct blob_buf bb;
    	enum dsl_stats_type type = DSL_STATS_QUARTERHOUR + 1;
    
    Jani Juvan's avatar
    Jani Juvan committed
    	char ifname[IFNAMSIZ];
    
    	struct if_stats if_stats = {0};
    
    	struct dsl_line_channel_stats stats;
    	struct dsl_channel_stats_interval channel_stats_interval;
    	int retval = UBUS_STATUS_OK;
    	int num = -1;
    
    	void *table;
    
    	// Parse and validation check the interval type if any
    
    	if ((retval = validate_interval_type(msg, &type)) != UBUS_STATUS_OK) {
    		return retval;
    
    	}
    
    	// Initialize the buffer
    	memset(&bb, 0, sizeof(bb));
    	blob_buf_init(&bb, 0);
    
    	// Get channel number
    	sscanf(obj->name, "dsl.channel.%d", &num);
    
    	num--;  // indexes start from 1 in ubus object
    
    
    	// Get channel interval statistics
    	if (type >= DSL_STATS_TOTAL && type <= DSL_STATS_QUARTERHOUR) {
    		if (xdsl_ops.get_channel_stats_interval == NULL || (*xdsl_ops.get_channel_stats_interval)
    			(num, type, &channel_stats_interval) != 0) {
    			retval = UBUS_STATUS_UNKNOWN_ERROR;
    			goto __ret;
    		}
    		dsl_stats_channel_interval_to_blob(&channel_stats_interval, &bb);
    	} else {
    
    Jani Juvan's avatar
    Jani Juvan committed
    		// Get interface counters
    
    		if (uci_get_ifname(ifname, sizeof(ifname), false) == 0)
    			if_getstats(ifname, &if_stats);
    		dsl_if_stats_to_blob(&if_stats, &bb);
    
    		// Get channel statistics
    		if (xdsl_ops.get_channel_stats == NULL || (*xdsl_ops.get_channel_stats)(num, &stats) != 0) {
    			retval = UBUS_STATUS_UNKNOWN_ERROR;
    			goto __ret;
    		}
    		dsl_stats_to_blob(&stats, &bb);
    
    		// Get all interval statistics
    
    		for (i = 0; i < ARRAY_SIZE(dsl_stats_types); i++) {
    			table = blobmsg_open_table(&bb, dsl_stats_types[i].text);
    
    
    			if (xdsl_ops.get_channel_stats_interval == NULL || (*xdsl_ops.get_channel_stats_interval)
    
    				(num, dsl_stats_types[i].value, &channel_stats_interval) != 0) {
    
    				retval = UBUS_STATUS_UNKNOWN_ERROR;
    				goto __ret;
    			}
    			dsl_stats_channel_interval_to_blob(&channel_stats_interval, &bb);
    
    			blobmsg_close_table(&bb, table);
    		}
    	}
    
    	// Send the reply
    	ubus_send_reply(ctx, req, bb.head);
    
    __ret:
    	blob_buf_free(&bb);
    	return retval;
    }
    
    static struct ubus_method dsl_channel_methods[] = {
    	{ .name = "status", .handler = dsl_channel_status },
    	UBUS_METHOD("stats", dsl_channel_stats, dsl_stats_policy )
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    };
    
    
    static struct ubus_object_type dsl_channel_type = UBUS_OBJECT_TYPE("dsl.channel", dsl_channel_methods);
    
    
    static void fast_line_status_to_blob(const struct fast_line *line, struct blob_buf *bb)
    {
    	void *array;
    	int i, j, count;
    	unsigned long opt;
    	char str[64];
    	enum itf_status if_status = line->status;
    
    	/*
    	 * Put most important information at the beginning
    	 */
    	if (if_status == IF_UP && line->link_status != LINK_UP) {
    		/* Some inconsistent status might be retrieved from the driver, i.e. interface status is
    		 * up and the link status is not up. In this case, we force reporting the interface's
    		 * status being down */
    		if_status = IF_DOWN;
    	}
    	blobmsg_add_string(bb, "status", dsl_if_status_str(if_status));
    	blobmsg_add_u8(bb, "upstream", line->upstream);
    	blobmsg_add_string(bb, "firmware_version", line->firmware_version);
    	blobmsg_add_string(bb, "link_status", dsl_link_status_str(line->link_status));
    	blobmsg_add_string(bb, "current_profile", fast_profile_str(line->current_profile));
    	blobmsg_add_string(bb, "power_management_state", dsl_power_state_str(line->power_management_state));
    
    	// max_bit_rate
    	unsigned long rates[] = { line->max_bit_rate.us, line->max_bit_rate.ds };
    	dsl_add_usds_to_blob("max_bit_rate", false, rates, bb);
    
    	// allowed_profiles
    	array = blobmsg_open_array(bb, "allowed_profiles");
    
    Jani Juvan's avatar
    Jani Juvan committed
    	for (opt = (unsigned long)FAST_106a; opt <= (unsigned long)FAST_212c; opt <<= 1) {
    
    		if (line->allowed_profiles & opt)
    			blobmsg_add_string(bb, "", fast_profile_str(opt));
    	}
    	blobmsg_close_array(bb, array);
    
    	blobmsg_add_u32(bb, "success_failure_cause", line->success_failure_cause);
    	blobmsg_add_u32(bb, "upbokler", line->upbokler);
    
    	// last_transmitted_signal
    	long signals[] = { line->last_transmitted_signal.us, line->last_transmitted_signal.ds };