Skip to content
Snippets Groups Projects
dslmngr.c 64.3 KiB
Newer Older
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 };
	dsl_add_usds_to_blob("last_transmitted_signal", true, signals, bb);


	blobmsg_add_u32(bb, "upbokle", line->upbokle);
	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);

	// 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);

	// snrm_rmc
	long rmc[] = { line->snrm_rmc.us, line->snrm_rmc.ds };
	dsl_add_usds_to_blob("snrm_rmc", true, rmc, bb);

	// bits_rmc_ps
Jani Juvan's avatar
Jani Juvan committed
	dsl_add_sequence_to_blob("bits_rmc_ps_ds", false, line->bits_rmc_ps_ds.count,
				 (const long *)line->bits_rmc_ps_ds.elements, bb);
	dsl_add_sequence_to_blob("bits_rmc_ps_us", false, line->bits_rmc_ps_us.count,
				 (const long *)line->bits_rmc_ps_us.elements, bb);

	// fext_to_cancel_enable
	void *table = blobmsg_open_table(bb, "fext_to_cancel_enable");
	blobmsg_add_u8(bb, "us", line->fext_to_cancel_enable.us);
	blobmsg_add_u8(bb, "ds", line->fext_to_cancel_enable.ds);
	blobmsg_close_table(bb, table);

	// etr
	unsigned long etr[] = { line->etr.us, line->etr.ds };
	dsl_add_usds_to_blob("etr", false, etr, bb);

	// att_etr
	unsigned long att_etr[] = { line->att_etr.us, line->att_etr.ds };
	dsl_add_usds_to_blob("att_etr", false, att_etr, bb);

	// min_eftr
	unsigned long min_eftr[] = { line->min_eftr.us, line->min_eftr.ds };
	dsl_add_usds_to_blob("min_eftr", false, min_eftr, bb);
}

Jani Juvan's avatar
Jani Juvan committed
int fast_status_all(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 fast_line line;
	int retval = UBUS_STATUS_OK;
Jani Juvan's avatar
Jani Juvan committed
	int i, max_line;
	void *array_line, *table_line;

	// Initialize the buffer
	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);

Jani Juvan's avatar
Jani Juvan committed
	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_fast_line_info == NULL || (*xdsl_ops.get_fast_line_info)(i, &line) != 0) {
			retval = UBUS_STATUS_UNKNOWN_ERROR;
			goto __ret;
		}
Jani Juvan's avatar
Jani Juvan committed
		// Line table
		table_line = blobmsg_open_table(&bb, "");

		// Line parameters
		blobmsg_add_u32(&bb, "id", (unsigned int)i + 1);
		fast_line_status_to_blob(&line, &bb);

		blobmsg_close_table(&bb, table_line);
Jani Juvan's avatar
Jani Juvan committed
	blobmsg_close_array(&bb, array_line);

	// Send the reply
	ubus_send_reply(ctx, req, bb.head);

__ret:
	blob_buf_free(&bb);
	return retval;
}

static void fast_line_stats_interval_to_blob(const struct fast_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);
	blobmsg_add_u64(bb, "loss", stats->loss);
	blobmsg_add_u64(bb, "lors", stats->lors);
	blobmsg_add_u64(bb, "uas", stats->uas);
	blobmsg_add_u64(bb, "rtx_uc", stats->rtx_uc);
	blobmsg_add_u64(bb, "rtx_tx", stats->rtx_tx);
	blobmsg_add_u64(bb, "success_bsw", stats->success_bsw);
	blobmsg_add_u64(bb, "success_sra", stats->success_sra);
	blobmsg_add_u64(bb, "success_fra", stats->success_fra);
	blobmsg_add_u64(bb, "success_rpa", stats->success_rpa);
	blobmsg_add_u64(bb, "success_tiga", stats->success_tiga);
}

Jani Juvan's avatar
Jani Juvan committed
int fast_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
	struct if_stats if_stats = {0};
	struct fast_line_stats stats;
Jani Juvan's avatar
Jani Juvan committed
	struct fast_line_stats_interval line_stats_interval;
	int i, j, max_line;
	void *array_line, *table_line, *table_interval;
Jani Juvan's avatar
Jani Juvan committed
	char ifname[IFNAMSIZ];
Jani Juvan's avatar
Jani Juvan committed

	// 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_fast_line_stats == NULL || (*xdsl_ops.get_fast_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), true) == 0)
			if_getstats(ifname, &if_stats);
		dsl_if_stats_to_blob(&if_stats, &bb);
Jani Juvan's avatar
Jani Juvan committed
		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_fast_line_stats_interval == NULL || (*xdsl_ops.get_fast_line_stats_interval)
				(i, dsl_stats_types[j].value, &line_stats_interval) != 0) {
				retval = UBUS_STATUS_UNKNOWN_ERROR;
				goto __ret;
			}
			fast_line_stats_interval_to_blob(&line_stats_interval, &bb);

			blobmsg_close_table(&bb, table_interval);
		}

		// 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 fast_methods[] = {
	{ .name = "status", .handler = fast_status_all },
	{ .name = "stats", .handler = fast_stats_all }
};
static struct ubus_object_type fast_type = UBUS_OBJECT_TYPE("fast", fast_methods);
static struct ubus_object fast_object = {
	.name = "fast",
	.type = &fast_type,
	.methods = fast_methods,
	.n_methods = ARRAY_SIZE(fast_methods),
};

int fast_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 fast_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, "fast.line.%d", &num);
	num--;  // indexes start from 1 in ubus object

	if (xdsl_ops.get_fast_line_info == NULL || (*xdsl_ops.get_fast_line_info)(num, &line) != 0) {
		retval = UBUS_STATUS_UNKNOWN_ERROR;
		goto __ret;
	}
	fast_line_status_to_blob(&line, &bb);

	// Send the reply
	ubus_send_reply(ctx, req, bb.head);

__ret:
	blob_buf_free(&bb);
	return retval;
}

int fast_line_stats_handler(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
	struct if_stats if_stats = {0};
	struct fast_line_stats stats;
	struct fast_line_stats_interval stats_interval;
	int retval = UBUS_STATUS_OK;
	int num = -1;
Jani Juvan's avatar
Jani Juvan committed
	char ifname[IFNAMSIZ];

	// 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, "fast.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_fast_line_stats_interval == NULL || (*xdsl_ops.get_fast_line_stats_interval)
			(num, type, &stats_interval) != 0) {
			retval = UBUS_STATUS_UNKNOWN_ERROR;
			goto __ret;
		}
		fast_line_stats_interval_to_blob(&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_fast_line_stats == NULL || (*xdsl_ops.get_fast_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_fast_line_stats_interval == NULL || (*xdsl_ops.get_fast_line_stats_interval)
				(num, dsl_stats_types[i].value, &stats_interval) != 0) {
				retval = UBUS_STATUS_UNKNOWN_ERROR;
				goto __ret;
			}
			fast_line_stats_interval_to_blob(&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 fast_line_methods[] = {
	{ .name = "status", .handler = fast_line_status },
	UBUS_METHOD("stats", fast_line_stats_handler, dsl_stats_policy )
};

static struct ubus_object_type fast_line_type = UBUS_OBJECT_TYPE("fast.line", fast_line_methods);

static const char *atm_link_type_str(enum atm_link_type type)
{
	const char *str = dsl_get_string_value(atm_link_types, type);

	if (!str)
		return "unknown";

	return str;
}

static const char *atm_encap_str(enum atm_encapsulation encap)
{
	const char *str = dsl_get_string_value(atm_encapsulations, encap);

	if (!str)
		return "unknown";

	return str;
}

static const char *atm_aal_str(enum atm_aal aal)
{
	switch (aal) {
	case ATM_AAL1: return "aal1";
	case ATM_AAL2: return "aal2";
	case ATM_AAL3: return "aal3";
	case ATM_AAL4: return "aal4";
	case ATM_AAL5: return "aal5";
	default: return "unknown";
	}
}

static const char *atm_qos_class_str(enum atm_qos_class class)
{
	const char *str = dsl_get_string_value(atm_qos_classes, class);

	if (!str)
		return "unknown";

	return str;
}

static void atm_add_dest_addr_to_blob(const struct atm_dest_addr *dest_addr, struct blob_buf *bb)
{
	void *table = blobmsg_open_table(bb, "dest_addr");
	blobmsg_add_u32(bb, "vpi", dest_addr->vpi);
	blobmsg_add_u32(bb, "vci", dest_addr->vci);
	blobmsg_close_table(bb, table);
}

static void atm_link_status_to_blob(const struct atm_link *link, const struct atm_link_qos *qos, struct blob_buf *bb)
{
	void *array, *table;
	int i;

	blobmsg_add_string(bb, "status", dsl_if_status_str(link->status));
	blobmsg_add_string(bb, "link_type", atm_link_type_str(link->link_type));
	blobmsg_add_u8(bb, "auto_config", link->auto_config);
	atm_add_dest_addr_to_blob(&link->dest_addr, bb);
	blobmsg_add_string(bb, "encapsulation", atm_encap_str(link->encapsulation));
	blobmsg_add_u8(bb, "fcs_preserved", link->fcs_preserved);

	array = blobmsg_open_array(bb, "vc_search_list");
	for (i = 0; i < link->vc_list_count; i++) {
		atm_add_dest_addr_to_blob(&link->vc_search_list[i], bb);
	}
	blobmsg_close_array(bb, array);

	blobmsg_add_string(bb, "aal", atm_aal_str(link->aal));

	table = blobmsg_open_table(bb, "qos");
	blobmsg_add_string(bb, "qos_class", atm_qos_class_str(qos->qos_class));
	blobmsg_add_u32(bb, "peak_cell_rate", qos->peak_cell_rate);
	blobmsg_add_u32(bb, "max_burst_size", qos->max_burst_size);
	blobmsg_add_u32(bb, "sustainable_cell_rate", qos->sustainable_cell_rate);
	blobmsg_close_table(bb, table);
}

int atm_link_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 atm_link link;
	struct atm_link_qos qos;
	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, "atm.link.%d", &num);
	num--;  // indexes start from 1 in ubus object

	if (atm_funcs.get_link_info == NULL || (*atm_funcs.get_link_info)(num, &link, &qos) != 0) {
		retval = UBUS_STATUS_UNKNOWN_ERROR;
		goto __ret;
	}
	atm_link_status_to_blob(&link, &qos, &bb);

	// Send the reply
	ubus_send_reply(ctx, req, bb.head);

__ret:
	blob_buf_free(&bb);
	return retval;
}

static void atm_link_stats_to_blob(const struct atm_link_stats *stats, struct blob_buf *bb)
{
	blobmsg_add_u64(bb, "transmitted_blocks", stats->transmitted_blocks);
	blobmsg_add_u64(bb, "received_blocks", stats->received_blocks);
	blobmsg_add_u64(bb, "crc_errors", stats->crc_errors);
}

int atm_link_stats_handler(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 atm_link_stats stats;
	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, "atm.link.%d", &num);
	num--;  // indexes start from 1 in ubus object

	if (atm_funcs.get_link_stats == NULL || (*atm_funcs.get_link_stats)(num, &stats) != 0) {
		retval = UBUS_STATUS_UNKNOWN_ERROR;
		goto __ret;
	}
	atm_link_stats_to_blob(&stats, &bb);

	// Send the reply
	ubus_send_reply(ctx, req, bb.head);


__ret:
	blob_buf_free(&bb);
	return retval;
}

enum {
	ATM_CONFIGURE_LINK_TYPE,
	ATM_CONFIGURE_VPI,
	ATM_CONFIGURE_VCI,
	ATM_CONFIGURE_ENCAPSULATION,
	ATM_CONFIGURE_QOS_CLASS,
	ATM_CONFIGURE_PEAK_CELL_RATE,
	ATM_CONFIGURE_MAX_BURST_SIZE,
	ATM_CONFIGURE_SUSTAINABLE_CELL_RATE,
	__ATM_CONFIGURE_MAX
};

static const struct blobmsg_policy atm_configure_policy[] = {
	[ATM_CONFIGURE_LINK_TYPE] = { .name = "link_type", .type = BLOBMSG_TYPE_STRING },
	[ATM_CONFIGURE_VPI] = { .name = "vpi", .type = BLOBMSG_TYPE_INT32 },
	[ATM_CONFIGURE_VCI] = { .name = "vci", .type = BLOBMSG_TYPE_INT32 },
	[ATM_CONFIGURE_ENCAPSULATION] = { .name = "encapsulation", .type = BLOBMSG_TYPE_STRING },
	[ATM_CONFIGURE_QOS_CLASS] = { .name = "qos_class", .type = BLOBMSG_TYPE_STRING },
	[ATM_CONFIGURE_PEAK_CELL_RATE] = { .name = "peak_cell_rate", .type = BLOBMSG_TYPE_INT32 },
	[ATM_CONFIGURE_MAX_BURST_SIZE] = { .name = "max_burst_size", .type = BLOBMSG_TYPE_INT32 },
	[ATM_CONFIGURE_SUSTAINABLE_CELL_RATE] = { .name = "sustainable_cell_rate", .type = BLOBMSG_TYPE_INT32 }
};

/**
 * Handler of UBUS call "atm.link.x configure"
 *
 * Calling example
 *
 * ubus call atm.link.0 configure "{'link_type':'eoa','vpi':8,'vci':35,'encapsulation':'llc',\
    'qos_class':'ubr','peak_cell_rate':10240,'max_burst_size':8124,'sustainable_cell_rate':9600}"
 *
 */
int atm_link_configure(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method, struct blob_attr *msg)
{
	struct blob_attr *tb[__ATM_CONFIGURE_MAX];
	struct atm_link link;
	struct atm_link_qos qos;
	int link_num = -1;

	memset(&link, 0, sizeof(link));
	memset(&qos, 0, sizeof(qos));

	// Parse and validation check the input parameters
	if (blobmsg_parse(atm_configure_policy, __ATM_CONFIGURE_MAX, tb, blob_data(msg), blob_len(msg)) != 0)
		return UBUS_STATUS_INVALID_ARGUMENT;

	// link_type
	if (tb[ATM_CONFIGURE_LINK_TYPE]) {
		const char *type_str = blobmsg_data(tb[ATM_CONFIGURE_LINK_TYPE]);
		int type_enum = dsl_get_enum_value(atm_link_types, type_str);

		if (type_enum == -1) {
			DSLMNGR_LOG(LOG_ERR,
				"Wrong argument for link_type. It should be like \"eoa\"\n");
			return UBUS_STATUS_INVALID_ARGUMENT;
		}
		link.link_type = type_enum;

		DSLMNGR_LOG(LOG_DEBUG, "link.link_type = %d(%s)\n", link.link_type, type_str);
	}

	// vpi & vci
	if (tb[ATM_CONFIGURE_VPI] && tb[ATM_CONFIGURE_VCI]) {
		link.dest_addr.vpi = blobmsg_get_u32(tb[ATM_CONFIGURE_VPI]);
		link.dest_addr.vci = blobmsg_get_u32(tb[ATM_CONFIGURE_VCI]);

		DSLMNGR_LOG(LOG_DEBUG, "link.dest_addr = { %d, %d }\n", link.dest_addr.vpi, link.dest_addr.vci);
	}

	// encapsulation
	if (tb[ATM_CONFIGURE_ENCAPSULATION]) {
		const char *encap_str = blobmsg_data(tb[ATM_CONFIGURE_ENCAPSULATION]);
		int encap_enum = dsl_get_enum_value(atm_encapsulations, encap_str);

		if (encap_enum == -1) {
			DSLMNGR_LOG(LOG_ERR,
				"Wrong argument for encapsulation. It should be like \"llc\"\n");
			return UBUS_STATUS_INVALID_ARGUMENT;
		}
		link.encapsulation = encap_enum;

		DSLMNGR_LOG(LOG_DEBUG, "link.encapsulation = %d(%s)\n", link.encapsulation, encap_str);
	}

	// qos_class
	if (tb[ATM_CONFIGURE_QOS_CLASS]) {
		const char *class_str = blobmsg_data(tb[ATM_CONFIGURE_QOS_CLASS]);
		int class_enum = dsl_get_enum_value(atm_qos_classes, class_str);

		if (class_enum == -1) {
			DSLMNGR_LOG(LOG_ERR,
				"Wrong argument for qos_class. It should be like \"ubr\"\n");
			return UBUS_STATUS_INVALID_ARGUMENT;
		}
		qos.qos_class = class_enum;

		DSLMNGR_LOG(LOG_DEBUG, "qos.qos_class = %d(%s)\n", qos.qos_class, class_str);
	}

	// peak_cell_rate
	if (tb[ATM_CONFIGURE_PEAK_CELL_RATE]) {
		qos.peak_cell_rate = blobmsg_get_u32(tb[ATM_CONFIGURE_PEAK_CELL_RATE]);

		DSLMNGR_LOG(LOG_DEBUG, "qos.peak_cell_rate = %u\n", qos.peak_cell_rate);
	}

	// max_burst_size
	if (tb[ATM_CONFIGURE_MAX_BURST_SIZE]) {
		qos.max_burst_size = blobmsg_get_u32(tb[ATM_CONFIGURE_MAX_BURST_SIZE]);

		DSLMNGR_LOG(LOG_DEBUG, "qos.max_burst_size = %u\n", qos.max_burst_size);
	}

	// sustainable_cell_rate
	if (tb[ATM_CONFIGURE_SUSTAINABLE_CELL_RATE]) {
		qos.sustainable_cell_rate = blobmsg_get_u32(tb[ATM_CONFIGURE_SUSTAINABLE_CELL_RATE]);

		DSLMNGR_LOG(LOG_DEBUG, "qos.sustainable_cell_rate = %u\n", qos.sustainable_cell_rate);
	}

	// Get link number and call libdsl API
	sscanf(obj->name, "atm.link.%d", &link_num);
	link_num--;  // indexes start from 1 in ubus object
	if (atm_funcs.configure(link_num, &link, &qos) != 0) {
		DSLMNGR_LOG(LOG_ERR, "ATM link %d confuration failed\n", link_num);
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	return 0;
}

static struct ubus_method atm_link_methods[] = {
	{ .name = "status", .handler = atm_link_status },
	{ .name = "stats",  .handler = atm_link_stats_handler },
	UBUS_METHOD("configure", atm_link_configure,  atm_configure_policy)
};

static struct ubus_object_type atm_link_type = UBUS_OBJECT_TYPE("atm.link", atm_link_methods);

static void ptm_link_status_to_blob(const struct ptm_link *link, struct blob_buf *bb)
{
	char mac[18];

	blobmsg_add_string(bb, "status", dsl_if_status_str(link->status));
	snprintf(mac, sizeof(mac), "%02x:%02x:%02x:%02x:%02x:%02x",
			link->mac_addr[0], link->mac_addr[1], link->mac_addr[2],
			link->mac_addr[3], link->mac_addr[4], link->mac_addr[5]);
	blobmsg_add_string(bb, "mac_addr", mac);
}

int ptm_link_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 ptm_link link;
	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, "ptm.link.%d", &num);
	num--;  // indexes start from 1 in ubus object

	if (ptm_funcs.get_link_info == NULL || (*ptm_funcs.get_link_info)(num, &link) != 0) {
		retval = UBUS_STATUS_UNKNOWN_ERROR;
		goto __ret;
	}
	ptm_link_status_to_blob(&link, &bb);

	// Send the reply
	ubus_send_reply(ctx, req, bb.head);

__ret:
	blob_buf_free(&bb);
	return retval;
}

static struct ubus_method ptm_link_methods[] = {
	{ .name = "status", .handler = ptm_link_status }
};

static struct ubus_object_type ptm_link_type = UBUS_OBJECT_TYPE("ptm.link", ptm_link_methods);

/**
 * Handler of UBUS call "dsl.line.x configure"
 *
 * Calling example
 *
 * ubus call dsl.line.0 configure "{'xtse':'01,02,ab,cd,41,52,f8,6e',\
    'vdsl2_profiles':'8a,8b,8c,8d,17a,30a,35b',\
    'fast_profiles':'106a,212a','data_gathering':true,'limit_mask':255,'us0_mask':511}"
 *
 */
int dsl_line_configure(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method, struct blob_attr *msg)
{
	struct blob_attr *tb[__DSL_CONFIGURE_MAX];
	struct dsl_config_params cfg_params;
	int line_num = -1, i;

	if (!xdsl_ops.configure) {
		DSLMNGR_LOG(LOG_ERR, "This function is not supported on the current platform yet\n");
		return UBUS_STATUS_NOT_SUPPORTED;
	}

	memset(&cfg_params, 0, sizeof(cfg_params));

	// Parse and validation check the input parameters
	if (blobmsg_parse(dsl_configure_policy, __DSL_CONFIGURE_MAX, tb, blob_data(msg), blob_len(msg)) != 0)
		return UBUS_STATUS_INVALID_ARGUMENT;

	// XTSE
	if (tb[DSL_CONFIGURE_XTSE]) {
		const char *xtse_str = blobmsg_data(tb[DSL_CONFIGURE_XTSE]);

		if (!xtse_str || !*xtse_str ||
			sscanf(xtse_str, "%02hhx,%02hhx,%02hhx,%02hhx,%02hhx,%02hhx,%02hhx,%02hhx",
					&cfg_params.xtse[0],&cfg_params.xtse[1],&cfg_params.xtse[2],&cfg_params.xtse[3],
					&cfg_params.xtse[4],&cfg_params.xtse[5],&cfg_params.xtse[6],&cfg_params.xtse[7]) != 8) {
			DSLMNGR_LOG(LOG_ERR, "Wrong argument for xtse. It should be like \"01,02,ab,cd,41,52,f8,6e\"\n");
			return UBUS_STATUS_INVALID_ARGUMENT;
		}
		DSLMNGR_LOG(LOG_DEBUG, "cfg_params.xtse = {%02hhx,%02hhx,%02hhx,%02hhx,%02hhx,%02hhx,%02hhx,%02hhx}\n",
				cfg_params.xtse[0],cfg_params.xtse[1],cfg_params.xtse[2],cfg_params.xtse[3],
				cfg_params.xtse[4],cfg_params.xtse[5],cfg_params.xtse[6],cfg_params.xtse[7]);
	}

	// VDSL2 profiles
	if (tb[DSL_CONFIGURE_VDSL2_PROFILES]) {
		const char *profiles_str = blobmsg_data(tb[DSL_CONFIGURE_VDSL2_PROFILES]);
		char *dup, *token, *saveptr;

		if (!profiles_str || !*profiles_str) {
			DSLMNGR_LOG(LOG_ERR,
				"Wrong argument for vdsl2_profiles. It should be like \"8a,8b,8c,8d,17a,30a,35b\"\n");
			return UBUS_STATUS_INVALID_ARGUMENT;
		}

		dup = alloca(strlen(profiles_str) + 1);
		strncpy(dup, profiles_str, strlen(profiles_str) + 1);
		for (token = strtok_r(dup, ",", &saveptr); token != NULL; token = strtok_r(NULL, ",", &saveptr)) {
			int profile = dsl_get_enum_value(vdsl2_profiles, token);
			if (profile != -1)
				cfg_params.vdsl2_profiles |= profile;
			else {
				DSLMNGR_LOG(LOG_ERR,
					"Wrong argument for vdsl2_profiles. It should be like \"8a,8b,8c,8d,17a,30a,35b\"\n");
				return UBUS_STATUS_INVALID_ARGUMENT;
			}
		}

		DSLMNGR_LOG(LOG_DEBUG, "cfg_params.vdsl2_profiles = 0x%lx(%s)\n", cfg_params.vdsl2_profiles, profiles_str);
	}

	// FAST profiles
	if (tb[DSL_CONFIGURE_FAST_PROFILES]) {
		const char *profiles_str = blobmsg_data(tb[DSL_CONFIGURE_FAST_PROFILES]);
		char *dup, *token, *saveptr;

		if (!profiles_str || !*profiles_str) {
			DSLMNGR_LOG(LOG_ERR,
				"Wrong argument for fast_profiles. It should be like \"106a,212a\"\n");
			return UBUS_STATUS_INVALID_ARGUMENT;
		}

		dup = alloca(strlen(profiles_str) + 1);
		strncpy(dup, profiles_str, strlen(profiles_str) + 1);
		for (token = strtok_r(dup, ",", &saveptr); token != NULL; token = strtok_r(NULL, ",", &saveptr)) {
			int profile = dsl_get_enum_value(fast_profiles, token);
			if (profile != -1)
				cfg_params.fast_profiles |= profile;
			else {
				DSLMNGR_LOG(LOG_ERR,
					"Wrong argument for fast_profiles. It should be like \"106a,212a\"\n");
				return UBUS_STATUS_INVALID_ARGUMENT;
			}
		}

		DSLMNGR_LOG(LOG_DEBUG, "cfg_params.fast_profiles = 0x%lx(%s)\n", cfg_params.fast_profiles, profiles_str);
	}

	// enable_data_gathering
	if (tb[DSL_CONFIGURE_ENABLE_DATA_GATHERING]) {
		uint8_t enabled = blobmsg_get_u8(tb[DSL_CONFIGURE_ENABLE_DATA_GATHERING]);

		cfg_params.enable_data_gathering = !!enabled;

		DSLMNGR_LOG(LOG_DEBUG, "cfg_params.enable_data_gathering = %d\n", cfg_params.enable_data_gathering);
	}

	// limit_mask
	if (tb[DSL_CONFIGURE_LIMIT_MASK]) {
		cfg_params.limit_mask = blobmsg_get_u32(tb[DSL_CONFIGURE_LIMIT_MASK]);

		DSLMNGR_LOG(LOG_DEBUG, "cfg_params.limit_mask = 0x%x\n", cfg_params.limit_mask);
	}

	// limit_mask
	if (tb[DSL_CONFIGURE_US0_MASK]) {
		cfg_params.us0_mask = blobmsg_get_u32(tb[DSL_CONFIGURE_US0_MASK]);

		DSLMNGR_LOG(LOG_DEBUG, "cfg_params.us0_mask = 0x%x\n", cfg_params.us0_mask);
	}

	// Get line number
	sscanf(obj->name, "dsl.line.%d", &line_num);
	line_num--;  // indexes start from 1 in ubus object
	if (xdsl_ops.configure(line_num, &cfg_params) != 0) {
		DSLMNGR_LOG(LOG_ERR, "DSL line %d confuration failed\n", line_num);
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	return 0;
}

int dsl_add_ubus_objects(struct ubus_context *ctx)
{
	struct ubus_object *line_objects = NULL;
	struct ubus_object *channel_objects = NULL;
	struct ubus_object *fast_objects = NULL;
	struct ubus_object *atm_objects = NULL;
	struct ubus_object *ptm_objects = NULL;
	int ret, max_line, max_channel, i;

	// Add objects dsl
	ret = ubus_add_object(ctx, &dsl_object);
	if (ret) {
		DSLMNGR_LOG(LOG_ERR, "Failed to add UBUS object '%s', %s\n",
				dsl_object.name, ubus_strerror(ret));
		return -1;
	}

	// Add objects dsl.line.x
	max_line = dsl_get_line_number();
	line_objects = calloc(max_line, sizeof(struct ubus_object));
	if (!line_objects) {
		DSLMNGR_LOG(LOG_ERR, "Out of memory\n");
		return -1;
	}
	for (i = 0; i < max_line; i++) {
		char *obj_name;

		if (asprintf(&obj_name, "dsl.line.%d", i + 1) <= 0) {
			DSLMNGR_LOG(LOG_ERR, "Out of memory\n");
			return -1;
		}

		line_objects[i].name = obj_name;
		line_objects[i].type = &dsl_line_type;
		line_objects[i].methods = dsl_line_methods;
		line_objects[i].n_methods = ARRAY_SIZE(dsl_line_methods);

		ret = ubus_add_object(ctx, &line_objects[i]);
		if (ret) {
			DSLMNGR_LOG(LOG_ERR, "Failed to add UBUS object '%s', %s\n",
					obj_name, ubus_strerror(ret));
			return -1;
		}
	}

	// Add objects dsl.channel.x
	max_channel = dsl_get_channel_number();
	channel_objects = calloc(max_channel, sizeof(struct ubus_object));
	if (!channel_objects) {
		DSLMNGR_LOG(LOG_ERR, "Out of memory\n");
		return -1;
	}
	for (i = 0; i < max_channel; i++) {
		char *obj_name;

		if (asprintf(&obj_name, "dsl.channel.%d", i + 1) <= 0) {
			DSLMNGR_LOG(LOG_ERR, "Out of memory\n");
			return -1;
		}

		channel_objects[i].name = obj_name;
		channel_objects[i].type = &dsl_channel_type;
		channel_objects[i].methods = dsl_channel_methods;
		channel_objects[i].n_methods = ARRAY_SIZE(dsl_channel_methods);

		ret = ubus_add_object(ctx, &channel_objects[i]);
		if (ret) {
			DSLMNGR_LOG(LOG_ERR, "Failed to add UBUS object '%s', %s\n",
					obj_name, ubus_strerror(ret));
			return -1;
		}
	}

Jani Juvan's avatar
Jani Juvan committed
	// Add object fast if supported
	if (xdsl_ops.get_fast_line_info && xdsl_ops.get_fast_line_stats &&
	    xdsl_ops.get_fast_line_stats_interval) {
		ret = ubus_add_object(ctx, &fast_object);
		if (ret) {
			DSLMNGR_LOG(LOG_ERR, "Failed to add UBUS object '%s', %s\n",
				    fast_object.name, ubus_strerror(ret));
			return -1;
		}
	}

	// Add objects fast.line.x if supported
	if (xdsl_ops.get_fast_line_info && xdsl_ops.get_fast_line_stats &&
		xdsl_ops.get_fast_line_stats_interval) {
		fast_objects = calloc(max_line, sizeof(struct ubus_object));
		if (!fast_objects) {
			DSLMNGR_LOG(LOG_ERR, "Out of memory\n");
			return -1;
		}
		for (i = 0; i < max_line; i++) {
			char *obj_name;

			if (asprintf(&obj_name, "fast.line.%d", i + 1) <= 0) {
				DSLMNGR_LOG(LOG_ERR, "Out of memory\n");
				return -1;
			}

			fast_objects[i].name = obj_name;
			fast_objects[i].type = &fast_line_type;
			fast_objects[i].methods = fast_line_methods;
			fast_objects[i].n_methods = ARRAY_SIZE(fast_line_methods);

			ret = ubus_add_object(ctx, &fast_objects[i]);
			if (ret) {
				DSLMNGR_LOG(LOG_ERR, "Failed to add UBUS object '%s', %s\n",
						obj_name, ubus_strerror(ret));
				return -1;
			}
		}
	}

	// Add objects atm.link.x if supported
	if (atm_funcs.get_link_info && atm_funcs.get_link_stats && atm_funcs.configure) {
		atm_objects = calloc(max_line, sizeof(struct ubus_object));
		if (!atm_objects) {
			DSLMNGR_LOG(LOG_ERR, "Out of memory\n");
			return -1;
		}
		for (i = 0; i < max_line; i++) {
			char *obj_name;

			if (asprintf(&obj_name, "atm.link.%d", i + 1) <= 0) {
				DSLMNGR_LOG(LOG_ERR, "Out of memory\n");
				return -1;
			}

			atm_objects[i].name = obj_name;
			atm_objects[i].type = &atm_link_type;
			atm_objects[i].methods = atm_link_methods;
			atm_objects[i].n_methods = ARRAY_SIZE(atm_link_methods);

			ret = ubus_add_object(ctx, &atm_objects[i]);
			if (ret) {
				DSLMNGR_LOG(LOG_ERR, "Failed to add UBUS object '%s', %s\n",
						obj_name, ubus_strerror(ret));
				return -1;
			}
		}
	}

	// Add objects ptm.link.x if supported
	if (ptm_funcs.get_link_info) {
		ptm_objects = calloc(max_line, sizeof(struct ubus_object));
		if (!ptm_objects) {
			DSLMNGR_LOG(LOG_ERR, "Out of memory\n");
			return -1;
		}
		for (i = 0; i < max_line; i++) {
			char *obj_name;

			if (asprintf(&obj_name, "ptm.link.%d", i + 1) <= 0) {
				DSLMNGR_LOG(LOG_ERR, "Out of memory\n");
				return -1;
			}

			ptm_objects[i].name = obj_name;
			ptm_objects[i].type = &ptm_link_type;
			ptm_objects[i].methods = ptm_link_methods;
			ptm_objects[i].n_methods = ARRAY_SIZE(ptm_link_methods);

			ret = ubus_add_object(ctx, &ptm_objects[i]);
			if (ret) {
				DSLMNGR_LOG(LOG_ERR, "Failed to add UBUS object '%s', %s\n",
						obj_name, ubus_strerror(ret));
				return -1;
			}
		}
	}

	// Returns on success
	return 0;

__error_ret:
	if (line_objects)
		free(line_objects);
	if (channel_objects)
		free(channel_objects);
	if (fast_objects)
		free(fast_objects);
	if (atm_objects)
		free(atm_objects);
	if (ptm_objects)
		free(ptm_objects);

Jani Juvan's avatar
Jani Juvan committed
static int uci_get_ifname(char *ifname, size_t len, bool is_fast)
{
	struct uci_context *ctx;
	struct uci_package *pkg;
	struct uci_element *e;
	const char *value;
	bool use_ptm = is_fast;
	int i = 0;   // always check line 0 for now

	/* First, we need to check if we are running ADSL or not */
	if (!is_fast) {
		struct dsl_line line;
		if (xdsl_ops.get_line_info == NULL || (*xdsl_ops.get_line_info)(i, &line) != 0)	{
			return -1;
		}
		if (line.standard_used.use_xtse) {
			if (XTSE_BIT_GET(line.standard_used.xtse, G_993_2_EUROPE) ||
			    XTSE_BIT_GET(line.standard_used.xtse, G_993_2_JAPAN) ||
			    XTSE_BIT_GET(line.standard_used.xtse, G_993_2_NORTH_AMERICA))
				use_ptm= true;
		} else {
			if (line.standard_used.mode &
			    (MOD_G_993_1 | MOD_G_993_1_Annex_A |
			     MOD_G_993_2_Annex_A | MOD_G_993_2_Annex_B |
			     MOD_G_993_2_Annex_C))
				use_ptm= true;
		}
	}

	ctx = uci_alloc_context();
	if (!ctx)
		return -1;

	if (uci_load(ctx, "dsl", &pkg)) {
		uci_free_context(ctx);
		return -1;
	}

	uci_foreach_element(&pkg->sections, e) {
		struct uci_section *s = uci_to_section(e);
		if (!strcmp(s->type, use_ptm ? "ptm-device" : "atm-device")) {
			value = uci_lookup_option_string(ctx, s, "device");
			if (value) {
				if (snprintf(ifname, len, "%s", value) >= len) {
					// truncated
					uci_free_context(ctx);
					return -2;
				}
				// success
				uci_free_context(ctx);
				return 0;
			}
		}
	}

	// not found
	uci_free_context(ctx);
	return -1;
}

Jani Juvan's avatar
Jani Juvan committed
static int uci_get_oem_params(char *vendor_id, char *sw_version, char *serial_nr)
{
	struct uci_context *ctx;
	struct uci_package *pkg;
	struct uci_element *e;
	const char *value;

	ctx = uci_alloc_context();
	if (!ctx)
		return -1;

	if (uci_load(ctx, "dsl", &pkg)) {
		uci_free_context(ctx);
		return -1;
	}

	uci_foreach_element(&pkg->sections, e) {
		struct uci_section *s = uci_to_section(e);
		if (!strcmp(s->type, "oem-parameters")) {
			value = uci_lookup_option_string(ctx, s, "country_code");
			if (value)
				sscanf(value, "%2hhX%2hhX", vendor_id, vendor_id + 1);
			value = uci_lookup_option_string(ctx, s, "vendor_id");
			if (value)
				strncpy(vendor_id + 2, value, 4);
			value = uci_lookup_option_string(ctx, s, "vendor_suffix");
			if (value)
				sscanf(value, "%2hhX%2hhX", vendor_id + 6, vendor_id + 7);
			value = uci_lookup_option_string(ctx, s, "sw_version");
			if (value)
				strncpy(sw_version, value, 16);
			value = uci_lookup_option_string(ctx, s, "serial_nr");
			if (value)
				strncpy(serial_nr, value, 32);
		}
	}

	uci_free_context(ctx);
	return 0;
}

int dsl_oem_init()
{
	if (xdsl_ops.set_oem_parameter) {
		char vendor_id[8] = { 0 };  // 2 byte hex country code + 4 byte string id + 2 byte hex suffix
		char sw_version[16] =  { 0 };
		char serial_nr[32] = { 0 };
		char zero[32] = { 0 };

		if (uci_get_oem_params(vendor_id, sw_version, serial_nr) != 0) {
			DSLMNGR_LOG(LOG_ERR, "Unable to read OEM parameters from config, skipping OEM init\n");
			return -1;
		}
		// Note: These are BRCM specific parameters from bcmadsl.h
		if (memcmp(vendor_id, zero, 8) != 0) {
			(*xdsl_ops.set_oem_parameter)(4, (void *)vendor_id, 8);
		}
		if (memcmp(sw_version, zero, 16) != 0) {
			(*xdsl_ops.set_oem_parameter)(5, (void *)sw_version, 16);
		}
		if (memcmp(serial_nr, zero, 32) != 0) {
			(*xdsl_ops.set_oem_parameter)(6, (void *)serial_nr, 32);
		}
	}
	return 0;
}