* Re: [RFC] net: towards a generic PON framework
2026-09-26 20:21 ` Gaoyang Wei
@ 2026-09-26 21:55 ` Matheus Sampaio Queiroga
2026-09-26 22:15 ` Gaoyang Wei
0 siblings, 1 reply; 26+ messages in thread
From: Matheus Sampaio Queiroga @ 2026-09-26 21:55 UTC (permalink / raw)
To: Gaoyang Wei
Cc: netdev, pbs05, Benjamin Larsson, Lorenzo Bianconi, Andrew Lunn,
Russell King
> > in my implementation part I have a system controlled by the kernel,
> > without userspace for OMCI, everything being handled and controlled
> > by the kernel
>
> This is particularly interesting because it gives us two working
> design points with a very different kernel/userspace split.
>
> The AN7581/AN7583 implementation keeps the G.988 MIB and service graph
> in userspace, while your implementation places the OMCI agent, MIB,
> ME handling and service graph in net/xpon/omci.
>
> I think this is probably one of the most important questions for this
> RFC.
>
> Could you describe what led you to keep the G.988 model in the kernel?
>
> In particular, I would be interested in whether there are operations
> which require sufficiently tight coupling with the PLOAM/GEM state
> that a userspace OMCI agent becomes impractical, or whether the main
> reason is architectural simplicity and having one kernel-controlled
> state machine.
>
> I am also wondering whether the transport and the OMCI implementation
> could be treated as two separate questions.
>
> For example, could a generic xPON layer provide the OMCC transport and
> GEM/T-CONT operations while allowing either:
>
> 1. an in-kernel OMCI implementation, or
> 2. a userspace OMCI implementation through a packet interface?
>
> That might let us avoid deciding too early that every driver must use
> the same OMCI architecture.
with tests on en7523 and en751221 with some users, I saw them
attaching the OMCI network interface within a bridge, and in order not
to expose this interface again to a user, I implemented my OMCI within
the kernel, without an export network interface in the system, with
this in the driver I capture the packets and forward the data to the
OMCI subsystem in which it processes, and sends the response back to
OLT, with this I also had less overhead in the ethernet system, and
faster responses to OLT due to the new implementation.
In some internal tests, removing the net device and pasting another
method, I saw a poorly done implementation and having a lot of
overhead in CPU usage, making the system very unstable and slow for
some responses to OMCI, in addition to keeping some subsystems already
in the kernel easier to integrate if userspace is needed to make
several calls to other kernel subsystems, and today we already have
functions exposing this data within the kernel.
I am also aware of some Realtek SoCs having slightly different OMCI in
which my subsystems need to be adjusted to see this data, something
that fails in my current implementation
> > I'm creating two new subsystems to control LDDLA (optical) and xPON
>
> The optical_frontend abstraction is also very relevant to Benjamin's
> earlier RFC.
>
> I think it would be useful to compare exactly which operations your
> optical_frontend API needs against the existing PHY/SFP/hwmon
> interfaces.
>
> For example, I would expect:
>
> - temperature / voltage / bias / optical power -> hwmon
> - calibration storage -> NVMEM where applicable
> - LOS / TX enable / burst control -> frontend <-> PON MAC API
>
> but I am less sure about protocol/wavelength configuration and where
> that should live.
Airoha LDDLA are the most different we have, they do not expose a
standard SFP implementation, and emulating this data is very expensive
for what we have today, but Semtech LDDLA (which my devices use) has
the support for exposing data in the SFP standard, but not in use in
my implementation
The optical subsystem in which I implemented it already uses several
subsystems already present in Linux, so temperatures, bias and optical
power are already used within hwmon, for calibration I already use
nvmem to obtain the data and start LDDLA.
Device control is exposed by new generic functions in which to take
the data from these optical devices and use these functions to control
LOS, TX enable, burst control, etc...
There is still a lot that needs to be fixed in this part too
> > In total, I have tests on the Airoha en7523, en751221 and en7528
>
> This is especially useful.
>
> Having EN751221/EN7523/EN7528 in addition to AN7581/AN7583 gives us
> more than one hardware generation to test the abstraction against.
> I would very much like to identify which fields and operations are
> actually common between them before defining a generic API.
>
> Your DT example also raises another question.
>
> The hardware relationships such as:
>
> phys = <&xpon_phy>;
> ethernet = <&gdm2>;
> optical-frontends = <&en7571>;
>
> seem like natural DT topology.
>
> On the other hand, properties such as:
>
> omci-vendor-id = "TPLG";
> airoha,gpon-serial-from-mac;
>
> look more like ONU identity/provisioning policy than hardware
> description to me.
>
> I suspect those may be better supplied through NVMEM or userspace
> configuration rather than becoming DT ABI, but this is another point
> where upstream feedback would be useful.
Not all data on all devices is saved somewhere in the memory region,
so some are abstracted from several values, such as the GPON SN which
can be abstracted from the interface's mac address plus the gpon id,
these values are not yet fully ready, there are many things that need
to be fixed regarding fixed data.
a good part of the subsystem depends on values coming from the
userspace, that's why we wrote a generic netlink and sysfs for
userspace communication
This is everything I implemented:
diff --git a/drivers/net/optical/Kconfig b/drivers/net/optical/Kconfig
index 3a460ea5aa66..4c7703f2e3b6 100644
--- a/drivers/net/optical/Kconfig
+++ b/drivers/net/optical/Kconfig
@@ -25,19 +25,9 @@ config OPTICAL_FRONTEND_HWMON
Alarm state and provider-supplied low/high thresholds are exported when
available. Say Y when standard hwmon userspace should monitor optics.
-config OPTICAL_FRONTEND_SFP_COMPAT
- bool "Virtual SFF-8472 compatibility bus"
- depends on I2C
- default y
- help
- Allow a soldered optical frontend to expose a virtual SFP I2C bus with
- SFF-8472 identity and DDMI pages for existing SFP/phylink tooling.
-
- This is intended as a compatibility path for existing device trees. New
- fixed-front-end consumers should use the optical frontend API directly.
-
endif
+source "drivers/net/optical/airoha/Kconfig"
source "drivers/net/optical/semtech/Kconfig"
endmenu
diff --git a/drivers/net/optical/Makefile b/drivers/net/optical/Makefile
index 2c02020b41b9..abffc2d60030 100644
--- a/drivers/net/optical/Makefile
+++ b/drivers/net/optical/Makefile
@@ -2,6 +2,6 @@
obj-$(CONFIG_OPTICAL_FRONTEND) += optical_frontend.o
optical_frontend-y := core.o
optical_frontend-$(CONFIG_OPTICAL_FRONTEND_HWMON) += hwmon.o
-optical_frontend-$(CONFIG_OPTICAL_FRONTEND_SFP_COMPAT) += sfp_compat.o
+obj-y += airoha/
obj-y += semtech/
diff --git a/drivers/net/optical/core.c b/drivers/net/optical/core.c
index 040e937c127c..f9c7d1dcd0c8 100644
--- a/drivers/net/optical/core.c
+++ b/drivers/net/optical/core.c
@@ -13,6 +13,7 @@
#include <linux/device/class.h>
#include <linux/device/devres.h>
#include <linux/err.h>
+#include <linux/gpio/consumer.h>
#include <linux/idr.h>
#include <linux/jiffies.h>
#include <linux/module.h>
@@ -22,6 +23,7 @@
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/string.h>
+#include <linux/sysfs.h>
struct optical_frontend {
struct device dev;
@@ -29,6 +31,7 @@ struct optical_frontend {
const struct optical_frontend_desc *desc;
const struct optical_frontend_ops *ops;
void *drvdata;
+ struct gpio_desc *tx_disable_gpio;
struct mutex op_lock; /* serializes provider callbacks and shared state */
struct optical_frontend_mode mode;
@@ -43,6 +46,215 @@ struct optical_frontend {
static DEFINE_IDA(optical_frontend_ida);
+static const char *optical_frontend_protocol_name(enum
optical_frontend_protocol protocol)
+{
+ switch (protocol) {
+ case OPTICAL_FRONTEND_PROTO_ETHERNET:
+ return "ethernet";
+ case OPTICAL_FRONTEND_PROTO_EPON:
+ return "epon";
+ case OPTICAL_FRONTEND_PROTO_GPON:
+ return "gpon";
+ case OPTICAL_FRONTEND_PROTO_XGPON:
+ return "xgpon";
+ case OPTICAL_FRONTEND_PROTO_XGSPON:
+ return "xgspon";
+ case OPTICAL_FRONTEND_PROTO_NGPON2:
+ return "ngpon2";
+ case OPTICAL_FRONTEND_PROTO_UNSPEC:
+ default:
+ return "unspecified";
+ }
+}
+
+static ssize_t vendor_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct optical_frontend *frontend =
+ container_of(dev, struct optical_frontend, dev);
+
+ return sysfs_emit(buf, "%s\n",
+ frontend->desc->vendor_name ?: "unknown");
+}
+static DEVICE_ATTR_RO(vendor);
+
+static ssize_t model_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct optical_frontend *frontend =
+ container_of(dev, struct optical_frontend, dev);
+
+ return sysfs_emit(buf, "%s\n",
+ frontend->desc->part_number ?: frontend->desc->name);
+}
+static DEVICE_ATTR_RO(model);
+
+static ssize_t type_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct optical_frontend *frontend =
+ container_of(dev, struct optical_frontend, dev);
+
+ return sysfs_emit(buf, "%s\n", frontend->desc->type ?: "unknown");
+}
+static DEVICE_ATTR_RO(type);
+
+static ssize_t capabilities_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct optical_frontend *frontend =
+ container_of(dev, struct optical_frontend, dev);
+ u32 capabilities = frontend->desc->capabilities;
+
+ if (frontend->tx_disable_gpio)
+ capabilities |= OPTICAL_FRONTEND_CAP_TX_DISABLE;
+
+ return sysfs_emit(buf, "0x%08x\n", capabilities);
+}
+static DEVICE_ATTR_RO(capabilities);
+
+static ssize_t supported_protocols_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct optical_frontend *frontend =
+ container_of(dev, struct optical_frontend, dev);
+ ssize_t len = 0;
+ unsigned int protocol;
+
+ for (protocol = OPTICAL_FRONTEND_PROTO_ETHERNET;
+ protocol <= OPTICAL_FRONTEND_PROTO_NGPON2; protocol++) {
+ if (!(frontend->desc->protocols & BIT(protocol)))
+ continue;
+ len += sysfs_emit_at(buf, len, "%s%s", len ? " " : "",
+ optical_frontend_protocol_name(protocol));
+ }
+
+ return sysfs_emit_at(buf, len, "\n");
+}
+static DEVICE_ATTR_RO(supported_protocols);
+
+static ssize_t protocol_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct optical_frontend *frontend =
+ container_of(dev, struct optical_frontend, dev);
+ struct optical_frontend_mode mode;
+ int ret;
+
+ ret = optical_frontend_get_mode(frontend, &mode);
+ if (ret == -ENODATA)
+ return sysfs_emit(buf, "unspecified\n");
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%s\n", optical_frontend_protocol_name(mode.protocol));
+}
+static DEVICE_ATTR_RO(protocol);
+
+static int optical_frontend_sysfs_state(struct optical_frontend *frontend,
+ struct optical_frontend_state *state)
+{
+ int ret = optical_frontend_get_state(frontend, state);
+
+ return ret == -EOPNOTSUPP ? -ENODATA : ret;
+}
+
+#define OPTICAL_FRONTEND_STATE_ATTR(_name, _flag, _member) \
+static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ struct optical_frontend *frontend = \
+ container_of(dev, struct optical_frontend, dev); \
+ struct optical_frontend_state state; \
+ int ret; \
+\
+ ret = optical_frontend_sysfs_state(frontend, &state); \
+ if (ret) \
+ return ret; \
+ if (!(state.valid & (_flag))) \
+ return -ENODATA; \
+ return sysfs_emit(buf, "%u\n", state._member); \
+} \
+static DEVICE_ATTR_RO(_name)
+
+OPTICAL_FRONTEND_STATE_ATTR(present,
OPTICAL_FRONTEND_STATE_F_PRESENT, present);
+OPTICAL_FRONTEND_STATE_ATTR(ready, OPTICAL_FRONTEND_STATE_F_READY, ready);
+OPTICAL_FRONTEND_STATE_ATTR(rx_los, OPTICAL_FRONTEND_STATE_F_RX_LOS, rx_los);
+OPTICAL_FRONTEND_STATE_ATTR(tx_fault,
OPTICAL_FRONTEND_STATE_F_TX_FAULT, tx_fault);
+OPTICAL_FRONTEND_STATE_ATTR(tx_enabled, OPTICAL_FRONTEND_STATE_F_TX_ENABLED,
+ tx_enabled);
+
+static ssize_t alarms_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct optical_frontend *frontend =
+ container_of(dev, struct optical_frontend, dev);
+ struct optical_frontend_telemetry telemetry;
+ int ret;
+
+ ret = optical_frontend_get_telemetry(frontend, &telemetry);
+ if (ret)
+ return ret;
+ if (!(telemetry.valid & OPTICAL_FRONTEND_TELEMETRY_F_ALARMS))
+ return -ENODATA;
+
+ return sysfs_emit(buf, "0x%08x\n", telemetry.alarms);
+}
+static DEVICE_ATTR_RO(alarms);
+
+static struct attribute *optical_frontend_attrs[] = {
+ &dev_attr_vendor.attr,
+ &dev_attr_model.attr,
+ &dev_attr_type.attr,
+ &dev_attr_capabilities.attr,
+ &dev_attr_supported_protocols.attr,
+ &dev_attr_protocol.attr,
+ &dev_attr_present.attr,
+ &dev_attr_ready.attr,
+ &dev_attr_rx_los.attr,
+ &dev_attr_tx_fault.attr,
+ &dev_attr_tx_enabled.attr,
+ &dev_attr_alarms.attr,
+ NULL,
+};
+
+static umode_t optical_frontend_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct optical_frontend *frontend =
+ container_of(dev, struct optical_frontend, dev);
+
+ if (attr == &dev_attr_present.attr || attr == &dev_attr_ready.attr)
+ return frontend->ops->get_state ? 0444 : 0;
+ if (attr == &dev_attr_rx_los.attr)
+ return frontend->desc->capabilities & OPTICAL_FRONTEND_CAP_RX_LOS ?
+ 0444 : 0;
+ if (attr == &dev_attr_tx_fault.attr)
+ return frontend->desc->capabilities & OPTICAL_FRONTEND_CAP_TX_FAULT ?
+ 0444 : 0;
+ if (attr == &dev_attr_tx_enabled.attr)
+ return frontend->tx_disable_gpio || frontend->ops->tx_enable ||
+ (frontend->desc->capabilities & OPTICAL_FRONTEND_CAP_TX_DISABLE) ?
+ 0444 : 0;
+ if (attr == &dev_attr_alarms.attr)
+ return frontend->desc->capabilities & OPTICAL_FRONTEND_CAP_ALARMS ?
+ 0444 : 0;
+
+ return 0444;
+}
+
+static const struct attribute_group optical_frontend_group = {
+ .attrs = optical_frontend_attrs,
+ .is_visible = optical_frontend_attr_is_visible,
+};
+
+static const struct attribute_group *optical_frontend_groups[] = {
+ &optical_frontend_group,
+ NULL,
+};
+
static void optical_frontend_dev_release(struct device *dev)
{
struct optical_frontend *frontend =
@@ -55,12 +267,15 @@ static void optical_frontend_dev_release(struct
device *dev)
static const struct class optical_frontend_class = {
.name = "optical_frontend",
.dev_release = optical_frontend_dev_release,
+ .dev_groups = optical_frontend_groups,
};
static void optical_frontend_unregister(void *data)
{
struct optical_frontend *frontend = data;
+ if (frontend->tx_disable_gpio)
+ gpiod_set_value_cansleep(frontend->tx_disable_gpio, 1);
device_unregister(&frontend->dev);
}
@@ -78,11 +293,20 @@ devm_optical_frontend_register(struct device *dev,
void *drvdata)
{
struct optical_frontend *frontend;
+ struct gpio_desc *tx_disable_gpio;
int ret;
if (!dev || !desc || !desc->name || !ops)
return ERR_PTR(-EINVAL);
+ tx_disable_gpio = devm_gpiod_get_optional(dev, "tx-disable",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(tx_disable_gpio)) {
+ ret = dev_err_probe(dev, PTR_ERR(tx_disable_gpio),
+ "failed to get TX disable GPIO\n");
+ return ERR_PTR(ret);
+ }
+
frontend = kzalloc(sizeof(*frontend), GFP_KERNEL);
if (!frontend)
return ERR_PTR(-ENOMEM);
@@ -98,11 +322,13 @@ devm_optical_frontend_register(struct device *dev,
frontend->desc = desc;
frontend->ops = ops;
frontend->drvdata = drvdata;
+ frontend->tx_disable_gpio = tx_disable_gpio;
mutex_init(&frontend->op_lock);
device_initialize(&frontend->dev);
frontend->dev.class = &optical_frontend_class;
frontend->dev.parent = dev;
+ frontend->dev.groups = desc->groups;
device_set_node(&frontend->dev, dev_fwnode(dev));
dev_set_name(&frontend->dev, "frontend%d", frontend->id);
@@ -229,6 +455,21 @@ void *optical_frontend_get_drvdata(struct
optical_frontend *frontend)
}
EXPORT_SYMBOL_GPL(optical_frontend_get_drvdata);
+struct optical_frontend *optical_frontend_from_dev(struct device *dev)
+{
+ if (!dev || dev->class != &optical_frontend_class)
+ return NULL;
+
+ return container_of(dev, struct optical_frontend, dev);
+}
+EXPORT_SYMBOL_GPL(optical_frontend_from_dev);
+
+struct device *optical_frontend_get_device(struct optical_frontend *frontend)
+{
+ return frontend ? &frontend->dev : NULL;
+}
+EXPORT_SYMBOL_GPL(optical_frontend_get_device);
+
struct device *optical_frontend_get_provider(struct optical_frontend *frontend)
{
return frontend ? frontend->provider : NULL;
@@ -286,6 +527,39 @@ int optical_frontend_get_mode(struct
optical_frontend *frontend,
}
EXPORT_SYMBOL_GPL(optical_frontend_get_mode);
+int optical_frontend_tx_enable(struct optical_frontend *frontend, bool enable)
+{
+ int ret = 0;
+
+ if (!frontend)
+ return -EINVAL;
+ if (!frontend->tx_disable_gpio && !frontend->ops->tx_enable)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&frontend->op_lock);
+
+ /* Block the optical output before asking the provider to shut down. */
+ if (!enable && frontend->tx_disable_gpio)
+ gpiod_set_value_cansleep(frontend->tx_disable_gpio, 1);
+
+ if (frontend->ops->tx_enable) {
+ ret = frontend->ops->tx_enable(frontend, enable);
+ if (ret)
+ goto out;
+ }
+
+ /* Release the external interlock only after the provider is ready. */
+ if (enable && frontend->tx_disable_gpio)
+ gpiod_set_value_cansleep(frontend->tx_disable_gpio, 0);
+
+out:
+ frontend->telemetry_cache_valid = false;
+ mutex_unlock(&frontend->op_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(optical_frontend_tx_enable);
+
int optical_frontend_tx_rearm(struct optical_frontend *frontend)
{
int ret;
@@ -307,18 +581,39 @@ EXPORT_SYMBOL_GPL(optical_frontend_tx_rearm);
int optical_frontend_get_state(struct optical_frontend *frontend,
struct optical_frontend_state *state)
{
- int ret;
+ int gpio_value;
+ int ret = 0;
if (!frontend || !state)
return -EINVAL;
- if (!frontend->ops->get_state)
- return -EOPNOTSUPP;
memset(state, 0, sizeof(*state));
mutex_lock(&frontend->op_lock);
- ret = frontend->ops->get_state(frontend, state);
- mutex_unlock(&frontend->op_lock);
+ if (frontend->ops->get_state) {
+ ret = frontend->ops->get_state(frontend, state);
+ if (ret)
+ goto out;
+ }
+ /* TX_DISABLE is a core-owned interlock, so report it even when the
+ * provider has no matching status register. GPIO values are logical:
+ * one means disabled regardless of the electrical active level.
+ */
+ if (frontend->tx_disable_gpio) {
+ gpio_value = gpiod_get_value_cansleep(frontend->tx_disable_gpio);
+ if (gpio_value < 0) {
+ ret = gpio_value;
+ goto out;
+ }
+ state->tx_enabled = !gpio_value;
+ state->valid |= OPTICAL_FRONTEND_STATE_F_TX_ENABLED;
+ }
+
+ if (!state->valid)
+ ret = -ENODATA;
+
+out:
+ mutex_unlock(&frontend->op_lock);
return ret;
}
EXPORT_SYMBOL_GPL(optical_frontend_get_state);
diff --git a/drivers/net/optical/hwmon.c b/drivers/net/optical/hwmon.c
index 25645f59e315..54b251d9b287 100644
--- a/drivers/net/optical/hwmon.c
+++ b/drivers/net/optical/hwmon.c
@@ -6,6 +6,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/optical_frontend.h>
+#include <linux/sysfs.h>
static bool optical_frontend_hwmon_has_threshold(
const struct optical_frontend_desc *desc,
@@ -285,25 +286,46 @@ static const struct hwmon_chip_info
optical_frontend_hwmon_chip_info = {
.info = optical_frontend_hwmon_info,
};
+static void optical_frontend_hwmon_remove_link(void *data)
+{
+ struct optical_frontend *frontend = data;
+ struct device *dev = optical_frontend_get_device(frontend);
+
+ if (dev)
+ sysfs_remove_link(&dev->kobj, "hwmon");
+}
+
int devm_optical_frontend_hwmon_register(struct optical_frontend *frontend)
{
const struct optical_frontend_desc *desc;
+ struct device *frontend_dev;
struct device *provider;
struct device *hwmon;
+ int ret;
if (!frontend)
return -EINVAL;
desc = optical_frontend_get_desc(frontend);
+ frontend_dev = optical_frontend_get_device(frontend);
provider = optical_frontend_get_provider(frontend);
- if (!desc || !provider)
+ if (!desc || !frontend_dev || !provider)
return -EINVAL;
hwmon = devm_hwmon_device_register_with_info(provider, desc->name,
frontend,
&optical_frontend_hwmon_chip_info,
NULL);
- return PTR_ERR_OR_ZERO(hwmon);
+ if (IS_ERR(hwmon))
+ return PTR_ERR(hwmon);
+
+ ret = sysfs_create_link(&frontend_dev->kobj, &hwmon->kobj, "hwmon");
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(provider,
+ optical_frontend_hwmon_remove_link,
+ frontend);
}
EXPORT_SYMBOL_GPL(devm_optical_frontend_hwmon_register);
diff --git a/include/linux/optical_frontend.h b/include/linux/optical_frontend.h
index aee25e92cd3d..426140e30545 100644
--- a/include/linux/optical_frontend.h
+++ b/include/linux/optical_frontend.h
@@ -8,6 +8,7 @@
#include <linux/kconfig.h>
#include <linux/types.h>
+struct attribute_group;
struct device;
struct fwnode_handle;
struct optical_frontend;
@@ -152,6 +153,7 @@ struct optical_frontend_thresholds {
/**
* struct optical_frontend_desc - immutable provider description
* @name: short driver/model name used by hwmon and diagnostics
+ * @type: frontend type string exposed through sysfs (for example "lddla")
* @vendor_name: vendor string for SFF compatibility
* @vendor_oui: vendor OUI for SFF compatibility
* @part_number: part number for SFF compatibility
@@ -161,9 +163,11 @@ struct optical_frontend_thresholds {
* @protocols: BIT(OPTICAL_FRONTEND_PROTO_*) bitmap
* @thresholds: optional normalized alarm thresholds
* @telemetry_cache_ms: core telemetry cache lifetime; zero disables caching
+ * @groups: optional provider-specific sysfs groups on the frontend
class device
*/
struct optical_frontend_desc {
const char *name;
+ const char *type;
const char *vendor_name;
u8 vendor_oui[3];
const char *part_number;
@@ -173,11 +177,13 @@ struct optical_frontend_desc {
u32 protocols;
const struct optical_frontend_thresholds *thresholds;
u32 telemetry_cache_ms;
+ const struct attribute_group **groups;
};
/**
* struct optical_frontend_ops - provider operations
* @set_mode: configure protocol/rates; optional
+ * @tx_enable: prepare/enable or disable the optical transmitter; optional
* @tx_rearm: rearm a transmitter safety/fault latch; optional
* @get_state: retrieve normalized frontend state; optional
* @get_telemetry: retrieve normalized live measurements; optional
@@ -188,6 +194,7 @@ struct optical_frontend_desc {
struct optical_frontend_ops {
int (*set_mode)(struct optical_frontend *frontend,
const struct optical_frontend_mode *mode);
+ int (*tx_enable)(struct optical_frontend *frontend, bool enable);
int (*tx_rearm)(struct optical_frontend *frontend);
int (*get_state)(struct optical_frontend *frontend,
struct optical_frontend_state *state);
@@ -209,6 +216,8 @@ devm_optical_frontend_get_by_fwnode(struct device *dev,
const struct fwnode_handle *fwnode);
void *optical_frontend_get_drvdata(struct optical_frontend *frontend);
+struct optical_frontend *optical_frontend_from_dev(struct device *dev);
+struct device *optical_frontend_get_device(struct optical_frontend *frontend);
struct device *optical_frontend_get_provider(struct optical_frontend
*frontend);
const struct optical_frontend_desc *
optical_frontend_get_desc(struct optical_frontend *frontend);
@@ -217,6 +226,7 @@ int optical_frontend_set_mode(struct
optical_frontend *frontend,
const struct optical_frontend_mode *mode);
int optical_frontend_get_mode(struct optical_frontend *frontend,
struct optical_frontend_mode *mode);
+int optical_frontend_tx_enable(struct optical_frontend *frontend, bool enable);
int optical_frontend_tx_rearm(struct optical_frontend *frontend);
int optical_frontend_get_state(struct optical_frontend *frontend,
struct optical_frontend_state *state);
@@ -251,6 +261,18 @@ static inline void
*optical_frontend_get_drvdata(struct optical_frontend *fronte
return NULL;
}
+static inline struct optical_frontend *
+optical_frontend_from_dev(struct device *dev)
+{
+ return NULL;
+}
+
+static inline struct device *
+optical_frontend_get_device(struct optical_frontend *frontend)
+{
+ return NULL;
+}
+
static inline struct device *
optical_frontend_get_provider(struct optical_frontend *frontend)
{
@@ -277,6 +299,12 @@ optical_frontend_get_mode(struct optical_frontend
*frontend,
return -EOPNOTSUPP;
}
+static inline int
+optical_frontend_tx_enable(struct optical_frontend *frontend, bool enable)
+{
+ return -EOPNOTSUPP;
+}
+
static inline int optical_frontend_tx_rearm(struct optical_frontend *frontend)
{
return -EOPNOTSUPP;
@@ -313,14 +341,4 @@ devm_optical_frontend_hwmon_register(struct
optical_frontend *frontend)
}
#endif
-#if IS_REACHABLE(CONFIG_OPTICAL_FRONTEND_SFP_COMPAT)
-int devm_optical_frontend_sfp_register(struct optical_frontend *frontend);
-#else
-static inline int
-devm_optical_frontend_sfp_register(struct optical_frontend *frontend)
-{
- return 0;
-}
-#endif
-
#endif /* _LINUX_OPTICAL_FRONTEND_H */
diff --git a/include/net/xpon/oam.h b/include/net/xpon/oam.h
new file mode 100644
index 000000000000..559a837222ff
--- /dev/null
+++ b/include/net/xpon/oam.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _NET_XPON_OAM_H
+#define _NET_XPON_OAM_H
+
+#include <linux/types.h>
+
+struct sk_buff;
+struct xpon_device;
+struct xpon_oam;
+
+#define XPON_OAM_MAX_LLID 8
+
+struct xpon_oam_ops {
+ int (*xmit)(struct xpon_oam *oam, struct sk_buff *skb, u8 llid_index);
+};
+
+struct xpon_oam *xpon_oam_register(struct xpon_device *xpon,
+ const struct xpon_oam_ops *ops,
+ void *priv);
+void xpon_oam_unregister(struct xpon_oam *oam);
+void *xpon_oam_priv(struct xpon_oam *oam);
+
+void xpon_oam_llid_registered(struct xpon_oam *oam, u8 index, u16 llid);
+void xpon_oam_llid_unregistered(struct xpon_oam *oam, u8 index);
+void xpon_oam_receive(struct xpon_oam *oam, struct sk_buff *skb,
+ u8 llid_index);
+
+#endif /* _NET_XPON_OAM_H */
diff --git a/include/net/xpon/omci.h b/include/net/xpon/omci.h
new file mode 100644
index 000000000000..7f38d9bef161
--- /dev/null
+++ b/include/net/xpon/omci.h
@@ -0,0 +1,364 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _NET_OMCI_H
+#define _NET_OMCI_H
+
+#include <linux/bits.h>
+#include <linux/types.h>
+#include <uapi/linux/omci.h>
+
+struct device;
+struct omci_device;
+struct xpon_device;
+struct sk_buff;
+
+#define OMCI_IDENTITY_F_SERIAL_NUMBER BIT(0)
+#define OMCI_IDENTITY_F_VENDOR_ID BIT(1)
+#define OMCI_IDENTITY_F_PASSWORD BIT(2)
+#define OMCI_IDENTITY_F_VERSION BIT(3)
+#define OMCI_IDENTITY_F_EQUIPMENT_ID BIT(4)
+
+/**
+ * struct omci_identity - normalized ONU identity and registration data
+ * @valid: OMCI_IDENTITY_F_* bitmap
+ * @serial_number: canonical eight-byte GPON serial number
+ * @vendor_id: canonical four-byte vendor identifier
+ * @password: canonical ten-byte GPON registration password
+ * @version: ONU-G version field
+ * @equipment_id: ONU2-G equipment identifier
+ * @serial_source: enum omci_config_source for @serial_number
+ * @vendor_source: enum omci_config_source for @vendor_id
+ * @password_source: enum omci_config_source for @password
+ * @version_source: enum omci_config_source for @version
+ * @equipment_source: enum omci_config_source for @equipment_id
+ */
+struct omci_identity {
+ u32 valid;
+ u8 serial_number[8];
+ u8 vendor_id[OMCI_OLT_VENDOR_ID_LEN];
+ u8 password[10];
+ u8 version[OMCI_OLT_VERSION_LEN];
+ u8 equipment_id[OMCI_OLT_EQUIPMENT_ID_LEN];
+ u8 serial_source;
+ u8 vendor_source;
+ u8 password_source;
+ u8 version_source;
+ u8 equipment_source;
+};
+
+/**
+ * struct omci_me_class - managed entity class descriptor
+ * @class_id: ITU managed entity class identifier
+ * @category: enum omci_class_category
+ * @support: enum omci_class_support
+ * @flags: OMCI_CLASS_F_* bitmap
+ * @name: stable human-readable managed entity name
+ */
+struct omci_me_class {
+ u16 class_id;
+ u8 category;
+ u8 support;
+ u32 flags;
+ const char *name;
+};
+
+enum omci_gem_port_direction {
+ OMCI_GEM_PORT_DIRECTION_UNI_TO_ANI = 1,
+ OMCI_GEM_PORT_DIRECTION_ANI_TO_UNI = 2,
+ OMCI_GEM_PORT_DIRECTION_BIDIRECTIONAL = 3,
+};
+
+/**
+ * struct omci_olt_g - parsed OLT-G identification attributes
+ * @vendor_id: four-character OLT vendor identifier
+ * @equipment_id: OLT equipment or model identifier
+ * @version: OLT software or hardware version identifier
+ * @vendor_id_valid: @vendor_id has been received from the OLT
+ * @equipment_id_valid: @equipment_id has been received from the OLT
+ * @version_valid: @version has been received from the OLT
+ * @valid: at least one identification attribute is available
+ */
+struct omci_olt_g {
+ char vendor_id[OMCI_OLT_VENDOR_ID_LEN + 1];
+ char equipment_id[OMCI_OLT_EQUIPMENT_ID_LEN + 1];
+ char version[OMCI_OLT_VERSION_LEN + 1];
+ bool vendor_id_valid;
+ bool equipment_id_valid;
+ bool version_valid;
+ bool valid;
+};
+
+/**
+ * struct omci_olt_profile_state - resolved OLT interoperability policy
+ * @configured: user-selected profile, including auto
+ * @effective: active concrete profile
+ * @forced: forced concrete profile, or OMCI_OLT_PROFILE_UNSPEC
+ * @quirks: OMCI_OLT_QUIRK_* bitmap for @effective
+ * @olt: latest parsed OLT-G identity
+ */
+struct omci_olt_profile_state {
+ u8 configured;
+ u8 effective;
+ u8 forced;
+ u32 quirks;
+ struct omci_olt_g olt;
+};
+
+/**
+ * struct omci_vlan_filter_entry - parsed VLAN tagging filter entry
+ * @tci: original VLAN tag control information value
+ * @vid: VLAN identifier
+ * @pbit: priority code point
+ * @dei: drop eligible indicator
+ */
+struct omci_vlan_filter_entry {
+ u16 tci;
+ u16 vid;
+ u8 pbit;
+ u8 dei;
+};
+
+/**
+ * struct omci_vlan_tagging_filter - parsed class 84 attributes
+ * @entries: valid VLAN filter entries
+ * @forward_operation: forwarding operation from the managed entity
+ * @num_entries: number of valid entries in @entries
+ * @valid: parsed data is available
+ */
+struct omci_vlan_tagging_filter {
+ struct omci_vlan_filter_entry entries[OMCI_VLAN_FILTER_MAX_ENTRIES];
+ u8 forward_operation;
+ u8 num_entries;
+ bool valid;
+};
+
+/**
+ * struct omci_extended_vlan_rule - parsed class 171 table entry
+ * @raw: original 16-byte table entry
+ * @filter_outer_vid: outer-tag VLAN identifier match
+ * @filter_inner_vid: inner-tag VLAN identifier match
+ * @treat_outer_vid: outer-tag VLAN identifier treatment
+ * @treat_inner_vid: inner-tag VLAN identifier treatment
+ * @filter_outer_pbit: outer-tag priority match
+ * @filter_outer_tpid_dei: outer-tag TPID/DEI match mode
+ * @filter_inner_pbit: inner-tag priority match
+ * @filter_inner_tpid_dei: inner-tag TPID/DEI match mode
+ * @filter_ethertype: EtherType match mode
+ * @tags_to_remove: number of tags removed, or discard mode
+ * @treat_outer_pbit: outer-tag priority treatment
+ * @treat_outer_tpid_dei: outer-tag TPID/DEI treatment mode
+ * @treat_inner_pbit: inner-tag priority treatment
+ * @treat_inner_tpid_dei: inner-tag TPID/DEI treatment mode
+ * @delete: entry encodes a table deletion
+ */
+struct omci_extended_vlan_rule {
+ u8 raw[OMCI_EXT_VLAN_RULE_LEN];
+ u16 filter_outer_vid;
+ u16 filter_inner_vid;
+ u16 treat_outer_vid;
+ u16 treat_inner_vid;
+ u8 filter_outer_pbit;
+ u8 filter_outer_tpid_dei;
+ u8 filter_inner_pbit;
+ u8 filter_inner_tpid_dei;
+ u8 filter_ethertype;
+ u8 tags_to_remove;
+ u8 treat_outer_pbit;
+ u8 treat_outer_tpid_dei;
+ u8 treat_inner_pbit;
+ u8 treat_inner_tpid_dei;
+ bool delete;
+};
+
+/**
+ * struct omci_extended_vlan - parsed class 171 attributes and table
+ * @rules: parsed VLAN operation table
+ * @dscp_to_pbit: DSCP-to-P-bit mapping attribute
+ * @input_tpid: input TPID
+ * @output_tpid: output TPID
+ * @associated_me: associated managed entity pointer
+ * @max_table_size: advertised maximum number of table entries
+ * @association_type: associated managed entity type
+ * @downstream_mode: downstream VLAN processing mode
+ * @rule_count: number of valid rules in @rules
+ * @valid: parsed data is available
+ */
+struct omci_extended_vlan {
+ struct omci_extended_vlan_rule rules[OMCI_EXT_VLAN_MAX_RULES];
+ u8 dscp_to_pbit[24];
+ u16 input_tpid;
+ u16 output_tpid;
+ u16 associated_me;
+ u16 max_table_size;
+ u8 association_type;
+ u8 downstream_mode;
+ u8 rule_count;
+ bool valid;
+};
+
+/**
+ * struct omci_telemetry - current PON and optical telemetry
+ * @valid: OMCI_TELEMETRY_F_* bitmap
+ * @bosa_temperature_mc: BOSA temperature in milli-degrees Celsius
+ * @bosa_voltage_uv: optical frontend supply voltage in microvolts
+ * @bosa_bias_ua: laser bias current in microamps
+ * @bosa_tx_power_nw: transmitted optical power in nanowatts
+ * @bosa_rx_power_nw: received optical power in nanowatts
+ * @bosa_alarms: hardware-specific optical alarm bitmap
+ * @downstream_fec: enum omci_fec_status
+ * @upstream_fec: enum omci_fec_status
+ */
+struct omci_telemetry {
+ u32 valid;
+ s32 bosa_temperature_mc;
+ u32 bosa_voltage_uv;
+ u32 bosa_bias_ua;
+ u32 bosa_tx_power_nw;
+ u32 bosa_rx_power_nw;
+ u32 bosa_alarms;
+ u8 downstream_fec;
+ u8 upstream_fec;
+};
+
+/**
+ * struct omci_ani_topology - pre-existing upstream traffic resources
+ * @tcont_base: first T-CONT managed entity ID
+ * @scheduler_base: first Traffic Scheduler managed entity ID
+ * @queue_base: first Priority Queue managed entity ID
+ * @maximum_queue_size: maximum queue size reported in 2048-byte blocks
+ * @allocated_queue_size: initial allocated queue size in 2048-byte blocks
+ * @tcont_count: number of upstream T-CONT resources
+ * @queues_per_tcont: number of upstream priority queues per T-CONT
+ * @queue_config_option: Priority Queue configuration option
+ * @scheduler_policy: initial Traffic Scheduler policy
+ *
+ * The OMCI agent uses this description to seed complete Priority Queue and
+ * Traffic Scheduler managed entities before the first MIB upload. Entity IDs
+ * are allocated contiguously. Queue entity IDs are grouped by T-CONT.
+ */
+/**
+ * struct omci_service_config - normalized upstream OMCI service
+ * @cookie: stable identifier used to replace or delete one service rule
+ * @uni_entity_id: normalized PPTP Ethernet UNI or VEIP entity
+ * @gem_ctp_entity_id: GEM port network CTP managed entity
+ * @gem_port_id: GEM port identifier programmed in the GPON MAC
+ * @tcont_entity_id: T-CONT managed entity associated with the GEM port
+ * @alloc_id: Alloc-ID of that T-CONT, so a backend whose entity map was
+ * cleared by a GPON restart can still resolve the channel
+ * @vlan_id: VLAN identifier used for upstream classification
+ * @pcp: IEEE 802.1p priority used for classification and queue selection
+ * @queue: hardware upstream queue
+ * @direction: enum omci_gem_port_direction
+ * @vlan_treatment: raw class 171 treatment words for the backend
+ * @multicast_ani_entity_id: Dasan WAN bridge port used as multicast ANI
+ * @vlan_valid: @vlan_id participates in classification
+ * @pcp_valid: @pcp participates in classification
+ * @vlan_treatment_valid: @vlan_treatment requires backend processing
+ * @multicast_ani_valid: @multicast_ani_entity_id was profile-resolved
+ * @multicast: service represents a multicast GEM path
+ * @default_service: fallback service when no VLAN/PCP rule matches
+ */
+struct omci_service_config {
+ u32 cookie;
+ u16 uni_entity_id;
+ u16 gem_ctp_entity_id;
+ u16 gem_port_id;
+ u16 tcont_entity_id;
+ u16 alloc_id;
+ u16 vlan_id;
+ u8 pcp;
+ u8 queue;
+ u8 direction;
+ u8 vlan_treatment[8];
+ u16 multicast_ani_entity_id;
+ bool vlan_valid;
+ bool pcp_valid;
+ bool vlan_treatment_valid;
+ bool multicast_ani_valid;
+ bool multicast;
+ bool default_service;
+};
+
+struct omci_ani_topology {
+ u16 tcont_base;
+ u16 scheduler_base;
+ u16 queue_base;
+ u16 maximum_queue_size;
+ u16 allocated_queue_size;
+ u8 tcont_count;
+ u8 queues_per_tcont;
+ u8 queue_config_option;
+ u8 scheduler_policy;
+};
+
+/**
+ * struct omci_device_ops - hardware transport and provisioning operations
+ * @start: acquire and start the OMCI transport independently of netdev state
+ * @stop: stop and release the OMCI transport
+ * @xmit: transmit an OMCI PDU; consumes @skb only on success
+ * @get_ani_topology: describe pre-existing ANI scheduling resources
+ * @set_tcont: configure a T-CONT mapping
+ * @set_gem_port: configure a GEM port
+ * @set_uni: enable or disable a UNI
+ * @replace_service: atomically add or replace a normalized upstream service
+ * @delete_service: remove a normalized upstream service by cookie
+ * @get_telemetry: refresh PON FEC and optical telemetry
+ * @set_olt_profile: apply a resolved OLT interoperability profile
+ * @set_operational: report whether the in-kernel OMCI agent is operational
+ * @config_changed: report a normalized runtime OMCI configuration change
+ */
+struct omci_device_ops {
+ int (*start)(struct omci_device *odev);
+ void (*stop)(struct omci_device *odev);
+ int (*xmit)(struct omci_device *odev, struct sk_buff *skb,
+ u16 gem_port_id);
+ int (*get_ani_topology)(struct omci_device *odev,
+ struct omci_ani_topology *topology);
+ int (*set_tcont)(struct omci_device *odev, u16 entity_id,
+ u16 alloc_id, bool valid);
+ int (*set_gem_port)(struct omci_device *odev, u16 entity_id,
+ u16 gem_port_id, u16 tcont_entity_id,
+ u8 direction, bool valid, bool encrypted);
+ int (*set_uni)(struct omci_device *odev, u16 entity_id, bool enable);
+ int (*replace_service)(struct omci_device *odev,
+ const struct omci_service_config *service);
+ int (*delete_service)(struct omci_device *odev, u32 cookie);
+ int (*get_telemetry)(struct omci_device *odev,
+ struct omci_telemetry *telemetry);
+ int (*set_olt_profile)(struct omci_device *odev,
+ const struct omci_olt_profile_state *state);
+ void (*set_operational)(struct omci_device *odev, bool operational);
+ void (*config_changed)(struct omci_device *odev, u16 key,
+ const struct omci_identity *identity);
+};
+
+struct omci_device *
+omci_device_register(struct xpon_device *xpon, u32 capabilities,
+ const struct omci_device_ops *ops, void *priv);
+void omci_device_unregister(struct omci_device *odev);
+int omci_device_start(struct omci_device *odev);
+void omci_device_stop(struct omci_device *odev);
+
+void *omci_device_priv(const struct omci_device *odev);
+u32 omci_device_id(const struct omci_device *odev);
+const char *omci_olt_profile_name(u8 profile);
+
+int omci_identity_load(struct device *dev, struct omci_identity *identity);
+void gpon_random_serial_number(const u8 vendor[4], struct
omci_identity *identity);
+void omci_device_set_identity_info(struct omci_device *odev,
+ const struct omci_identity *identity);
+void omci_device_set_identity(struct omci_device *odev,
+ const u8 serial_number[8],
+ const u8 password[10]);
+void omci_device_set_onu_id(struct omci_device *odev, u16 onu_id);
+void omci_device_set_channel(struct omci_device *odev, u16 gem_port_id,
+ bool valid);
+void omci_device_set_state(struct omci_device *odev, u8 state);
+void omci_device_reset_session(struct omci_device *odev);
+int omci_device_set_dying_gasp_enabled(struct omci_device *odev,
+ bool enabled, u8 source);
+int omci_device_send_dying_gasp(struct omci_device *odev);
+void omci_device_receive(struct omci_device *odev, struct sk_buff *skb,
+ u16 gem_port_id, u32 flags);
+int omci_device_reconcile_services(struct omci_device *odev);
+
+#endif /* _NET_OMCI_H */
diff --git a/net/xpon/Kconfig b/net/xpon/Kconfig
new file mode 100644
index 000000000000..f72e6b460066
--- /dev/null
+++ b/net/xpon/Kconfig
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menuconfig XPON
+ tristate "xPON subsystem"
+ depends on NET
+ help
+ Generic Passive Optical Network subsystem. It provides the common
+ representation used by GPON, EPON and XGS-PON devices, including
+ registration state, optical state, carrier, LEDs, sysfs and Generic
+ Netlink notifications.
+
+if XPON
+
+config XPON_OMCI
+ tristate "xPON OMCI agent"
+ default y
+ help
+ In-kernel ONU Management and Control Interface agent used by GPON and
+ XGS-PON providers. The protocol implementation is owned by the xPON
+ subsystem and is attached to one xPON device.
+
+config XPON_OAM
+ tristate "xPON IEEE 802.3ah OAM"
+ default y
+ help
+ IEEE 802.3ah Ethernet OAM support for EPON links. The initial
+ implementation provides the per-LLID OAM object and Information
+ OAMPDU wire definitions. Event, variable and vendor extensions are
+ added on top of this core.
+
+endif # XPON
diff --git a/net/xpon/Makefile b/net/xpon/Makefile
new file mode 100644
index 000000000000..654060432a68
--- /dev/null
+++ b/net/xpon/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_XPON) += xpon.o
+xpon-y := core.o sysfs.o leds.o genl.o
+
+obj-$(CONFIG_XPON_OMCI) += omci/
+obj-$(CONFIG_XPON_OAM) += oam/
diff --git a/net/xpon/core.c b/net/xpon/core.c
new file mode 100644
index 000000000000..c3d3ad0cb378
--- /dev/null
+++ b/net/xpon/core.c
@@ -0,0 +1,340 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Generic xPON core
+ *
+ * GPON, EPON and XGS-PON use different registration and management wire
+ * protocols, but share one datapath object, optical state and set of system
+ * consumers. Protocol providers translate their native state into this core.
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/slab.h>
+#include <net/xpon.h>
+
+#include "internal.h"
+
+static LIST_HEAD(xpon_devices);
+static DEFINE_MUTEX(xpon_devices_lock);
+
+static void xpon_notify_work(struct work_struct *work)
+{
+ struct xpon_device *xpon = container_of(work, struct xpon_device,
+ notify_work);
+ struct xpon_notifier_info info;
+ unsigned long flags, changed;
+
+ spin_lock_irqsave(&xpon->state_lock, flags);
+ changed = xpon->pending_events;
+ if (!changed) {
+ spin_unlock_irqrestore(&xpon->state_lock, flags);
+ return;
+ }
+
+ info.xpon = xpon;
+ info.changed = changed;
+ info.old = xpon->notify_old;
+ info.new = xpon->state;
+ xpon->notify_old = xpon->state;
+ xpon->pending_events = 0;
+ spin_unlock_irqrestore(&xpon->state_lock, flags);
+
+ xpon_leds_update(xpon, &info.new);
+ xpon_sysfs_notify(xpon, changed);
+ xpon_genl_notify(xpon, changed);
+ blocking_notifier_call_chain(&xpon->notifier, changed, &info);
+}
+
+static void xpon_queue_event(struct xpon_device *xpon, unsigned long changed)
+{
+ unsigned long flags;
+
+ if (!xpon)
+ return;
+
+ spin_lock_irqsave(&xpon->state_lock, flags);
+ xpon->pending_events |= changed;
+ spin_unlock_irqrestore(&xpon->state_lock, flags);
+ schedule_work(&xpon->notify_work);
+}
+
+struct xpon_device *
+xpon_device_register(struct device *parent,
+ const struct xpon_device_desc *desc)
+{
+ struct xpon_device *xpon;
+ int ret;
+
+ if (!parent || !desc || !desc->netdev ||
+ desc->mode >= __XPON_MODE_MAX ||
+ !(desc->modes & XPON_MODE_CAP(desc->mode)))
+ return ERR_PTR(-EINVAL);
+
+ xpon = kzalloc(sizeof(*xpon), GFP_KERNEL);
+ if (!xpon)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&xpon->list);
+ spin_lock_init(&xpon->state_lock);
+ mutex_init(&xpon->mode_lock);
+ INIT_WORK(&xpon->notify_work, xpon_notify_work);
+ BLOCKING_INIT_NOTIFIER_HEAD(&xpon->notifier);
+
+ xpon->parent = parent;
+ xpon->netdev = desc->netdev;
+ xpon->optical = desc->optical;
+ xpon->ops = desc->ops;
+ xpon->priv = desc->priv;
+ xpon->modes = desc->modes;
+ xpon->pon_led = desc->pon_led;
+ xpon->los_led = desc->los_led;
+ xpon->fiber_led = desc->fiber_led;
+ xpon->state.mode = desc->mode;
+ xpon->state.registration = XPON_REGISTRATION_DOWN;
+ xpon->state.valid = 0;
+ xpon->notify_old = xpon->state;
+
+ ret = xpon_sysfs_register(xpon);
+ if (ret) {
+ kfree(xpon);
+ return ERR_PTR(ret);
+ }
+
+ mutex_lock(&xpon_devices_lock);
+ list_add_tail(&xpon->list, &xpon_devices);
+ mutex_unlock(&xpon_devices_lock);
+
+ dev_info(parent, "registered xPON device for %s\n", desc->netdev->name);
+ return xpon;
+}
+EXPORT_SYMBOL_GPL(xpon_device_register);
+
+void xpon_device_unregister(struct xpon_device *xpon)
+{
+ if (!xpon)
+ return;
+
+ mutex_lock(&xpon_devices_lock);
+ list_del_init(&xpon->list);
+ mutex_unlock(&xpon_devices_lock);
+ cancel_work_sync(&xpon->notify_work);
+ xpon_sysfs_unregister(xpon);
+ kfree(xpon);
+}
+EXPORT_SYMBOL_GPL(xpon_device_unregister);
+
+struct device *xpon_device_dev(struct xpon_device *xpon)
+{
+ return xpon ? xpon->class_dev : NULL;
+}
+EXPORT_SYMBOL_GPL(xpon_device_dev);
+
+struct net_device *xpon_device_netdev(struct xpon_device *xpon)
+{
+ return xpon ? xpon->netdev : NULL;
+}
+EXPORT_SYMBOL_GPL(xpon_device_netdev);
+
+void *xpon_device_priv(struct xpon_device *xpon)
+{
+ return xpon ? xpon->priv : NULL;
+}
+EXPORT_SYMBOL_GPL(xpon_device_priv);
+
+unsigned long xpon_device_modes(struct xpon_device *xpon)
+{
+ return xpon ? xpon->modes : 0;
+}
+EXPORT_SYMBOL_GPL(xpon_device_modes);
+
+enum xpon_mode xpon_device_mode(struct xpon_device *xpon)
+{
+ return xpon ? READ_ONCE(xpon->state.mode) : XPON_MODE_GPON;
+}
+EXPORT_SYMBOL_GPL(xpon_device_mode);
+
+int xpon_device_set_mode(struct xpon_device *xpon, enum xpon_mode mode)
+{
+ enum xpon_mode old_mode;
+ int ret = 0;
+
+ if (!xpon || mode >= __XPON_MODE_MAX)
+ return -EINVAL;
+ if (!(xpon->modes & XPON_MODE_CAP(mode)))
+ return -EOPNOTSUPP;
+
+ mutex_lock(&xpon->mode_lock);
+ if (xpon->mode_changing) {
+ ret = -EBUSY;
+ goto out;
+ }
+ if (netif_running(xpon->netdev)) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ old_mode = xpon->state.mode;
+ if (old_mode == mode)
+ goto out;
+
+ xpon->mode_changing = true;
+ xpon_device_report_carrier(xpon, false);
+ xpon_device_report_registration(xpon, XPON_REGISTRATION_DOWN);
+
+ if (xpon->ops && xpon->ops->set_mode) {
+ ret = xpon->ops->set_mode(xpon, mode);
+ if (ret)
+ goto out_changing;
+ }
+
+ WRITE_ONCE(xpon->state.mode, mode);
+ xpon_queue_event(xpon, XPON_EVENT_MODE);
+
+out_changing:
+ xpon->mode_changing = false;
+out:
+ mutex_unlock(&xpon->mode_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(xpon_device_set_mode);
+
+void xpon_device_report_registration(struct xpon_device *xpon,
+ enum xpon_registration_state state)
+{
+ unsigned long flags;
+
+ if (!xpon)
+ return;
+
+ spin_lock_irqsave(&xpon->state_lock, flags);
+ if (xpon->state.registration == state) {
+ spin_unlock_irqrestore(&xpon->state_lock, flags);
+ return;
+ }
+ xpon->state.registration = state;
+ xpon->pending_events |= XPON_EVENT_REGISTRATION;
+ spin_unlock_irqrestore(&xpon->state_lock, flags);
+ schedule_work(&xpon->notify_work);
+}
+EXPORT_SYMBOL_GPL(xpon_device_report_registration);
+
+void xpon_device_report_carrier(struct xpon_device *xpon, bool carrier)
+{
+ unsigned long flags;
+
+ if (!xpon)
+ return;
+
+ spin_lock_irqsave(&xpon->state_lock, flags);
+ if (xpon->state.carrier == carrier) {
+ spin_unlock_irqrestore(&xpon->state_lock, flags);
+ return;
+ }
+ xpon->state.carrier = carrier;
+ xpon->pending_events |= XPON_EVENT_CARRIER;
+ spin_unlock_irqrestore(&xpon->state_lock, flags);
+ schedule_work(&xpon->notify_work);
+}
+EXPORT_SYMBOL_GPL(xpon_device_report_carrier);
+
+void xpon_device_report_optical(struct xpon_device *xpon,
+ bool signal_detect, bool los)
+{
+ unsigned long flags;
+ unsigned long changed = 0;
+
+ if (!xpon)
+ return;
+
+ spin_lock_irqsave(&xpon->state_lock, flags);
+ if (!(xpon->state.valid & XPON_STATE_F_SIGNAL) ||
+ xpon->state.signal_detect != signal_detect)
+ changed |= XPON_EVENT_SIGNAL;
+ if (!(xpon->state.valid & XPON_STATE_F_LOS) || xpon->state.los != los)
+ changed |= XPON_EVENT_LOS;
+ xpon->state.valid |= XPON_STATE_F_SIGNAL | XPON_STATE_F_LOS;
+ xpon->state.signal_detect = signal_detect;
+ xpon->state.los = los;
+ xpon->pending_events |= changed;
+ spin_unlock_irqrestore(&xpon->state_lock, flags);
+
+ if (changed)
+ schedule_work(&xpon->notify_work);
+}
+EXPORT_SYMBOL_GPL(xpon_device_report_optical);
+
+void xpon_device_report_event(struct xpon_device *xpon, unsigned long event)
+{
+ xpon_queue_event(xpon, event);
+}
+EXPORT_SYMBOL_GPL(xpon_device_report_event);
+
+int xpon_device_register_notifier(struct xpon_device *xpon,
+ struct notifier_block *nb)
+{
+ if (!xpon || !nb)
+ return -EINVAL;
+ return blocking_notifier_chain_register(&xpon->notifier, nb);
+}
+EXPORT_SYMBOL_GPL(xpon_device_register_notifier);
+
+int xpon_device_unregister_notifier(struct xpon_device *xpon,
+ struct notifier_block *nb)
+{
+ if (!xpon || !nb)
+ return -EINVAL;
+ return blocking_notifier_chain_unregister(&xpon->notifier, nb);
+}
+EXPORT_SYMBOL_GPL(xpon_device_unregister_notifier);
+
+struct xpon_device *xpon_device_find_by_ifindex(u32 ifindex)
+{
+ struct xpon_device *xpon;
+
+ mutex_lock(&xpon_devices_lock);
+ list_for_each_entry(xpon, &xpon_devices, list) {
+ if (xpon->netdev->ifindex == ifindex) {
+ get_device(xpon->class_dev);
+ mutex_unlock(&xpon_devices_lock);
+ return xpon;
+ }
+ }
+ mutex_unlock(&xpon_devices_lock);
+ return NULL;
+}
+
+void xpon_device_put(struct xpon_device *xpon)
+{
+ if (xpon && xpon->class_dev)
+ put_device(xpon->class_dev);
+}
+
+static int __init xpon_init(void)
+{
+ int ret;
+
+ ret = xpon_sysfs_init();
+ if (ret)
+ return ret;
+
+ ret = xpon_genl_init();
+ if (ret) {
+ xpon_sysfs_exit();
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit xpon_exit(void)
+{
+ xpon_genl_exit();
+ xpon_sysfs_exit();
+}
+
+module_init(xpon_init);
+module_exit(xpon_exit);
+
+MODULE_DESCRIPTION("Generic xPON subsystem");
+MODULE_LICENSE("GPL");
diff --git a/net/xpon/genl.c b/net/xpon/genl.c
new file mode 100644
index 000000000000..c6c8c0fd7ae6
--- /dev/null
+++ b/net/xpon/genl.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <net/genetlink.h>
+#include <net/xpon.h>
+
+#include "internal.h"
+
+static const struct nla_policy xpon_genl_policy[XPON_ATTR_MAX + 1] = {
+ [XPON_ATTR_IFINDEX] = { .type = NLA_U32 },
+ [XPON_ATTR_MODE] = { .type = NLA_U8 },
+};
+
+static struct genl_family xpon_genl_family;
+
+static int xpon_genl_put_state(struct sk_buff *msg, struct xpon_device *xpon)
+{
+ if (nla_put_u32(msg, XPON_ATTR_IFINDEX, xpon->netdev->ifindex) ||
+ nla_put_u8(msg, XPON_ATTR_MODE, xpon->state.mode) ||
+ nla_put_u32(msg, XPON_ATTR_AVAILABLE_MODES, xpon->modes) ||
+ nla_put_u8(msg, XPON_ATTR_REGISTRATION, xpon->state.registration) ||
+ nla_put_u8(msg, XPON_ATTR_CARRIER, xpon->state.carrier) ||
+ nla_put_u8(msg, XPON_ATTR_SIGNAL_DETECT, xpon->state.signal_detect) ||
+ nla_put_u8(msg, XPON_ATTR_LOS, xpon->state.los))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int xpon_genl_get(struct sk_buff *skb, struct genl_info *info)
+{
+ struct xpon_device *xpon;
+ struct sk_buff *msg;
+ void *hdr;
+ int ret;
+
+ if (!info->attrs[XPON_ATTR_IFINDEX])
+ return -EINVAL;
+
+ xpon = xpon_device_find_by_ifindex(
+ nla_get_u32(info->attrs[XPON_ATTR_IFINDEX]));
+ if (!xpon)
+ return -ENODEV;
+
+ msg = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ hdr = genlmsg_put_reply(msg, info, &xpon_genl_family, 0, XPON_CMD_GET);
+ if (!hdr) {
+ nlmsg_free(msg);
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ ret = xpon_genl_put_state(msg, xpon);
+ if (ret) {
+ nlmsg_free(msg);
+ goto out;
+ }
+ genlmsg_end(msg, hdr);
+ ret = genlmsg_reply(msg, info);
+out:
+ xpon_device_put(xpon);
+ return ret;
+}
+
+static int xpon_genl_set_mode(struct sk_buff *skb, struct genl_info *info)
+{
+ struct xpon_device *xpon;
+ u8 mode;
+ int ret;
+
+ if (!info->attrs[XPON_ATTR_IFINDEX] || !info->attrs[XPON_ATTR_MODE])
+ return -EINVAL;
+ mode = nla_get_u8(info->attrs[XPON_ATTR_MODE]);
+
+ xpon = xpon_device_find_by_ifindex(
+ nla_get_u32(info->attrs[XPON_ATTR_IFINDEX]));
+ if (!xpon)
+ return -ENODEV;
+ ret = xpon_device_set_mode(xpon, mode);
+ xpon_device_put(xpon);
+ return ret;
+}
+
+static const struct genl_ops xpon_genl_ops[] = {
+ {
+ .cmd = XPON_CMD_GET,
+ .policy = xpon_genl_policy,
+ .doit = xpon_genl_get,
+ },
+ {
+ .cmd = XPON_CMD_GET_STATE,
+ .policy = xpon_genl_policy,
+ .doit = xpon_genl_get,
+ },
+ {
+ .cmd = XPON_CMD_SET_MODE,
+ .flags = GENL_ADMIN_PERM,
+ .policy = xpon_genl_policy,
+ .doit = xpon_genl_set_mode,
+ },
+};
+
+static const struct genl_multicast_group xpon_mcgrps[] = {
+ { .name = "events" },
+};
+
+static struct genl_family xpon_genl_family = {
+ .name = XPON_GENL_NAME,
+ .version = XPON_GENL_VERSION,
+ .maxattr = XPON_ATTR_MAX,
+ .module = THIS_MODULE,
+ .ops = xpon_genl_ops,
+ .n_ops = ARRAY_SIZE(xpon_genl_ops),
+ .mcgrps = xpon_mcgrps,
+ .n_mcgrps = ARRAY_SIZE(xpon_mcgrps),
+};
+
+void xpon_genl_notify(struct xpon_device *xpon, unsigned long changed)
+{
+ struct sk_buff *msg;
+ void *hdr;
+
+ msg = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return;
+ hdr = genlmsg_put(msg, 0, 0, &xpon_genl_family, 0, XPON_CMD_EVENT);
+ if (!hdr)
+ goto free;
+ if (xpon_genl_put_state(msg, xpon) ||
+ nla_put_u32(msg, XPON_ATTR_CHANGED, changed))
+ goto free;
+ genlmsg_end(msg, hdr);
+ genlmsg_multicast(&xpon_genl_family, msg, 0, 0, GFP_KERNEL);
+ return;
+free:
+ nlmsg_free(msg);
+}
+
+int xpon_genl_init(void)
+{
+ return genl_register_family(&xpon_genl_family);
+}
+
+void xpon_genl_exit(void)
+{
+ genl_unregister_family(&xpon_genl_family);
+}
diff --git a/net/xpon/internal.h b/net/xpon/internal.h
new file mode 100644
index 000000000000..8ca207f48d16
--- /dev/null
+++ b/net/xpon/internal.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _NET_XPON_INTERNAL_H
+#define _NET_XPON_INTERNAL_H
+
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <net/xpon.h>
+
+struct xpon_device {
+ struct list_head list;
+ struct device *parent;
+ struct device *class_dev;
+ struct net_device *netdev;
+ struct device *optical;
+ const struct xpon_device_ops *ops;
+ void *priv;
+
+ unsigned long modes;
+ struct xpon_state state;
+ spinlock_t state_lock;
+ struct mutex mode_lock;
+ bool mode_changing;
+
+ unsigned long pending_events;
+ struct xpon_state notify_old;
+ struct work_struct notify_work;
+ struct blocking_notifier_head notifier;
+
+ struct led_classdev *pon_led;
+ struct led_classdev *los_led;
+ struct led_classdev *fiber_led;
+
+ void *omci;
+ void *oam;
+};
+
+extern struct class *xpon_class;
+
+int xpon_sysfs_init(void);
+void xpon_sysfs_exit(void);
+int xpon_sysfs_register(struct xpon_device *xpon);
+void xpon_sysfs_unregister(struct xpon_device *xpon);
+void xpon_sysfs_notify(struct xpon_device *xpon, unsigned long changed);
+
+void xpon_leds_update(struct xpon_device *xpon,
+ const struct xpon_state *state);
+
+int xpon_genl_init(void);
+void xpon_genl_exit(void);
+void xpon_genl_notify(struct xpon_device *xpon, unsigned long changed);
+
+struct xpon_device *xpon_device_find_by_ifindex(u32 ifindex);
+void xpon_device_put(struct xpon_device *xpon);
+
+#endif /* _NET_XPON_INTERNAL_H */
diff --git a/net/xpon/leds.c b/net/xpon/leds.c
new file mode 100644
index 000000000000..57059e108690
--- /dev/null
+++ b/net/xpon/leds.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/leds.h>
+
+#include "internal.h"
+
+static void xpon_led_set(struct led_classdev *led, enum led_brightness value)
+{
+ if (led)
+ led_set_brightness(led, value);
+}
+
+void xpon_leds_update(struct xpon_device *xpon,
+ const struct xpon_state *state)
+{
+ unsigned long delay_on = 250;
+ unsigned long delay_off = 250;
+
+ if ((state->valid & XPON_STATE_F_LOS) && state->los) {
+ xpon_led_set(xpon->fiber_led, LED_OFF);
+ xpon_led_set(xpon->los_led, LED_FULL);
+ xpon_led_set(xpon->pon_led, LED_OFF);
+ return;
+ }
+
+ if (state->valid & XPON_STATE_F_SIGNAL)
+ xpon_led_set(xpon->fiber_led,
+ state->signal_detect ? LED_FULL : LED_OFF);
+ xpon_led_set(xpon->los_led, LED_OFF);
+
+ switch (state->registration) {
+ case XPON_REGISTRATION_OPERATIONAL:
+ xpon_led_set(xpon->pon_led, LED_FULL);
+ break;
+ case XPON_REGISTRATION_DISCOVERY:
+ case XPON_REGISTRATION_REGISTERING:
+ if (xpon->pon_led)
+ led_blink_set(xpon->pon_led, &delay_on, &delay_off);
+ break;
+ default:
+ xpon_led_set(xpon->pon_led, LED_OFF);
+ break;
+ }
+}
diff --git a/net/xpon/oam/Makefile b/net/xpon/oam/Makefile
new file mode 100644
index 000000000000..09d83f948771
--- /dev/null
+++ b/net/xpon/oam/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_XPON_OAM) += xpon-oam.o
+xpon-oam-y := core.o wire.o sysfs.o
diff --git a/net/xpon/oam/core.c b/net/xpon/oam/core.c
new file mode 100644
index 000000000000..9d8d4c5fe473
--- /dev/null
+++ b/net/xpon/oam/core.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/skbuff.h>
+#include <net/xpon.h>
+#include <net/xpon/oam.h>
+
+#include "../internal.h"
+#include "internal.h"
+
+struct xpon_oam *xpon_oam_register(struct xpon_device *xpon,
+ const struct xpon_oam_ops *ops,
+ void *priv)
+{
+ struct xpon_oam *oam;
+ int ret;
+
+ if (!xpon || !ops || !ops->xmit)
+ return ERR_PTR(-EINVAL);
+ if (xpon->oam)
+ return ERR_PTR(-EBUSY);
+
+ oam = kzalloc(sizeof(*oam), GFP_KERNEL);
+ if (!oam)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&oam->lock);
+ oam->xpon = xpon;
+ oam->ops = ops;
+ oam->priv = priv;
+
+ ret = xpon_oam_sysfs_register(oam);
+ if (ret) {
+ kfree(oam);
+ return ERR_PTR(ret);
+ }
+
+ xpon->oam = oam;
+ xpon_device_report_event(xpon, XPON_EVENT_OAM);
+ return oam;
+}
+EXPORT_SYMBOL_GPL(xpon_oam_register);
+
+void xpon_oam_unregister(struct xpon_oam *oam)
+{
+ if (!oam)
+ return;
+
+ if (oam->xpon && oam->xpon->oam == oam)
+ oam->xpon->oam = NULL;
+ xpon_oam_sysfs_unregister(oam);
+ kfree(oam);
+}
+EXPORT_SYMBOL_GPL(xpon_oam_unregister);
+
+void *xpon_oam_priv(struct xpon_oam *oam)
+{
+ return oam ? oam->priv : NULL;
+}
+EXPORT_SYMBOL_GPL(xpon_oam_priv);
+
+void xpon_oam_llid_registered(struct xpon_oam *oam, u8 index, u16 llid)
+{
+ if (!oam || index >= XPON_OAM_MAX_LLID)
+ return;
+
+ mutex_lock(&oam->lock);
+ oam->llids[index].llid = llid;
+ oam->llids[index].registered = true;
+ memset(&oam->llids[index].remote, 0,
+ sizeof(oam->llids[index].remote));
+ mutex_unlock(&oam->lock);
+ xpon_device_report_event(oam->xpon, XPON_EVENT_LLID | XPON_EVENT_OAM);
+}
+EXPORT_SYMBOL_GPL(xpon_oam_llid_registered);
+
+void xpon_oam_llid_unregistered(struct xpon_oam *oam, u8 index)
+{
+ if (!oam || index >= XPON_OAM_MAX_LLID)
+ return;
+
+ mutex_lock(&oam->lock);
+ memset(&oam->llids[index], 0, sizeof(oam->llids[index]));
+ mutex_unlock(&oam->lock);
+ xpon_device_report_event(oam->xpon, XPON_EVENT_LLID | XPON_EVENT_OAM);
+}
+EXPORT_SYMBOL_GPL(xpon_oam_llid_unregistered);
+
+void xpon_oam_receive(struct xpon_oam *oam, struct sk_buff *skb,
+ u8 llid_index)
+{
+ if (!oam || !skb || llid_index >= XPON_OAM_MAX_LLID) {
+ kfree_skb(skb);
+ return;
+ }
+
+ if (!oam->llids[llid_index].registered) {
+ kfree_skb(skb);
+ return;
+ }
+
+ oam->llids[llid_index].rx_oampdu++;
+ if (xpon_oam_wire_receive(oam, skb, llid_index))
+ kfree_skb(skb);
+}
+EXPORT_SYMBOL_GPL(xpon_oam_receive);
diff --git a/net/xpon/oam/internal.h b/net/xpon/oam/internal.h
new file mode 100644
index 000000000000..5b3d21ce25c2
--- /dev/null
+++ b/net/xpon/oam/internal.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _NET_XPON_OAM_INTERNAL_H
+#define _NET_XPON_OAM_INTERNAL_H
+
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <net/xpon/oam.h>
+
+#define XPON_OAM_SUBTYPE 0x03
+
+enum xpon_oam_code {
+ XPON_OAM_CODE_INFO = 0x00,
+ XPON_OAM_CODE_EVENT = 0x01,
+ XPON_OAM_CODE_VAR_REQ = 0x02,
+ XPON_OAM_CODE_VAR_RESP = 0x03,
+ XPON_OAM_CODE_LOOPBACK = 0x04,
+ XPON_OAM_CODE_ORG_SPECIFIC = 0xfe,
+};
+
+struct xpon_oam_hdr {
+ u8 subtype;
+ __be16 flags;
+ u8 code;
+} __packed;
+
+struct xpon_oam_info {
+ u8 version;
+ u16 revision;
+ u8 state;
+ u8 config;
+ u16 pdu_config;
+ u8 oui[3];
+ u32 vendor_info;
+ bool valid;
+};
+
+struct xpon_oam_llid {
+ u16 llid;
+ bool registered;
+ struct xpon_oam_info remote;
+ u64 rx_oampdu;
+ u64 tx_oampdu;
+};
+
+struct xpon_oam {
+ struct xpon_device *xpon;
+ const struct xpon_oam_ops *ops;
+ void *priv;
+ struct mutex lock;
+ struct xpon_oam_llid llids[XPON_OAM_MAX_LLID];
+};
+
+int xpon_oam_wire_receive(struct xpon_oam *oam, struct sk_buff *skb,
+ u8 llid_index);
+int xpon_oam_sysfs_register(struct xpon_oam *oam);
+void xpon_oam_sysfs_unregister(struct xpon_oam *oam);
+
+#endif /* _NET_XPON_OAM_INTERNAL_H */
diff --git a/net/xpon/oam/sysfs.c b/net/xpon/oam/sysfs.c
new file mode 100644
index 000000000000..05cef1c3d4ee
--- /dev/null
+++ b/net/xpon/oam/sysfs.c
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <net/xpon.h>
+
+#include "../internal.h"
+#include "internal.h"
+
+static ssize_t enabled_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct xpon_device *xpon = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", !!xpon->oam);
+}
+static DEVICE_ATTR_RO(enabled);
+
+static struct attribute *xpon_oam_attrs[] = {
+ &dev_attr_enabled.attr,
+ NULL,
+};
+
+static const struct attribute_group xpon_oam_group = {
+ .name = "oam",
+ .attrs = xpon_oam_attrs,
+};
+
+int xpon_oam_sysfs_register(struct xpon_oam *oam)
+{
+ return sysfs_create_group(&oam->xpon->class_dev->kobj, &xpon_oam_group);
+}
+
+void xpon_oam_sysfs_unregister(struct xpon_oam *oam)
+{
+ if (oam->xpon && oam->xpon->class_dev)
+ sysfs_remove_group(&oam->xpon->class_dev->kobj, &xpon_oam_group);
+}
diff --git a/net/xpon/oam/wire.c b/net/xpon/oam/wire.c
new file mode 100644
index 000000000000..ddfb66d44928
--- /dev/null
+++ b/net/xpon/oam/wire.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* IEEE 802.3ah OAMPDU wire handling. */
+
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/unaligned.h>
+
+#include "internal.h"
+
+#define XPON_OAM_INFO_TLV 0x01
+#define XPON_OAM_INFO_TLV_LEN 16
+#define XPON_OAM_END_TLV 0x00
+
+static int xpon_oam_parse_information(struct xpon_oam *oam,
+ struct sk_buff *skb, u8 llid_index)
+{
+ struct xpon_oam_info remote = {};
+ const u8 *p;
+
+ if (skb->len < sizeof(struct xpon_oam_hdr) + 2)
+ return -EINVAL;
+
+ p = skb->data + sizeof(struct xpon_oam_hdr);
+ while (p + 2 <= skb_tail_pointer(skb)) {
+ u8 type = p[0];
+ u8 len = p[1];
+
+ if (type == XPON_OAM_END_TLV)
+ break;
+ if (len < 2 || p + len > skb_tail_pointer(skb))
+ return -EINVAL;
+ if (type == XPON_OAM_INFO_TLV && len >= XPON_OAM_INFO_TLV_LEN) {
+ remote.version = p[2];
+ remote.revision = get_unaligned_be16(p + 3);
+ remote.state = p[5];
+ remote.config = p[6];
+ remote.pdu_config = get_unaligned_be16(p + 7);
+ memcpy(remote.oui, p + 9, sizeof(remote.oui));
+ remote.vendor_info = get_unaligned_be32(p + 12);
+ remote.valid = true;
+ }
+ p += len;
+ }
+
+ if (remote.valid) {
+ mutex_lock(&oam->lock);
+ oam->llids[llid_index].remote = remote;
+ mutex_unlock(&oam->lock);
+ }
+ return 0;
+}
+
+int xpon_oam_wire_receive(struct xpon_oam *oam, struct sk_buff *skb,
+ u8 llid_index)
+{
+ struct xpon_oam_hdr *hdr;
+
+ if (!pskb_may_pull(skb, sizeof(*hdr)))
+ return -EINVAL;
+ hdr = (struct xpon_oam_hdr *)skb->data;
+ if (hdr->subtype != XPON_OAM_SUBTYPE)
+ return -EPROTO;
+
+ switch (hdr->code) {
+ case XPON_OAM_CODE_INFO:
+ return xpon_oam_parse_information(oam, skb, llid_index);
+ case XPON_OAM_CODE_EVENT:
+ case XPON_OAM_CODE_VAR_REQ:
+ case XPON_OAM_CODE_VAR_RESP:
+ case XPON_OAM_CODE_LOOPBACK:
+ case XPON_OAM_CODE_ORG_SPECIFIC:
+ return -EOPNOTSUPP;
+ default:
+ return -EPROTO;
+ }
+}
diff --git a/net/xpon/omci/Kconfig b/net/xpon/omci/Kconfig
new file mode 100644
index 000000000000..4a3a9609aedf
--- /dev/null
+++ b/net/xpon/omci/Kconfig
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+# OMCI configuration is exposed by net/xpon/Kconfig.
diff --git a/net/xpon/omci/Makefile b/net/xpon/omci/Makefile
new file mode 100644
index 000000000000..5fe45f7ad04a
--- /dev/null
+++ b/net/xpon/omci/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_XPON_OMCI) += omci.o
+omci-y := core.o agent.o me.o wire.o identity.o profiles.o sysfs.o
diff --git a/net/xpon/omci/agent.c b/net/xpon/omci/agent.c
new file mode 100644
index 000000000000..01e2691ba622
--- /dev/null
+++ b/net/xpon/omci/agent.c
@@ -0,0 +1,5036 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * In-kernel ONU Management and Control Interface agent
+ *
+ * The agent implements the baseline OMCI transaction path and owns the
+ * operational MIB. Managed entities without a dedicated hardware callback
+ * are stored as opaque baseline attribute data so that userspace can inspect,
+ * persist and amend them through Generic Netlink.
+ */
+
+#include <linux/bitmap.h>
+#include <linux/errno.h>
+#include <linux/int_log.h>
+#include <linux/jhash.h>
+#include <linux/jiffies.h>
+#include <linux/if_vlan.h>
+#include <linux/kernel.h>
+#include <linux/math.h>
+#include <linux/math64.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/unaligned.h>
+#include <net/netlink.h>
+
+#include "internal.h"
+#include "me.h"
+#include "wire.h"
+
+#define OMCI_MSG_TYPE_CREATE 4
+#define OMCI_MSG_TYPE_DELETE 6
+#define OMCI_MSG_TYPE_SET 8
+#define OMCI_MSG_TYPE_GET 9
+#define OMCI_MSG_TYPE_GET_ALL_ALARMS 11
+#define OMCI_MSG_TYPE_GET_ALL_ALARMS_NEXT 12
+#define OMCI_MSG_TYPE_MIB_UPLOAD 13
+#define OMCI_MSG_TYPE_MIB_UPLOAD_NEXT 14
+#define OMCI_MSG_TYPE_MIB_RESET 15
+#define OMCI_MSG_TYPE_ALARM_NOTIFICATION 16
+#define OMCI_MSG_TYPE_TEST 18
+#define OMCI_MSG_TYPE_START_SW_DOWNLOAD 19
+#define OMCI_MSG_TYPE_DOWNLOAD_SECTION 20
+#define OMCI_MSG_TYPE_END_SW_DOWNLOAD 21
+#define OMCI_MSG_TYPE_ACTIVATE_SW 22
+#define OMCI_MSG_TYPE_COMMIT_SW 23
+#define OMCI_MSG_TYPE_SYNC_TIME 24
+#define OMCI_MSG_TYPE_REBOOT 25
+#define OMCI_MSG_TYPE_GET_NEXT 26
+#define OMCI_MSG_TYPE_TEST_RESULT 27
+#define OMCI_MSG_TYPE_GET_CURRENT_DATA 28
+#define OMCI_MSG_TYPE_SET_TABLE 29
+
+#define OMCI_RESULT_SUCCESS 0
+#define OMCI_RESULT_PROCESSING_ERROR 1
+#define OMCI_RESULT_NOT_SUPPORTED 2
+#define OMCI_RESULT_PARAMETER_ERROR 3
+#define OMCI_RESULT_UNKNOWN_ME 4
+#define OMCI_RESULT_UNKNOWN_INSTANCE 5
+#define OMCI_RESULT_DEVICE_BUSY 6
+#define OMCI_RESULT_INSTANCE_EXISTS 7
+#define OMCI_RESULT_ATTRIBUTE_FAILED 9
+
+#define OMCI_BRIDGE_TP_PPTP_ETH_UNI 1
+#define OMCI_BRIDGE_TP_8021P_MAPPER 3
+#define OMCI_BRIDGE_TP_GEM_IWTP 5
+#define OMCI_BRIDGE_TP_MULTICAST_GEM_IWTP 6
+#define OMCI_BRIDGE_TP_VEIP 11
+
+static void omci_agent_reset_table_snapshot_locked(struct omci_agent *agent);
+
+static bool omci_agent_result_can_be_faked(u8 result)
+{
+ switch (result) {
+ case OMCI_RESULT_NOT_SUPPORTED:
+ case OMCI_RESULT_UNKNOWN_ME:
+ case OMCI_RESULT_UNKNOWN_INSTANCE:
+ case OMCI_RESULT_INSTANCE_EXISTS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+#define OMCI_PRIORITY_QUEUE_ATTR_MASK 0xfff0
+#define OMCI_TRAFFIC_SCHEDULER_ATTR_MASK 0xf000
+
+#define OMCI_PRIORITY_QUEUE_QUEUE_CONFIG_MASK 0x8000
+#define OMCI_PRIORITY_QUEUE_MAX_SIZE_MASK 0x4000
+#define OMCI_PRIORITY_QUEUE_ALLOC_SIZE_MASK 0x2000
+#define OMCI_PRIORITY_QUEUE_DISCARD_RESET_MASK 0x1000
+#define OMCI_PRIORITY_QUEUE_DISCARD_THRESHOLD_MASK 0x0800
+#define OMCI_PRIORITY_QUEUE_RELATED_PORT_MASK 0x0400
+#define OMCI_PRIORITY_QUEUE_SCHEDULER_MASK 0x0200
+#define OMCI_PRIORITY_QUEUE_WEIGHT_MASK 0x0100
+#define OMCI_PRIORITY_QUEUE_BACKPRESSURE_OP_MASK 0x0080
+#define OMCI_PRIORITY_QUEUE_BACKPRESSURE_TIME_MASK 0x0040
+#define OMCI_PRIORITY_QUEUE_BP_OCCUR_MASK 0x0020
+#define OMCI_PRIORITY_QUEUE_BP_CLEAR_MASK 0x0010
+
+#define OMCI_TRAFFIC_SCHEDULER_TCONT_MASK 0x8000
+#define OMCI_TRAFFIC_SCHEDULER_PARENT_MASK 0x4000
+#define OMCI_TRAFFIC_SCHEDULER_POLICY_MASK 0x2000
+#define OMCI_TRAFFIC_SCHEDULER_PRIORITY_MASK 0x1000
+
+#define OMCI_DEFAULT_TCONT_COUNT 8
+#define OMCI_DEFAULT_QUEUES_PER_TCONT 1
+#define OMCI_MAX_TCONT_RESOURCES 32
+#define OMCI_MAX_QUEUES_PER_TCONT 8
+
+#define OMCI_OMCI_ME_TYPE_TABLE_MASK BIT(15)
+#define OMCI_OMCI_MESSAGE_TYPE_TABLE_MASK BIT(14)
+#define OMCI_TABLE_SNAPSHOT_TIMEOUT (60 * HZ)
+#define OMCI_BASELINE_TABLE_FRAGMENT_LEN 29
+
+#define OMCI_MANAGED_ENTITY_NAME_MASK BIT(15)
+#define OMCI_MANAGED_ENTITY_ATTR_TABLE_MASK BIT(14)
+#define OMCI_MANAGED_ENTITY_ACCESS_MASK BIT(13)
+#define OMCI_MANAGED_ENTITY_ALARMS_MASK BIT(12)
+#define OMCI_MANAGED_ENTITY_AVCS_MASK BIT(11)
+#define OMCI_MANAGED_ENTITY_ACTIONS_MASK BIT(10)
+#define OMCI_MANAGED_ENTITY_INSTANCES_MASK BIT(9)
+#define OMCI_MANAGED_ENTITY_SUPPORT_MASK BIT(8)
+#define OMCI_MANAGED_ENTITY_TABLE_MASKS \
+ (OMCI_MANAGED_ENTITY_ATTR_TABLE_MASK | \
+ OMCI_MANAGED_ENTITY_ALARMS_MASK | \
+ OMCI_MANAGED_ENTITY_AVCS_MASK | \
+ OMCI_MANAGED_ENTITY_INSTANCES_MASK)
+
+#define OMCI_ATTRIBUTE_NAME_MASK BIT(15)
+#define OMCI_ATTRIBUTE_SIZE_MASK BIT(14)
+#define OMCI_ATTRIBUTE_ACCESS_MASK BIT(13)
+#define OMCI_ATTRIBUTE_FORMAT_MASK BIT(12)
+#define OMCI_ATTRIBUTE_LOWER_LIMIT_MASK BIT(11)
+#define OMCI_ATTRIBUTE_UPPER_LIMIT_MASK BIT(10)
+#define OMCI_ATTRIBUTE_BIT_FIELD_MASK BIT(9)
+#define OMCI_ATTRIBUTE_CODE_POINTS_MASK BIT(8)
+#define OMCI_ATTRIBUTE_SUPPORT_MASK BIT(7)
+#define OMCI_ATTRIBUTE_TABLE_MASKS OMCI_ATTRIBUTE_CODE_POINTS_MASK
+
+#define OMCI_CAPABILITY_NAME_LEN 25
+#define OMCI_ATTRIBUTE_FORMAT_BIT_FIELD 2
+#define OMCI_ATTRIBUTE_FORMAT_UNSIGNED 4
+#define OMCI_ATTRIBUTE_FORMAT_STRING 5
+#define OMCI_ATTRIBUTE_FORMAT_TABLE 7
+
+#define OMCI_ONU_G_DYING_GASP_ALARM 7
+#define OMCI_ALARM_BITMAP_LEN 28
+#define OMCI_ALARM_SEQUENCE_OFFSET (8 + OMCI_ALARM_BITMAP_LEN)
+
+#define OMCI_ANI_G_ENTITY_ID 0x8001
+#define OMCI_ETHERNET_SLOT 0x01
+#define OMCI_VEIP_SLOT 0x0a
+#define OMCI_GPON_SLOT 0x80
+#define OMCI_ETHERNET_UNIT_TYPE 47
+#define OMCI_VEIP_UNIT_TYPE 48
+#define OMCI_GPON_UNIT_TYPE 248
+#define OMCI_EQUIPMENT_ENTITY_ID(_slot) (0x0100 | (_slot))
+#define OMCI_UNI_ENTITY_ID(_slot, _port) (((_slot) << 8) | (_port))
+#define OMCI_ANI_G_RX_LEVEL_MASK 0x0040
+#define OMCI_ANI_G_TX_LEVEL_MASK 0x0004
+#define OMCI_ANI_G_OPTICAL_LEVEL_MASK (OMCI_ANI_G_RX_LEVEL_MASK | \
+ OMCI_ANI_G_TX_LEVEL_MASK)
+
+#define OMCI_TEST_TYPE_UNSUPPORTED 0
+#define OMCI_TEST_TYPE_VOLTAGE 1
+#define OMCI_TEST_TYPE_RX_POWER 3
+#define OMCI_TEST_TYPE_TX_POWER 5
+#define OMCI_TEST_TYPE_BIAS 9
+#define OMCI_TEST_TYPE_TEMPERATURE 12
+
+#define OMCI_OPTICAL_LEVEL_SCALE 500
+#define OMCI_NW_TO_DBM_0P002_OFFSET 30000
+#define OMCI_NW_TO_DBUW_0P002_OFFSET 15000
+
+#define OMCI_VLAN_FILTER_LIST_MASK 0x8000
+#define OMCI_VLAN_FILTER_FORWARD_MASK 0x4000
+#define OMCI_VLAN_FILTER_COUNT_MASK 0x2000
+
+#define OMCI_EXT_VLAN_ASSOC_TYPE_MASK 0x8000
+#define OMCI_EXT_VLAN_MAX_SIZE_MASK 0x4000
+#define OMCI_EXT_VLAN_INPUT_TPID_MASK 0x2000
+#define OMCI_EXT_VLAN_OUTPUT_TPID_MASK 0x1000
+#define OMCI_EXT_VLAN_DOWNSTREAM_MASK 0x0800
+#define OMCI_EXT_VLAN_TABLE_MASK 0x0400
+#define OMCI_EXT_VLAN_ASSOC_PTR_MASK 0x0200
+#define OMCI_EXT_VLAN_DSCP_MAP_MASK 0x0100
+
+#define OMCI_OLT_G_VENDOR_ID_MASK 0x8000
+#define OMCI_OLT_G_EQUIPMENT_ID_MASK 0x4000
+#define OMCI_OLT_G_VERSION_MASK 0x2000
+#define OMCI_OMCC_VERSION_G988_2010_BASELINE 0xa0
+#define OMCI_OLT_G_TIME_OF_DAY_MASK 0x1000
+#define OMCI_OLT_G_TIME_OF_DAY_LEN 14
+
+#define OMCI_ONU_G_ATTR_MASK GENMASK(15, 3)
+#define OMCI_ONU2_G_ATTR_MASK GENMASK(15, 2)
+
+#define OMCI_ATTR_VALUE_ZERO U8_MAX
+
+struct omci_get_attr_layout {
+ u16 mask;
+ u8 offset;
+ u8 len;
+};
+
+static const struct omci_get_attr_layout omci_onu_g_attr_layout[] = {
+ { BIT(15), 0, 4 }, /* Vendor ID */
+ { BIT(14), 4, 14 }, /* Version */
+ { BIT(13), 18, 8 }, /* Serial number */
+ { BIT(12), 26, 1 }, /* Traffic management option */
+ { BIT(11), 27, 1 }, /* Deprecated */
+ { BIT(10), 28, 1 }, /* Battery backup */
+ { BIT(9), 29, 1 }, /* Administrative state */
+ { BIT(8), 30, 1 }, /* Operational state */
+ { BIT(7), 31, 1 }, /* ONU survival time */
+ { BIT(6), OMCI_ATTR_VALUE_ZERO, 24 }, /* Logical ONU ID */
+ { BIT(5), OMCI_ATTR_VALUE_ZERO, 12 }, /* Logical password */
+ { BIT(4), OMCI_ATTR_VALUE_ZERO, 1 }, /* Credentials status */
+ { BIT(3), OMCI_ATTR_VALUE_ZERO, 1 }, /* Extended TC layer */
+};
+
+static const struct omci_get_attr_layout omci_onu2_g_attr_layout[] = {
+ { BIT(15), 0, 20 }, /* Equipment ID */
+ { BIT(14), 20, 1 }, /* OMCC version */
+ { BIT(13), 21, 2 }, /* Vendor product code */
+ { BIT(12), 23, 1 }, /* Security capability */
+ { BIT(11), 24, 1 }, /* Security mode */
+ { BIT(10), 25, 2 }, /* Total priority queues */
+ { BIT(9), 27, 1 }, /* Total traffic schedulers */
+ { BIT(8), 28, 1 }, /* Deprecated */
+ { BIT(7), 29, 2 }, /* Total GEM port IDs */
+ { BIT(6), OMCI_ATTR_VALUE_ZERO, 4 }, /* System uptime */
+ { BIT(5), OMCI_ATTR_VALUE_ZERO, 2 }, /* Connectivity capability */
+ { BIT(4), 31, 1 }, /* Connectivity mode */
+ { BIT(3), OMCI_ATTR_VALUE_ZERO, 2 }, /* QoS configuration */
+ { BIT(2), OMCI_ATTR_VALUE_ZERO, 2 }, /* Priority queue scale */
+};
+
+static const u8 *omci_olt_g_consume(const u8 **data, size_t *len,
+ size_t size)
+{
+ const u8 *value;
+
+ if (*len < size)
+ return NULL;
+ value = *data;
+ *data += size;
+ *len -= size;
+
+ return value;
+}
+
+static void omci_olt_g_copy_string(char *dest, size_t dest_len,
+ const u8 *source, size_t source_len)
+{
+ size_t len = min(source_len, dest_len - 1);
+
+ memcpy(dest, source, len);
+ dest[len] = '\0';
+}
+
+static int omci_olt_g_parse_set(struct omci_mib_object *object, u16 mask,
+ const u8 *data, size_t len)
+{
+ struct omci_olt_g *olt = &object->olt_g;
+ const u8 *value;
+
+ if (mask & OMCI_OLT_G_VENDOR_ID_MASK) {
+ value = omci_olt_g_consume(&data, &len,
+ OMCI_OLT_VENDOR_ID_LEN);
+ if (!value)
+ return -EINVAL;
+ omci_olt_g_copy_string(olt->vendor_id, sizeof(olt->vendor_id),
+ value, OMCI_OLT_VENDOR_ID_LEN);
+ olt->vendor_id_valid = true;
+ }
+ if (mask & OMCI_OLT_G_EQUIPMENT_ID_MASK) {
+ value = omci_olt_g_consume(&data, &len,
+ OMCI_OLT_EQUIPMENT_ID_LEN);
+ if (!value)
+ return -EINVAL;
+ omci_olt_g_copy_string(olt->equipment_id,
+ sizeof(olt->equipment_id), value,
+ OMCI_OLT_EQUIPMENT_ID_LEN);
+ olt->equipment_id_valid = true;
+ }
+ if (mask & OMCI_OLT_G_VERSION_MASK) {
+ value = omci_olt_g_consume(&data, &len, OMCI_OLT_VERSION_LEN);
+ if (!value)
+ return -EINVAL;
+ omci_olt_g_copy_string(olt->version, sizeof(olt->version),
+ value, OMCI_OLT_VERSION_LEN);
+ olt->version_valid = true;
+ }
+ if (mask & OMCI_OLT_G_TIME_OF_DAY_MASK) {
+ value = omci_olt_g_consume(&data, &len,
+ OMCI_OLT_G_TIME_OF_DAY_LEN);
+ if (!value)
+ return -EINVAL;
+ }
+ olt->valid = olt->vendor_id_valid || olt->equipment_id_valid ||
+ olt->version_valid;
+
+ return 0;
+}
+
+static void omci_vlan_filter_parse_entries(struct
omci_vlan_tagging_filter *filter,
+ const u8 *data)
+{
+ unsigned int i;
+
+ for (i = 0; i < OMCI_VLAN_FILTER_MAX_ENTRIES; i++) {
+ u16 tci = get_unaligned_be16(data + i * sizeof(tci));
+
+ filter->entries[i].tci = tci;
+ filter->entries[i].vid = tci & GENMASK(11, 0);
+ filter->entries[i].dei = (tci >> 12) & 0x1;
+ filter->entries[i].pbit = (tci >> 13) & 0x7;
+ }
+}
+
+static void omci_vlan_filter_parse_create(struct omci_mib_object *object,
+ const u8 *content)
+{
+ struct omci_vlan_tagging_filter *filter = &object->vlan_filter;
+
+ omci_vlan_filter_parse_entries(filter, content);
+ filter->forward_operation = content[24];
+ filter->num_entries = min_t(u8, content[25],
+ OMCI_VLAN_FILTER_MAX_ENTRIES);
+ filter->valid = true;
+}
+
+static int omci_vlan_filter_parse_set(struct omci_mib_object *object,
+ u16 mask, const u8 *data,
+ size_t len)
+{
+ struct omci_vlan_tagging_filter *filter = &object->vlan_filter;
+
+ if (mask & OMCI_VLAN_FILTER_LIST_MASK) {
+ if (len < 24)
+ return -EINVAL;
+ omci_vlan_filter_parse_entries(filter, data);
+ data += 24;
+ len -= 24;
+ }
+ if (mask & OMCI_VLAN_FILTER_FORWARD_MASK) {
+ if (!len)
+ return -EINVAL;
+ filter->forward_operation = *data++;
+ len--;
+ }
+ if (mask & OMCI_VLAN_FILTER_COUNT_MASK) {
+ if (!len)
+ return -EINVAL;
+ filter->num_entries = min_t(u8, *data,
+ OMCI_VLAN_FILTER_MAX_ENTRIES);
+ }
+ filter->valid = true;
+
+ return 0;
+}
+
+static bool omci_ext_vlan_rule_is_delete(const u8 *raw)
+{
+ unsigned int i;
+
+ for (i = 8; i < OMCI_EXT_VLAN_RULE_LEN; i++)
+ if (raw[i] != 0xff)
+ return false;
+
+ return true;
+}
+
+static void omci_ext_vlan_parse_rule(struct omci_extended_vlan_rule *rule,
+ const u8 *raw)
+{
+ u32 word;
+
+ memcpy(rule->raw, raw, sizeof(rule->raw));
+ word = get_unaligned_be32(raw);
+ rule->filter_outer_pbit = (word >> 28) & 0xf;
+ rule->filter_outer_vid = (word >> 15) & 0x1fff;
+ rule->filter_outer_tpid_dei = (word >> 12) & 0x7;
+
+ word = get_unaligned_be32(raw + 4);
+ rule->filter_inner_pbit = (word >> 28) & 0xf;
+ rule->filter_inner_vid = (word >> 15) & 0x1fff;
+ rule->filter_inner_tpid_dei = (word >> 12) & 0x7;
+ rule->filter_ethertype = word & 0xf;
+
+ word = get_unaligned_be32(raw + 8);
+ rule->tags_to_remove = (word >> 30) & 0x3;
+ rule->treat_outer_pbit = (word >> 16) & 0xf;
+ rule->treat_outer_vid = (word >> 3) & 0x1fff;
+ rule->treat_outer_tpid_dei = word & 0x7;
+
+ word = get_unaligned_be32(raw + 12);
+ rule->treat_inner_pbit = (word >> 16) & 0xf;
+ rule->treat_inner_vid = (word >> 3) & 0x1fff;
+ rule->treat_inner_tpid_dei = word & 0x7;
+ rule->delete = omci_ext_vlan_rule_is_delete(raw);
+}
+
+static int omci_ext_vlan_update_rule(struct omci_extended_vlan *vlan,
+ const u8 *raw)
+{
+ unsigned int index;
+ bool delete;
+
+ delete = omci_ext_vlan_rule_is_delete(raw);
+ for (index = 0; index < vlan->rule_count; index++)
+ if (!memcmp(vlan->rules[index].raw, raw, 8))
+ break;
+
+ if (delete) {
+ if (index == vlan->rule_count)
+ return 0;
+ memmove(&vlan->rules[index], &vlan->rules[index + 1],
+ (vlan->rule_count - index - 1) * sizeof(vlan->rules[0]));
+ vlan->rule_count--;
+ memset(&vlan->rules[vlan->rule_count], 0,
+ sizeof(vlan->rules[0]));
+ return 0;
+ }
+
+ if (index == vlan->rule_count) {
+ if (vlan->rule_count >= OMCI_EXT_VLAN_MAX_RULES)
+ return -ENOSPC;
+ vlan->rule_count++;
+ }
+ omci_ext_vlan_parse_rule(&vlan->rules[index], raw);
+
+ return 0;
+}
+
+static void omci_ext_vlan_parse_create(struct omci_mib_object *object,
+ const u8 *content)
+{
+ struct omci_extended_vlan *vlan = &object->extended_vlan;
+
+ vlan->association_type = content[0];
+ vlan->associated_me = get_unaligned_be16(content + 24);
+ vlan->max_table_size = OMCI_EXT_VLAN_MAX_RULES;
+ vlan->valid = true;
+}
+
+static const u8 *omci_ext_vlan_consume(const u8 **data, size_t *len,
+ size_t size)
+{
+ const u8 *value;
+
+ if (*len < size)
+ return NULL;
+ value = *data;
+ *data += size;
+ *len -= size;
+
+ return value;
+}
+
+static int omci_ext_vlan_parse_set(struct omci_mib_object *object, u16 mask,
+ const u8 *data, size_t len)
+{
+ struct omci_extended_vlan *vlan = &object->extended_vlan;
+
+ if (mask & OMCI_EXT_VLAN_ASSOC_TYPE_MASK) {
+ const u8 *value = omci_ext_vlan_consume(&data, &len, 1);
+
+ if (!value)
+ return -EINVAL;
+ vlan->association_type = value[0];
+ }
+ if (mask & OMCI_EXT_VLAN_MAX_SIZE_MASK) {
+ const u8 *value = omci_ext_vlan_consume(&data, &len, 2);
+
+ if (!value)
+ return -EINVAL;
+ vlan->max_table_size = get_unaligned_be16(value);
+ }
+ if (mask & OMCI_EXT_VLAN_INPUT_TPID_MASK) {
+ const u8 *value = omci_ext_vlan_consume(&data, &len, 2);
+
+ if (!value)
+ return -EINVAL;
+ vlan->input_tpid = get_unaligned_be16(value);
+ }
+ if (mask & OMCI_EXT_VLAN_OUTPUT_TPID_MASK) {
+ const u8 *value = omci_ext_vlan_consume(&data, &len, 2);
+
+ if (!value)
+ return -EINVAL;
+ vlan->output_tpid = get_unaligned_be16(value);
+ }
+ if (mask & OMCI_EXT_VLAN_DOWNSTREAM_MASK) {
+ const u8 *value = omci_ext_vlan_consume(&data, &len, 1);
+
+ if (!value)
+ return -EINVAL;
+ vlan->downstream_mode = value[0];
+ }
+ if (mask & OMCI_EXT_VLAN_TABLE_MASK) {
+ const u8 *value = omci_ext_vlan_consume(&data, &len,
+ OMCI_EXT_VLAN_RULE_LEN);
+ int ret;
+
+ if (!value)
+ return -EINVAL;
+ ret = omci_ext_vlan_update_rule(vlan, value);
+ if (ret)
+ return ret;
+ }
+ if (mask & OMCI_EXT_VLAN_ASSOC_PTR_MASK) {
+ const u8 *value = omci_ext_vlan_consume(&data, &len, 2);
+
+ if (!value)
+ return -EINVAL;
+ vlan->associated_me = get_unaligned_be16(value);
+ }
+ if (mask & OMCI_EXT_VLAN_DSCP_MAP_MASK) {
+ const u8 *value = omci_ext_vlan_consume(&data, &len,
+ sizeof(vlan->dscp_to_pbit));
+
+ if (!value)
+ return -EINVAL;
+ memcpy(vlan->dscp_to_pbit, value, sizeof(vlan->dscp_to_pbit));
+ }
+ vlan->valid = true;
+
+ return 0;
+}
+
+struct omci_attr_layout {
+ u16 mask;
+ u8 offset;
+ u8 length;
+};
+
+static const struct omci_attr_layout omci_priority_queue_layout[] = {
+ { OMCI_PRIORITY_QUEUE_QUEUE_CONFIG_MASK, 0, 1 },
+ { OMCI_PRIORITY_QUEUE_MAX_SIZE_MASK, 1, 2 },
+ { OMCI_PRIORITY_QUEUE_ALLOC_SIZE_MASK, 3, 2 },
+ { OMCI_PRIORITY_QUEUE_DISCARD_RESET_MASK, 5, 2 },
+ { OMCI_PRIORITY_QUEUE_DISCARD_THRESHOLD_MASK, 7, 2 },
+ { OMCI_PRIORITY_QUEUE_RELATED_PORT_MASK, 9, 4 },
+ { OMCI_PRIORITY_QUEUE_SCHEDULER_MASK, 13, 2 },
+ { OMCI_PRIORITY_QUEUE_WEIGHT_MASK, 15, 1 },
+ { OMCI_PRIORITY_QUEUE_BACKPRESSURE_OP_MASK, 16, 2 },
+ { OMCI_PRIORITY_QUEUE_BACKPRESSURE_TIME_MASK, 18, 4 },
+ { OMCI_PRIORITY_QUEUE_BP_OCCUR_MASK, 22, 2 },
+ { OMCI_PRIORITY_QUEUE_BP_CLEAR_MASK, 24, 2 },
+};
+
+static const struct omci_attr_layout omci_traffic_scheduler_layout[] = {
+ { OMCI_TRAFFIC_SCHEDULER_TCONT_MASK, 0, 2 },
+ { OMCI_TRAFFIC_SCHEDULER_PARENT_MASK, 2, 2 },
+ { OMCI_TRAFFIC_SCHEDULER_POLICY_MASK, 4, 1 },
+ { OMCI_TRAFFIC_SCHEDULER_PRIORITY_MASK, 5, 1 },
+};
+
+static int omci_attr_parse_set(struct omci_mib_object *object, u16 mask,
+ const u8 *data, size_t len,
+ const struct omci_attr_layout *layout,
+ size_t layout_count, u16 supported_mask)
+{
+ size_t i;
+
+ if (mask & ~supported_mask)
+ return -EINVAL;
+
+ for (i = 0; i < layout_count; i++) {
+ if (!(mask & layout[i].mask))
+ continue;
+ if (len < layout[i].length)
+ return -EINVAL;
+ memcpy(object->data + layout[i].offset, data,
+ layout[i].length);
+ data += layout[i].length;
+ len -= layout[i].length;
+ }
+
+ return 0;
+}
+
+static int omci_attr_serialize(const struct omci_mib_object *object, u16 mask,
+ u8 *data, size_t len, size_t *encoded_len,
+ const struct omci_attr_layout *layout,
+ size_t layout_count, u16 supported_mask)
+{
+ size_t used = 0;
+ size_t i;
+
+ if (mask & ~supported_mask)
+ return -EINVAL;
+
+ for (i = 0; i < layout_count; i++) {
+ if (!(mask & layout[i].mask))
+ continue;
+ if (len - used < layout[i].length)
+ return -ENOSPC;
+ memcpy(data + used, object->data + layout[i].offset,
+ layout[i].length);
+ used += layout[i].length;
+ }
+
+ *encoded_len = used;
+ return 0;
+}
+
+static int
+omci_priority_queue_serialize(const struct omci_mib_object *object,
+ u16 mask, u8 *data, size_t len,
+ size_t *encoded_len)
+{
+ return omci_attr_serialize(object, mask, data, len, encoded_len,
+ omci_priority_queue_layout,
+ ARRAY_SIZE(omci_priority_queue_layout),
+ OMCI_PRIORITY_QUEUE_ATTR_MASK);
+}
+
+static int
+omci_traffic_scheduler_serialize(const struct omci_mib_object *object,
+ u16 mask, u8 *data, size_t len,
+ size_t *encoded_len)
+{
+ return omci_attr_serialize(object, mask, data, len, encoded_len,
+ omci_traffic_scheduler_layout,
+ ARRAY_SIZE(omci_traffic_scheduler_layout),
+ OMCI_TRAFFIC_SCHEDULER_ATTR_MASK);
+}
+
+static int omci_mib_parse_create(struct omci_mib_object *object,
+ const u8 *content)
+{
+ switch (object->class_id) {
+ case OMCI_CLASS_VLAN_TAGGING_FILTER_DATA:
+ omci_vlan_filter_parse_create(object, content);
+ break;
+ case OMCI_CLASS_EXTENDED_VLAN:
+ omci_ext_vlan_parse_create(object, content);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int omci_mib_parse_set(struct omci_mib_object *object, u16 mask,
+ const u8 *data, size_t len)
+{
+ switch (object->class_id) {
+ case OMCI_CLASS_OLT_G:
+ return omci_olt_g_parse_set(object, mask, data, len);
+ case OMCI_CLASS_VLAN_TAGGING_FILTER_DATA:
+ return omci_vlan_filter_parse_set(object, mask, data, len);
+ case OMCI_CLASS_EXTENDED_VLAN:
+ return omci_ext_vlan_parse_set(object, mask, data, len);
+ case OMCI_CLASS_PRIORITY_QUEUE:
+ return omci_attr_parse_set(object, mask, data, len,
+ omci_priority_queue_layout,
+ ARRAY_SIZE(omci_priority_queue_layout),
+ OMCI_PRIORITY_QUEUE_ATTR_MASK);
+ case OMCI_CLASS_TRAFFIC_SCHEDULER:
+ return omci_attr_parse_set(object, mask, data, len,
+ omci_traffic_scheduler_layout,
+ ARRAY_SIZE(omci_traffic_scheduler_layout),
+ OMCI_TRAFFIC_SCHEDULER_ATTR_MASK);
+ default:
+ return 0;
+ }
+}
+
+static unsigned long omci_mib_key(u16 class_id, u16 entity_id)
+{
+ return ((unsigned long)class_id << 16) | entity_id;
+}
+
+static struct omci_mib_object *
+omci_mib_lookup(struct omci_agent *agent, u16 class_id, u16 entity_id)
+{
+ struct omci_mib_object *object;
+
+ object = xa_load(&agent->mib, omci_mib_key(class_id, entity_id));
+ if (object && object->pending_delete)
+ return NULL;
+
+ return object;
+}
+
+static void omci_agent_set_mib_sync_locked(struct omci_agent *agent, u8 sync)
+{
+ struct omci_mib_object *object;
+
+ agent->mib_sync = sync;
+ object = omci_mib_lookup(agent, OMCI_CLASS_ONU_DATA, 0);
+ if (object)
+ object->data[0] = sync;
+}
+
+static void omci_agent_increment_mib_sync_locked(struct omci_agent *agent)
+{
+ omci_agent_set_mib_sync_locked(agent, (u8)(agent->mib_sync + 1));
+}
+
+static int omci_mib_store_locked(struct omci_agent *agent,
+ const struct omci_mib_object *source)
+{
+ struct omci_mib_object *object;
+ void *old;
+
+ object = kmemdup(source, sizeof(*object), GFP_KERNEL);
+ if (!object)
+ return -ENOMEM;
+
+ old = xa_store(&agent->mib,
+ omci_mib_key(source->class_id, source->entity_id),
+ object, GFP_KERNEL);
+ if (xa_is_err(old)) {
+ kfree(object);
+ return xa_err(old);
+ }
+ kfree(old);
+ return 0;
+}
+
+static struct omci_olt_g *omci_agent_olt_g_locked(struct omci_agent *agent)
+{
+ struct omci_mib_object *object;
+
+ object = omci_mib_lookup(agent, OMCI_CLASS_OLT_G, 0);
+ if (!object || !object->olt_g.valid)
+ return NULL;
+
+ return &object->olt_g;
+}
+
+int omci_agent_olt_g_get(struct omci_device *odev, struct omci_olt_g *olt)
+{
+ struct omci_olt_g *stored;
+ int ret = 0;
+
+ if (!olt)
+ return -EINVAL;
+
+ mutex_lock(&odev->agent.lock);
+ stored = omci_agent_olt_g_locked(&odev->agent);
+ if (!stored)
+ ret = -ENODATA;
+ else
+ *olt = *stored;
+ mutex_unlock(&odev->agent.lock);
+
+ return ret;
+}
+
+static void
+omci_agent_profile_state_locked(struct omci_agent *agent,
+ const struct omci_olt_g *olt,
+ struct omci_olt_profile_state *state)
+{
+ u8 effective;
+
+ memset(state, 0, sizeof(*state));
+ state->configured = agent->config.olt_profile;
+ state->forced = agent->config.olt_profile_force;
+
+ if (state->forced != OMCI_OLT_PROFILE_UNSPEC)
+ effective = state->forced;
+ else if (state->configured == OMCI_OLT_PROFILE_AUTO)
+ effective = omci_profile_detect(olt);
+ else
+ effective = state->configured;
+
+ if (effective == OMCI_OLT_PROFILE_UNSPEC ||
+ effective == OMCI_OLT_PROFILE_AUTO)
+ effective = OMCI_OLT_PROFILE_GENERIC;
+
+ state->effective = effective;
+ state->quirks = omci_profile_quirks(effective);
+ if (olt)
+ state->olt = *olt;
+}
+
+static int
+omci_agent_profile_reconcile_locked(struct omci_device *odev,
+ u8 old_profile, u8 new_profile);
+static int omci_agent_clear_services_locked(struct omci_device *odev);
+static int omci_agent_reconcile_services_locked(struct omci_device *odev);
+static void omci_agent_reset_duplicate_locked(struct omci_agent *agent);
+
+static void omci_agent_free_object_array(struct xarray *objects)
+{
+ struct omci_mib_object *object;
+ unsigned long index;
+
+ xa_for_each(objects, index, object) {
+ xa_erase(objects, index);
+ kfree(object);
+ }
+}
+
+static int omci_agent_snapshot_vendor_objects_locked(struct omci_agent *agent,
+ struct xarray *snapshot)
+{
+ struct omci_mib_object *object;
+ struct omci_mib_object *copy;
+ unsigned long index;
+ void *old;
+
+ xa_for_each(&agent->mib, index, object) {
+ if (!object->owner_profile)
+ continue;
+ copy = kmemdup(object, sizeof(*copy), GFP_KERNEL);
+ if (!copy)
+ return -ENOMEM;
+ old = xa_store(snapshot, index, copy, GFP_KERNEL);
+ if (xa_is_err(old)) {
+ kfree(copy);
+ return xa_err(old);
+ }
+ kfree(old);
+ }
+
+ return 0;
+}
+
+static int omci_agent_restore_vendor_objects_locked(struct omci_agent *agent,
+ struct xarray *snapshot)
+{
+ struct omci_mib_object *object;
+ unsigned long index;
+ void *old;
+ int ret = 0;
+
+ xa_for_each(&agent->mib, index, object) {
+ if (!object->owner_profile)
+ continue;
+ xa_erase(&agent->mib, index);
+ kfree(object);
+ }
+ xa_for_each(snapshot, index, object) {
+ xa_erase(snapshot, index);
+ old = xa_store(&agent->mib, index, object, GFP_KERNEL);
+ if (xa_is_err(old)) {
+ kfree(object);
+ if (!ret)
+ ret = xa_err(old);
+ continue;
+ }
+ kfree(old);
+ }
+
+ return ret;
+}
+
+static int
+omci_agent_profile_refresh_locked(struct omci_device *odev,
+ struct omci_olt_g *olt,
+ bool *profile_changed)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_olt_profile_state old_state;
+ struct omci_olt_profile_state state;
+ struct xarray snapshot;
+ u32 old_quirks = agent->profile_quirks;
+ u8 old_profile = agent->profile_effective;
+ bool changed;
+ int ret;
+
+ omci_agent_profile_state_locked(agent, olt, &state);
+ omci_profile_sanitize_olt_g(olt, state.quirks);
+ if (olt)
+ state.olt = *olt;
+
+ changed = old_profile != state.effective || old_quirks != state.quirks;
+ if (!changed)
+ goto apply_profile;
+
+ xa_init(&snapshot);
+ ret = omci_agent_snapshot_vendor_objects_locked(agent, &snapshot);
+ if (ret)
+ goto destroy_snapshot;
+ ret = omci_agent_profile_reconcile_locked(odev, old_profile,
+ state.effective);
+ if (ret)
+ goto rollback;
+
+apply_profile:
+ if (odev->ops->set_olt_profile) {
+ ret = odev->ops->set_olt_profile(odev, &state);
+ if (ret && changed)
+ goto rollback;
+ if (ret)
+ return ret;
+ }
+
+ agent->profile_effective = state.effective;
+ agent->profile_quirks = state.quirks;
+ if (profile_changed)
+ *profile_changed = changed;
+
+ if (changed) {
+ omci_agent_reset_table_snapshot_locked(agent);
+ agent->upload_index = 0;
+ omci_agent_free_object_array(&snapshot);
+ xa_destroy(&snapshot);
+ dev_info(odev->parent,
+ "OMCI OLT profile: configured=%s effective=%s forced=%s quirks=%#x\n",
+ omci_olt_profile_name(state.configured),
+ omci_olt_profile_name(state.effective),
+ omci_olt_profile_name(state.forced), state.quirks);
+ }
+
+ return 0;
+
+rollback:
+ omci_agent_restore_vendor_objects_locked(agent, &snapshot);
+ agent->profile_effective = old_profile;
+ agent->profile_quirks = old_quirks;
+ omci_agent_reconcile_services_locked(odev);
+ if (odev->ops->set_olt_profile) {
+ old_state = state;
+ old_state.effective = old_profile;
+ old_state.quirks = old_quirks;
+ odev->ops->set_olt_profile(odev, &old_state);
+ }
+ omci_agent_free_object_array(&snapshot);
+
+destroy_snapshot:
+ xa_destroy(&snapshot);
+ return ret;
+}
+
+static bool
+omci_agent_profile_has_quirk(const struct omci_agent *agent, u32 quirk)
+{
+ return agent->profile_quirks & quirk;
+}
+
+static bool
+omci_agent_fakes_unsupported(const struct omci_agent *agent)
+{
+ return agent->fake_omci ||
+ omci_agent_profile_has_quirk(agent,
+ OMCI_OLT_QUIRK_FAKE_UNSUPPORTED_SUCCESS);
+}
+
+static bool
+omci_agent_should_fake_result(const struct omci_agent *agent, u16 class_id,
+ u8 result)
+{
+ const struct omci_me_desc *desc = omci_me_lookup(agent, class_id);
+
+ if (desc && (desc->flags & OMCI_ME_F_DATAPATH))
+ return false;
+
+ return omci_agent_fakes_unsupported(agent) &&
+ omci_agent_result_can_be_faked(result);
+}
+
+static int
+omci_mib_add_default_profile_mask(struct omci_agent *agent, u8 profile,
+ u16 class_id, u16 entity_id,
+ u16 attr_mask, const void *data,
+ size_t len)
+{
+ const struct omci_me_desc *desc;
+ struct omci_mib_object *object;
+ int ret;
+
+ desc = omci_me_lookup_profile(profile, class_id);
+ object = kzalloc(sizeof(*object), GFP_KERNEL);
+ if (!object)
+ return -ENOMEM;
+
+ object->class_id = class_id;
+ object->entity_id = entity_id;
+ object->attr_mask = attr_mask;
+ object->origin = OMCI_MIB_ORIGIN_DEFAULT;
+ object->owner_profile = desc && (desc->flags & OMCI_ME_F_VENDOR) ?
+ profile : OMCI_OLT_PROFILE_UNSPEC;
+ object->data_len = desc ? desc->data_len :
+ min_t(size_t, len, sizeof(object->data));
+ if (data)
+ memcpy(object->data, data, min(len, sizeof(object->data)));
+
+ ret = omci_mib_store_locked(agent, object);
+ kfree(object);
+ return ret;
+}
+
+static int omci_mib_add_default_mask(struct omci_agent *agent, u16 class_id,
+ u16 entity_id, u16 attr_mask,
+ const void *data, size_t len)
+{
+ u8 profile = agent->profile_effective;
+
+ if (!profile)
+ profile = OMCI_OLT_PROFILE_GENERIC;
+
+ return omci_mib_add_default_profile_mask(agent, profile, class_id,
+ entity_id, attr_mask, data, len);
+}
+
+static int omci_mib_add_default(struct omci_agent *agent, u16 class_id,
+ u16 entity_id, const void *data, size_t len)
+{
+ return omci_mib_add_default_mask(agent, class_id, entity_id, 0xffff,
+ data, len);
+}
+
+static int
+omci_agent_seed_huawei_locked(struct omci_device *odev, u8 profile)
+{
+ struct omci_agent *agent = &odev->agent;
+ u8 data[OMCI_MAX_ATTR_DATA] = {};
+ int ret;
+
+ data[1] = agent->config.traffic_mgmt_option;
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_HUAWEI_FLOW_MAPPING, 0, GENMASK(15, 7),
+ data, sizeof(data));
+ if (ret)
+ return ret;
+
+ memset(data, 0, sizeof(data));
+ memcpy(data, agent->config.version, min_t(size_t, 14,
+ sizeof(agent->config.version)));
+ data[14] = 1;
+ data[15] = 1;
+ data[16] = 1;
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_HUAWEI_SW_IMAGE_EXT, 0, GENMASK(15, 11),
+ data, sizeof(data));
+ if (ret)
+ return ret;
+
+ memset(data, 0, sizeof(data));
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_HUAWEI_MULTICAST_367, 0, GENMASK(15, 11),
+ data, sizeof(data));
+ if (ret)
+ return ret;
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_HUAWEI_MULTICAST_370, 0x0101,
+ GENMASK(15, 11), data, sizeof(data));
+ if (ret)
+ return ret;
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_HUAWEI_MULTICAST_373, 0, GENMASK(15, 11),
+ data, sizeof(data));
+ if (ret)
+ return ret;
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_HUAWEI_MULTICAST_65408, 0, GENMASK(15, 11),
+ data, sizeof(data));
+ if (ret)
+ return ret;
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_HUAWEI_MULTICAST_65414, 0, GENMASK(15, 11),
+ data, sizeof(data));
+ if (ret)
+ return ret;
+
+ return omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_HUAWEI_MULTICAST_65425, 0, GENMASK(15, 11),
+ data, sizeof(data));
+}
+
+static int
+omci_agent_seed_nokia_locked(struct omci_device *odev, u8 profile)
+{
+ struct omci_agent *agent = &odev->agent;
+ u8 data[OMCI_MAX_ATTR_DATA] = {};
+ unsigned int i;
+ int ret;
+
+ data[0] = 1;
+ data[3] = 1;
+ data[6] = 1;
+ data[9] = 1;
+ data[12] = 1;
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_NOKIA_OPTICAL_SUPERVISION,
+ OMCI_ANI_G_ENTITY_ID,
+ GENMASK(15, 6), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ memset(data, 0, sizeof(data));
+ memcpy(data + 15, agent->config.vendor_id,
+ sizeof(agent->config.vendor_id));
+ memcpy(data + 19, agent->config.serial_number,
+ sizeof(agent->config.serial_number));
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_NOKIA_ONT_GENERIC_V2, 0,
+ GENMASK(15, 4), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ for (i = 0; i < agent->config.uni_count; i++) {
+ memset(data, 0, sizeof(data));
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_NOKIA_UNI_SUPPLEMENTAL_V2,
+ OMCI_UNI_ENTITY_ID(OMCI_ETHERNET_SLOT,
+ i + 1),
+ GENMASK(15, 9), data, sizeof(data));
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * In Nokia GPON HGU / VEIP mode, the OLT assigns Alloc-ID 256 and 257
+ * along with GEM ports 256 and 257 via PLOAM without OMCI Create messages.
+ * Seed the standard VEIP datapath bridge for both GEM 256 and GEM 257.
+ */
+ if (agent->config.onu_type == 2) {
+ /* T-CONT 0x8000 -> Alloc-ID 256 */
+ memset(data, 0, sizeof(data));
+ put_unaligned_be16(256, data);
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_TCONT, 0x8000,
+ BIT(15), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ /* GEM Port Network CTP 256 -> port 256, T-CONT 0x8000, bidir(3) */
+ memset(data, 0, sizeof(data));
+ put_unaligned_be16(256, data);
+ put_unaligned_be16(0x8000, data + 2);
+ data[4] = 3;
+ put_unaligned_be16(0x8000, data + 5);
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_GEM_PORT_CTP, 256,
+ GENMASK(15, 6), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ /*
+ * GEM IWTP 256 -> GEM CTP 256. Interworking option 5 is
+ * "802.1p mapper", so the service profile pointer at offset 3
+ * has to name a real class 130 instance; leaving it zero
+ * advertises mapper interworking with no mapper behind it.
+ */
+ memset(data, 0, sizeof(data));
+ put_unaligned_be16(256, data);
+ data[2] = 5;
+ put_unaligned_be16(OMCI_NOKIA_MAPPER_MGMT, data + 3);
+ put_unaligned_be16(0x0a01, data + 5);
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_GEM_IWTP, 256,
+ GENMASK(15, 8), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ /* T-CONT 0x8001 -> Alloc-ID 257 */
+ memset(data, 0, sizeof(data));
+ put_unaligned_be16(257, data);
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_TCONT, 0x8001,
+ BIT(15), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ /* GEM Port Network CTP 257 -> port 257, T-CONT 0x8001, bidir(3) */
+ memset(data, 0, sizeof(data));
+ put_unaligned_be16(257, data);
+ put_unaligned_be16(0x8001, data + 2);
+ data[4] = 3;
+ put_unaligned_be16(0x8001, data + 5);
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_GEM_PORT_CTP, 257,
+ GENMASK(15, 6), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ /* GEM IWTP 257 -> GEM CTP 257, via 802.1p mapper 2. */
+ memset(data, 0, sizeof(data));
+ put_unaligned_be16(257, data);
+ data[2] = 5;
+ put_unaligned_be16(OMCI_NOKIA_MAPPER_HSI, data + 3);
+ put_unaligned_be16(0x0a01, data + 5);
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_GEM_IWTP, 257,
+ GENMASK(15, 8), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ /*
+ * 802.1p mappers, one per GEM interworking TP. A reference ONU
+ * carrying service on this OLT points every P-bit at the same
+ * interworking TP, so do the same rather than invent a
+ * priority split the OLT never asked for.
+ *
+ * Layout (G.988 9.3.10): TP pointer at 0, then the eight P-bit
+ * interworking TP pointers, unmarked frame option at 18, DSCP
+ * map at 19, default P-bit marking at 43, TP type at 44.
+ */
+ for (i = 0; i < 2; i++) {
+ u16 mapper = i ? OMCI_NOKIA_MAPPER_HSI :
+ OMCI_NOKIA_MAPPER_MGMT;
+ u16 iwtp = i ? 257 : 256;
+ unsigned int pbit;
+
+ memset(data, 0, sizeof(data));
+ put_unaligned_be16(0xffff, data);
+ for (pbit = 0; pbit < 8; pbit++)
+ put_unaligned_be16(iwtp, data + 2 + pbit * 2);
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_8021P_MAPPER, mapper,
+ GENMASK(15, 3), data, sizeof(data));
+ if (ret)
+ return ret;
+ }
+
+ /* MAC Bridge Service Profile 1 */
+ memset(data, 0, sizeof(data));
+ data[0] = 1;
+ data[1] = 1;
+ data[2] = 1;
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_MAC_BRIDGE_SERVICE_PROFILE, 1,
+ GENMASK(15, 6), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ /* MAC Bridge Port Config 1 -> VEIP (TP type 11, slot 6) */
+ memset(data, 0, sizeof(data));
+ put_unaligned_be16(1, data);
+ data[2] = 1;
+ data[3] = 11;
+ put_unaligned_be16(OMCI_UNI_ENTITY_ID(6, 1), data + 4);
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_MAC_BRIDGE_PORT_CONFIG_DATA, 1,
+ GENMASK(15, 12), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ /*
+ * MAC Bridge Port 2 -> 802.1p mapper 2 (Internet / HSI).
+ * TP type 3 is "802.1p mapper"; binding the port straight to
+ * the GEM interworking TP with type 5 skips the mapper layer a
+ * working reference ONU on this OLT uses.
+ */
+ memset(data, 0, sizeof(data));
+ put_unaligned_be16(1, data);
+ data[2] = 2;
+ data[3] = 3;
+ put_unaligned_be16(OMCI_NOKIA_MAPPER_HSI, data + 4);
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_MAC_BRIDGE_PORT_CONFIG_DATA, 2,
+ GENMASK(15, 12), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ /* MAC Bridge Port 3 -> 802.1p mapper 1 (management / VoIP). */
+ memset(data, 0, sizeof(data));
+ put_unaligned_be16(1, data);
+ data[2] = 3;
+ data[3] = 3;
+ put_unaligned_be16(OMCI_NOKIA_MAPPER_MGMT, data + 4);
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_MAC_BRIDGE_PORT_CONFIG_DATA, 3,
+ GENMASK(15, 12), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ /*
+ * Class 84 (VLAN tagging filter data), one per mapper: VID 100
+ * for high speed internet and VID 660 for management and VoIP.
+ * data[24] is the forward operation, data[25] the number of
+ * valid entries in the filter list.
+ */
+ memset(data, 0, sizeof(data));
+ put_unaligned_be16(100, data);
+ data[24] = 0x10;
+ data[25] = 1;
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_VLAN_TAGGING_FILTER_DATA,
+ OMCI_NOKIA_MAPPER_HSI,
+ GENMASK(15, 13), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ memset(data, 0, sizeof(data));
+ put_unaligned_be16(660, data);
+ data[24] = 0x10;
+ data[25] = 1;
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_VLAN_TAGGING_FILTER_DATA,
+ OMCI_NOKIA_MAPPER_MGMT,
+ GENMASK(15, 13), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ /*
+ * Class 171 (extended VLAN tagging operation configuration
+ * data) on both VEIP instances, association type 10 (VEIP).
+ * G.988 9.3.13 layout: association type at 0, table size at 1,
+ * input and output TPID at 3 and 5, downstream mode at 7 and
+ * the associated ME pointer at 24.
+ */
+ memset(data, 0, sizeof(data));
+ data[0] = 10;
+ put_unaligned_be16(OMCI_EXT_VLAN_MAX_RULES, data + 1);
+ put_unaligned_be16(ETH_P_8021Q, data + 3);
+ put_unaligned_be16(ETH_P_8021Q, data + 5);
+ data[7] = 0;
+ put_unaligned_be16(OMCI_UNI_ENTITY_ID(6, 1), data + 24);
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_EXTENDED_VLAN,
+ OMCI_UNI_ENTITY_ID(6, 1),
+ GENMASK(15, 10), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ put_unaligned_be16(OMCI_UNI_ENTITY_ID(OMCI_VEIP_SLOT, 1),
+ data + 24);
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_EXTENDED_VLAN,
+ OMCI_UNI_ENTITY_ID(OMCI_VEIP_SLOT, 1),
+ GENMASK(15, 10), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ /* Class 131 (OLT-G): pre-seed the Nokia/ALCL OLT identity. */
+ memset(data, 0, sizeof(data));
+ memcpy(data, "ALCL", 4);
+ memcpy(data + 4, "ISAM ", 20);
+ ret = omci_mib_add_default_profile_mask(agent, profile,
+ OMCI_CLASS_OLT_G, 0,
+ GENMASK(15, 14), data, sizeof(data));
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int
+omci_agent_seed_profile_locked(struct omci_device *odev, u8 profile)
+{
+ switch (profile) {
+ case OMCI_OLT_PROFILE_HUAWEI:
+ return omci_agent_seed_huawei_locked(odev, profile);
+ case OMCI_OLT_PROFILE_NOKIA_ALCL:
+ return omci_agent_seed_nokia_locked(odev, profile);
+ default:
+ return 0;
+ }
+}
+
+static int
+omci_agent_profile_reconcile_locked(struct omci_device *odev,
+ u8 old_profile, u8 new_profile)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_mib_object *object;
+ unsigned long index;
+ int ret;
+
+ if (old_profile == new_profile)
+ return 0;
+
+ xa_for_each(&agent->mib, index, object) {
+ if (!object->owner_profile || object->owner_profile == new_profile)
+ continue;
+ xa_erase(&agent->mib, index);
+ kfree(object);
+ }
+
+ ret = omci_agent_seed_profile_locked(odev, new_profile);
+ if (ret)
+ return ret;
+ agent->profile_effective = new_profile;
+ ret = omci_agent_reconcile_services_locked(odev);
+ agent->profile_effective = old_profile;
+ if (ret && ret != -EOPNOTSUPP)
+ return ret;
+
+ return 0;
+}
+
+static int
+omci_mib_add_priority_queue_default(struct omci_agent *agent,
+ u16 entity_id, const u8 *data)
+{
+ return omci_mib_add_default_mask(agent, OMCI_CLASS_PRIORITY_QUEUE,
+ entity_id,
+ OMCI_PRIORITY_QUEUE_ATTR_MASK, data,
+ OMCI_MAX_ATTR_DATA);
+}
+
+static int
+omci_mib_add_traffic_scheduler_default(struct omci_agent *agent,
+ u16 entity_id, const u8 *data)
+{
+ return omci_mib_add_default_mask(agent, OMCI_CLASS_TRAFFIC_SCHEDULER,
+ entity_id,
+ OMCI_TRAFFIC_SCHEDULER_ATTR_MASK,
+ data, OMCI_MAX_ATTR_DATA);
+}
+
+static u16 omci_mib_count_class(struct omci_agent *agent, u16 class_id)
+{
+ struct omci_mib_object *object;
+ unsigned long index;
+ u16 count = 0;
+
+ xa_for_each(&agent->mib, index, object) {
+ if (object->class_id == class_id && count < U16_MAX)
+ count++;
+ }
+
+ return count;
+}
+
+static void omci_agent_refresh_identity_locked(struct omci_agent *agent)
+{
+ struct omci_mib_object *object;
+ unsigned long index;
+ u16 queues, schedulers;
+
+ xa_for_each(&agent->mib, index, object) {
+ if (object->class_id == OMCI_CLASS_SOFTWARE_IMAGE &&
+ object->entity_id < ARRAY_SIZE(agent->config.software_version)) {
+ memcpy(object->data,
+ agent->config.software_version[object->entity_id],
+ OMCI_SOFTWARE_VERSION_LEN);
+ } else if (object->class_id == OMCI_CLASS_CARDHOLDER) {
+ memcpy(object->data + 3, agent->config.equipment_id,
+ sizeof(agent->config.equipment_id));
+ memcpy(object->data + 23, agent->config.equipment_id,
+ sizeof(agent->config.equipment_id));
+ } else if (object->class_id == OMCI_CLASS_CIRCUIT_PACK) {
+ memcpy(object->data + 2, agent->config.serial_number,
+ sizeof(agent->config.serial_number));
+ memcpy(object->data + 10, agent->config.version,
+ sizeof(agent->config.version));
+ memcpy(object->data + 24, agent->config.vendor_id,
+ sizeof(agent->config.vendor_id));
+ memcpy(object->data + 31, agent->config.equipment_id,
+ sizeof(agent->config.equipment_id));
+ }
+ }
+
+ object = omci_mib_lookup(agent, OMCI_CLASS_ONU_G, 0);
+ if (object) {
+ memset(object->data, 0, sizeof(object->data));
+ memcpy(object->data, agent->config.vendor_id,
+ sizeof(agent->config.vendor_id));
+ memcpy(object->data + 4, agent->config.version,
+ sizeof(agent->config.version));
+ memcpy(object->data + 18, agent->config.serial_number,
+ sizeof(agent->config.serial_number));
+ object->data[26] = agent->config.traffic_mgmt_option;
+ }
+
+ object = omci_mib_lookup(agent, OMCI_CLASS_ONU2_G, 0);
+ if (object) {
+ memset(object->data, 0, sizeof(object->data));
+ memcpy(object->data, agent->config.equipment_id,
+ min(sizeof(agent->config.equipment_id),
+ sizeof(object->data)));
+ object->data[20] = agent->config.omcc_version;
+ queues = omci_mib_count_class(agent, OMCI_CLASS_PRIORITY_QUEUE);
+ put_unaligned_be16(queues, object->data + 25);
+ schedulers = omci_mib_count_class(agent,
+ OMCI_CLASS_TRAFFIC_SCHEDULER);
+ object->data[27] = min_t(u16, schedulers, U8_MAX);
+ put_unaligned_be16(32, object->data + 29);
+ /*
+ * G.988 9.1.3. The OLT reads these to decide what it may
+ * provision. Left at zero they say the ONU supports no
+ * security and no connectivity model at all, and an OLT that
+ * honours that identifies the ONU and then has nothing it can
+ * legally create.
+ */
+ object->data[23] = OMCI_ONU2G_SECURITY_AES128;
+ object->data[24] = OMCI_ONU2G_SECURITY_AES128;
+ put_unaligned_be16(OMCI_ONU2G_CONNECTIVITY_CAPABILITY,
+ object->data + 35);
+ put_unaligned_be16(1, object->data + 40);
+ }
+
+ object = omci_mib_lookup(agent, OMCI_CLASS_HUAWEI_SW_IMAGE_EXT, 0);
+ if (object)
+ memcpy(object->data, agent->config.software_version[0],
+ min_t(size_t, 14, OMCI_SOFTWARE_VERSION_LEN));
+
+ object = omci_mib_lookup(agent, OMCI_CLASS_NOKIA_ONT_GENERIC_V2, 0);
+ if (object) {
+ memcpy(object->data + 15, agent->config.vendor_id,
+ sizeof(agent->config.vendor_id));
+ memcpy(object->data + 19, agent->config.serial_number,
+ sizeof(agent->config.serial_number));
+ }
+}
+
+static void omci_agent_apply_identity(struct omci_agent *agent,
+ const struct omci_identity *identity)
+{
+ if (identity->valid & OMCI_IDENTITY_F_SERIAL_NUMBER) {
+ memcpy(agent->config.serial_number, identity->serial_number,
+ sizeof(agent->config.serial_number));
+ agent->config.serial_source = identity->serial_source;
+ }
+ if (identity->valid & OMCI_IDENTITY_F_VENDOR_ID) {
+ memcpy(agent->config.vendor_id, identity->vendor_id,
+ sizeof(agent->config.vendor_id));
+ agent->config.vendor_source = identity->vendor_source;
+ }
+ if (identity->valid & OMCI_IDENTITY_F_PASSWORD) {
+ memcpy(agent->config.password, identity->password,
+ sizeof(agent->config.password));
+ agent->config.password_source = identity->password_source;
+ }
+ if (identity->valid & OMCI_IDENTITY_F_VERSION) {
+ memcpy(agent->config.version, identity->version,
+ sizeof(agent->config.version));
+ memcpy(agent->config.software_version[0], identity->version,
+ sizeof(agent->config.software_version[0]));
+ memcpy(agent->config.software_version[1], identity->version,
+ sizeof(agent->config.software_version[1]));
+ agent->config.version_source = identity->version_source;
+ agent->config.software_version_source[0] =
+ identity->version_source;
+ agent->config.software_version_source[1] =
+ identity->version_source;
+ }
+ if (identity->valid & OMCI_IDENTITY_F_EQUIPMENT_ID) {
+ memcpy(agent->config.equipment_id, identity->equipment_id,
+ sizeof(agent->config.equipment_id));
+ agent->config.equipment_source = identity->equipment_source;
+ }
+}
+
+static void
+omci_agent_identity_state_locked(const struct omci_agent *agent,
+ struct omci_identity *identity)
+{
+ memset(identity, 0, sizeof(*identity));
+ identity->valid = OMCI_IDENTITY_F_SERIAL_NUMBER |
+ OMCI_IDENTITY_F_VENDOR_ID |
+ OMCI_IDENTITY_F_PASSWORD |
+ OMCI_IDENTITY_F_VERSION |
+ OMCI_IDENTITY_F_EQUIPMENT_ID;
+ memcpy(identity->serial_number, agent->config.serial_number,
+ sizeof(identity->serial_number));
+ memcpy(identity->vendor_id, agent->config.vendor_id,
+ sizeof(identity->vendor_id));
+ memcpy(identity->password, agent->config.password,
+ sizeof(identity->password));
+ memcpy(identity->version, agent->config.version,
+ sizeof(identity->version));
+ memcpy(identity->equipment_id, agent->config.equipment_id,
+ sizeof(identity->equipment_id));
+ identity->serial_source = agent->config.serial_source;
+ identity->vendor_source = agent->config.vendor_source;
+ identity->password_source = agent->config.password_source;
+ identity->version_source = agent->config.version_source;
+ identity->equipment_source = agent->config.equipment_source;
+}
+
+static int omci_agent_get_ani_topology(struct omci_device *odev,
+ struct omci_ani_topology *topology)
+{
+ u32 last_tcont, last_scheduler, last_queue;
+ int ret = 0;
+
+ *topology = (struct omci_ani_topology) {
+ .tcont_base = 0x8000,
+ .scheduler_base = 0x8000,
+ .queue_base = 0x8000,
+ .maximum_queue_size = 0xffff,
+ .allocated_queue_size = 4,
+ .tcont_count = OMCI_DEFAULT_TCONT_COUNT,
+ .queues_per_tcont = OMCI_DEFAULT_QUEUES_PER_TCONT,
+ .queue_config_option = 1,
+ .scheduler_policy = 1,
+ };
+
+ if (odev->ops && odev->ops->get_ani_topology) {
+ ret = odev->ops->get_ani_topology(odev, topology);
+ if (ret)
+ return ret;
+ }
+
+ if (!topology->tcont_count ||
+ topology->tcont_count > OMCI_MAX_TCONT_RESOURCES ||
+ !topology->queues_per_tcont ||
+ topology->queues_per_tcont > OMCI_MAX_QUEUES_PER_TCONT)
+ return -EINVAL;
+
+ last_tcont = topology->tcont_base + topology->tcont_count - 1;
+ last_scheduler = topology->scheduler_base + topology->tcont_count - 1;
+ last_queue = topology->queue_base +
+ topology->tcont_count * topology->queues_per_tcont - 1;
+ if (last_tcont > U16_MAX || last_scheduler > U16_MAX ||
+ last_queue > U16_MAX)
+ return -ERANGE;
+
+ return 0;
+}
+
+static void
+omci_priority_queue_default_data(const struct omci_ani_topology *topology,
+ unsigned int tcont, unsigned int queue,
+ u8 data[OMCI_MAX_ATTR_DATA])
+{
+ u16 tcont_entity = topology->tcont_base + tcont;
+ u16 scheduler_entity = topology->scheduler_base + tcont;
+ u16 priority = topology->queues_per_tcont - queue - 1;
+
+ memset(data, 0, OMCI_MAX_ATTR_DATA);
+ data[0] = topology->queue_config_option;
+ put_unaligned_be16(topology->maximum_queue_size, data + 1);
+ put_unaligned_be16(topology->allocated_queue_size, data + 3);
+ put_unaligned_be16(tcont_entity, data + 9);
+ put_unaligned_be16(priority, data + 11);
+ put_unaligned_be16(scheduler_entity, data + 13);
+ data[15] = 1;
+ put_unaligned_be16(0xffff, data + 22);
+}
+
+static void
+omci_traffic_scheduler_default_data(const struct omci_ani_topology *topology,
+ unsigned int tcont,
+ u8 data[OMCI_MAX_ATTR_DATA])
+{
+ memset(data, 0, OMCI_MAX_ATTR_DATA);
+ put_unaligned_be16(topology->tcont_base + tcont, data);
+ data[4] = topology->scheduler_policy;
+}
+
+static void
+omci_cardholder_default_data(const struct omci_agent *agent, u8 unit_type,
+ u8 port_count, u8 data[OMCI_MAX_ATTR_DATA])
+{
+ memset(data, 0, OMCI_MAX_ATTR_DATA);
+ data[0] = unit_type;
+ data[1] = unit_type;
+ data[2] = port_count;
+ memcpy(data + 3, agent->config.equipment_id,
+ sizeof(agent->config.equipment_id));
+ memcpy(data + 23, agent->config.equipment_id,
+ sizeof(agent->config.equipment_id));
+}
+
+static void
+omci_circuit_pack_default_data(const struct omci_agent *agent,
+ const struct omci_ani_topology *topology,
+ u8 unit_type, u8 port_count, bool ani,
+ u8 data[OMCI_MAX_ATTR_DATA])
+{
+ u16 queues;
+
+ memset(data, 0, OMCI_MAX_ATTR_DATA);
+ data[0] = unit_type;
+ data[1] = port_count;
+ memcpy(data + 2, agent->config.serial_number,
+ sizeof(agent->config.serial_number));
+ memcpy(data + 10, agent->config.version,
+ sizeof(agent->config.version));
+ memcpy(data + 24, agent->config.vendor_id,
+ sizeof(agent->config.vendor_id));
+ /* Administrative and operational states are enabled. */
+ data[28] = 0;
+ data[29] = 0;
+ /* The default service model is bridged rather than routed. */
+ data[30] = 0;
+ memcpy(data + 31, agent->config.equipment_id,
+ sizeof(agent->config.equipment_id));
+ if (!ani)
+ return;
+
+ queues = topology->tcont_count * topology->queues_per_tcont;
+ data[52] = min_t(u16, topology->tcont_count, U8_MAX);
+ data[53] = min_t(u16, queues, U8_MAX);
+ data[54] = min_t(u16, topology->tcont_count, U8_MAX);
+}
+
+static int
+omci_agent_add_equipment_default(struct omci_agent *agent,
+ const struct omci_ani_topology *topology,
+ u8 slot, u8 unit_type, u8 port_count,
+ bool ani, u8 data[OMCI_MAX_ATTR_DATA])
+{
+ u16 entity_id = OMCI_EQUIPMENT_ENTITY_ID(slot);
+ int ret;
+
+ omci_cardholder_default_data(agent, unit_type, port_count, data);
+ ret = omci_mib_add_default_mask(agent, OMCI_CLASS_CARDHOLDER,
+ entity_id, GENMASK(15, 10), data,
+ OMCI_MAX_ATTR_DATA);
+ if (ret)
+ return ret;
+
+ omci_circuit_pack_default_data(agent, topology, unit_type, port_count,
+ ani, data);
+ return omci_mib_add_default_mask(agent, OMCI_CLASS_CIRCUIT_PACK,
+ entity_id, GENMASK(15, 2), data,
+ OMCI_MAX_ATTR_DATA);
+}
+
+static void omci_uni_g_default_data(u8 data[OMCI_MAX_ATTR_DATA])
+{
+ memset(data, 0, OMCI_MAX_ATTR_DATA);
+ /* Support both OMCI and non-OMCI management. */
+ data[3] = 2;
+}
+
+static int omci_agent_populate_defaults(struct omci_device *odev)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_ani_topology topology;
+ u8 data[OMCI_MAX_ATTR_DATA] = {};
+ unsigned int i, queue;
+ int ret;
+
+ ret = omci_agent_get_ani_topology(odev, &topology);
+ if (ret)
+ return ret;
+
+ ret = omci_mib_add_default_mask(agent, OMCI_CLASS_ONU_DATA, 0,
+ BIT(15), data, 1);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(agent->config.software_version); i++) {
+ memset(data, 0, sizeof(data));
+ memcpy(data, agent->config.software_version[i],
+ OMCI_SOFTWARE_VERSION_LEN);
+ data[14] = i == 0; /* Committed image. */
+ data[15] = i == 0; /* Active image. */
+ data[16] = 1; /* Valid image. */
+ ret = omci_mib_add_default_mask(agent, OMCI_CLASS_SOFTWARE_IMAGE,
+ i, GENMASK(15, 12), data,
+ sizeof(data));
+ if (ret)
+ return ret;
+ }
+
+ ret = omci_agent_add_equipment_default(agent, &topology,
+ OMCI_GPON_SLOT, OMCI_GPON_UNIT_TYPE,
+ 1, true, data);
+ if (ret)
+ return ret;
+ ret = omci_agent_add_equipment_default(agent, &topology,
+ OMCI_ETHERNET_SLOT,
+ OMCI_ETHERNET_UNIT_TYPE,
+ agent->config.uni_count, false, data);
+ if (ret)
+ return ret;
+ ret = omci_agent_add_equipment_default(agent, &topology,
+ OMCI_VEIP_SLOT, OMCI_VEIP_UNIT_TYPE,
+ 1, false, data);
+ if (ret)
+ return ret;
+
+ ret = omci_mib_add_default(agent, OMCI_CLASS_ONU_G, 0, data,
+ sizeof(data));
+ if (ret)
+ return ret;
+ ret = omci_mib_add_default(agent, OMCI_CLASS_ONU2_G, 0, data,
+ sizeof(data));
+ if (ret)
+ return ret;
+ memset(data, 0, sizeof(data));
+ ret = omci_mib_add_default(agent, OMCI_CLASS_ANI_G,
+ OMCI_ANI_G_ENTITY_ID, data,
+ sizeof(data));
+ if (ret)
+ return ret;
+
+ for (i = 0; i < agent->config.uni_count; i++) {
+ u16 entity_id = OMCI_UNI_ENTITY_ID(OMCI_ETHERNET_SLOT, i + 1);
+
+ memset(data, 0, sizeof(data));
+ data[4] = 0; /* Administrative state unlocked. */
+ ret = omci_mib_add_default(agent, OMCI_CLASS_PPTP_ETHERNET_UNI,
+ entity_id, data, sizeof(data));
+ if (ret)
+ return ret;
+
+ omci_uni_g_default_data(data);
+ ret = omci_mib_add_default_mask(agent, OMCI_CLASS_UNI_G,
+ entity_id, GENMASK(15, 12),
+ data, sizeof(data));
+ if (ret)
+ return ret;
+ }
+
+ memset(data, 0, sizeof(data));
+ data[0] = 0; /* Administrative state unlocked. */
+ ret = omci_mib_add_default(agent, OMCI_CLASS_VEIP,
+ OMCI_UNI_ENTITY_ID(OMCI_VEIP_SLOT, 1), data,
+ sizeof(data));
+ if (ret)
+ return ret;
+ omci_uni_g_default_data(data);
+ ret = omci_mib_add_default_mask(agent, OMCI_CLASS_UNI_G,
+ OMCI_UNI_ENTITY_ID(OMCI_VEIP_SLOT, 1),
+ GENMASK(15, 12), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ /* Also seed Slot 6 VEIP (0x0601) for compatibility with
Alcatel-Lucent / EcoNet OLT profiles */
+ memset(data, 0, sizeof(data));
+ data[0] = 0;
+ ret = omci_mib_add_default(agent, OMCI_CLASS_VEIP,
+ OMCI_UNI_ENTITY_ID(6, 1), data,
+ sizeof(data));
+ if (ret)
+ return ret;
+ omci_uni_g_default_data(data);
+ ret = omci_mib_add_default_mask(agent, OMCI_CLASS_UNI_G,
+ OMCI_UNI_ENTITY_ID(6, 1),
+ GENMASK(15, 12), data, sizeof(data));
+ if (ret)
+ return ret;
+
+ for (i = 0; i < topology.tcont_count; i++) {
+ u16 scheduler_id = topology.scheduler_base + i;
+
+ memset(data, 0, sizeof(data));
+ put_unaligned_be16(0xffff, data);
+ ret = omci_mib_add_default(agent, OMCI_CLASS_TCONT,
+ topology.tcont_base + i, data,
+ sizeof(data));
+ if (ret)
+ return ret;
+
+ omci_traffic_scheduler_default_data(&topology, i, data);
+ ret = omci_mib_add_traffic_scheduler_default(agent, scheduler_id, data);
+ if (ret)
+ return ret;
+
+ for (queue = 0; queue < topology.queues_per_tcont; queue++) {
+ u16 entity_id = topology.queue_base +
+ i * topology.queues_per_tcont + queue;
+
+ omci_priority_queue_default_data(&topology, i, queue, data);
+ ret = omci_mib_add_priority_queue_default(agent, entity_id, data);
+ if (ret)
+ return ret;
+ }
+ }
+
+ omci_agent_refresh_identity_locked(agent);
+ return 0;
+}
+
+int omci_agent_init(struct omci_device *odev)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_identity identity;
+ int ret;
+
+ mutex_init(&agent->lock);
+ xa_init(&agent->mib);
+ xa_init(&agent->services);
+ agent->enabled = true;
+ agent->permissive = false;
+ agent->fake_omci = false;
+ agent->dying_gasp = false;
+ agent->config.dying_gasp_source = OMCI_CONFIG_SOURCE_DEFAULT;
+ agent->config.uni_count = 4;
+ agent->config.onu_type = OMCI_ONU_TYPE_HGU;
+ agent->config.onu_type_source = OMCI_CONFIG_SOURCE_DEFAULT;
+ agent->config.traffic_mgmt_option = 0;
+ agent->config.olt_profile = OMCI_OLT_PROFILE_AUTO;
+ agent->config.olt_profile_force = OMCI_OLT_PROFILE_UNSPEC;
+ agent->config.olt_profile_source = OMCI_CONFIG_SOURCE_DEFAULT;
+ agent->config.olt_profile_force_source = OMCI_CONFIG_SOURCE_DEFAULT;
+ memcpy(agent->config.serial_number, "OPEN0001", 8);
+ memcpy(agent->config.vendor_id, "OPEN", 4);
+ strscpy(agent->config.version, "OpenWrt", sizeof(agent->config.version));
+ strscpy(agent->config.software_version[0], "OpenWrt",
+ sizeof(agent->config.software_version[0]));
+ strscpy(agent->config.software_version[1], "OpenWrt",
+ sizeof(agent->config.software_version[1]));
+ agent->config.omcc_version = OMCI_OMCC_VERSION_G988_2010_BASELINE;
+ strscpy(agent->config.equipment_id, "Airoha EN7523",
+ sizeof(agent->config.equipment_id));
+ agent->config.serial_source = OMCI_CONFIG_SOURCE_DEFAULT;
+ agent->config.vendor_source = OMCI_CONFIG_SOURCE_DEFAULT;
+ agent->config.password_source = OMCI_CONFIG_SOURCE_DEFAULT;
+ agent->config.version_source = OMCI_CONFIG_SOURCE_DEFAULT;
+ agent->config.software_version_source[0] = OMCI_CONFIG_SOURCE_DEFAULT;
+ agent->config.software_version_source[1] = OMCI_CONFIG_SOURCE_DEFAULT;
+ agent->config.omcc_version_source = OMCI_CONFIG_SOURCE_DEFAULT;
+ agent->config.equipment_source = OMCI_CONFIG_SOURCE_DEFAULT;
+
+ ret = omci_identity_load(odev->parent, &identity);
+ if (ret)
+ return ret;
+ omci_agent_apply_identity(agent, &identity);
+
+ mutex_lock(&agent->lock);
+ ret = omci_agent_populate_defaults(odev);
+ if (!ret)
+ ret = omci_agent_profile_refresh_locked(odev, NULL, NULL);
+ mutex_unlock(&agent->lock);
+ return ret;
+}
+
+void omci_agent_cleanup(struct omci_device *odev)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_mib_object *object;
+ unsigned long index;
+
+ mutex_lock(&agent->lock);
+ omci_agent_reset_duplicate_locked(agent);
+ omci_agent_reset_table_snapshot_locked(agent);
+ omci_agent_clear_services_locked(odev);
+ xa_for_each(&agent->mib, index, object) {
+ xa_erase(&agent->mib, index);
+ kfree(object);
+ }
+ mutex_unlock(&agent->lock);
+ xa_destroy(&agent->services);
+ xa_destroy(&agent->mib);
+}
+
+static bool omci_mib_object_active(const struct omci_agent *agent,
+ const struct omci_mib_object *object)
+{
+ if (object->pending_delete)
+ return false;
+ if (object->owner_profile &&
+ object->owner_profile != agent->profile_effective)
+ return false;
+
+ return omci_me_active(agent, object->class_id);
+}
+
+static void omci_agent_reset_table_snapshot_locked(struct omci_agent *agent)
+{
+ kfree(agent->table_snapshot);
+ agent->table_snapshot = NULL;
+ agent->table_snapshot_len = 0;
+ agent->table_snapshot_jiffies = 0;
+ agent->table_snapshot_class_id = 0;
+ agent->table_snapshot_entity_id = 0;
+ agent->table_snapshot_mask = 0;
+}
+
+static void
+omci_set_table_snapshot_locked(struct omci_agent *agent, u16 class_id,
+ u16 entity_id, u16 mask, u8 *table,
+ size_t table_len)
+{
+ omci_agent_reset_table_snapshot_locked(agent);
+ agent->table_snapshot = table;
+ agent->table_snapshot_len = table_len;
+ agent->table_snapshot_jiffies = jiffies;
+ agent->table_snapshot_class_id = class_id;
+ agent->table_snapshot_entity_id = entity_id;
+ agent->table_snapshot_mask = mask;
+}
+
+static int omci_agent_build_me_type_table_locked(struct omci_device *odev,
+ u8 **table,
+ size_t *table_len)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_mib_object *object;
+ struct omci_me_class class;
+ unsigned long *classes;
+ unsigned long class_id;
+ unsigned long index;
+ u32 next = 0;
+ size_t count;
+ u8 *data;
+ int ret;
+
+ classes = bitmap_zalloc(U16_MAX + 1, GFP_KERNEL);
+ if (!classes)
+ return -ENOMEM;
+
+ for (;;) {
+ ret = omci_me_class_next(odev, next, &class, &next);
+ if (ret)
+ break;
+ if (class.support >= OMCI_CLASS_SUPPORT_PARSED)
+ set_bit(class.class_id, classes);
+ }
+ if (ret != -ENOENT)
+ goto out_free_bitmap;
+
+ xa_for_each(&agent->mib, index, object) {
+ if (omci_mib_object_active(agent, object))
+ set_bit(object->class_id, classes);
+ }
+ /* Stored bridge/GAL profiles are supported as partial shadow MEs. */
+ set_bit(OMCI_CLASS_MAC_BRIDGE_SERVICE_PROFILE, classes);
+ set_bit(OMCI_CLASS_MAC_BRIDGE_CONFIG_DATA, classes);
+ set_bit(OMCI_CLASS_GAL_ETHERNET_PROFILE, classes);
+ set_bit(OMCI_CLASS_OMCI, classes);
+
+ count = bitmap_weight(classes, U16_MAX + 1);
+ data = kmalloc_array(count, sizeof(u16), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto out_free_bitmap;
+ }
+
+ count = 0;
+ for_each_set_bit(class_id, classes, U16_MAX + 1) {
+ put_unaligned_be16(class_id, data + count * sizeof(u16));
+ count++;
+ }
+ *table = data;
+ *table_len = count * sizeof(u16);
+ ret = 0;
+
+out_free_bitmap:
+ bitmap_free(classes);
+ return ret;
+}
+
+static int omci_agent_build_message_type_table(u8 **table, size_t *table_len)
+{
+ static const u8 message_types[] = {
+ OMCI_MSG_TYPE_CREATE,
+ OMCI_MSG_TYPE_DELETE,
+ OMCI_MSG_TYPE_SET,
+ OMCI_MSG_TYPE_GET,
+ OMCI_MSG_TYPE_GET_ALL_ALARMS,
+ OMCI_MSG_TYPE_GET_ALL_ALARMS_NEXT,
+ OMCI_MSG_TYPE_MIB_UPLOAD,
+ OMCI_MSG_TYPE_MIB_UPLOAD_NEXT,
+ OMCI_MSG_TYPE_MIB_RESET,
+ OMCI_MSG_TYPE_ALARM_NOTIFICATION,
+ OMCI_MSG_TYPE_TEST,
+ OMCI_MSG_TYPE_SYNC_TIME,
+ OMCI_MSG_TYPE_REBOOT,
+ OMCI_MSG_TYPE_GET_NEXT,
+ OMCI_MSG_TYPE_TEST_RESULT,
+ OMCI_MSG_TYPE_GET_CURRENT_DATA,
+ OMCI_MSG_TYPE_SET_TABLE,
+ };
+
+ *table = kmemdup(message_types, sizeof(message_types), GFP_KERNEL);
+ if (!*table)
+ return -ENOMEM;
+ *table_len = sizeof(message_types);
+ return 0;
+}
+
+static u8 omci_agent_get_omci_table_locked(struct omci_device *odev,
+ u16 entity_id, u16 mask,
+ u8 *response_content,
+ size_t response_capacity,
+ size_t *response_len)
+{
+ struct omci_agent *agent = &odev->agent;
+ u8 *table = NULL;
+ size_t table_len = 0;
+ int ret;
+
+ if (entity_id || hweight16(mask) != 1 ||
+ !(mask & (OMCI_OMCI_ME_TYPE_TABLE_MASK |
+ OMCI_OMCI_MESSAGE_TYPE_TABLE_MASK)))
+ return OMCI_RESULT_PARAMETER_ERROR;
+ if (response_capacity < 7)
+ return OMCI_RESULT_PARAMETER_ERROR;
+
+ if (mask == OMCI_OMCI_ME_TYPE_TABLE_MASK)
+ ret = omci_agent_build_me_type_table_locked(odev, &table,
+ &table_len);
+ else
+ ret = omci_agent_build_message_type_table(&table, &table_len);
+ if (ret)
+ return OMCI_RESULT_PROCESSING_ERROR;
+
+ omci_set_table_snapshot_locked(agent, OMCI_CLASS_OMCI, entity_id,
+ mask, table, table_len);
+
+ memset(response_content, 0, response_capacity);
+ response_content[0] = OMCI_RESULT_SUCCESS;
+ put_unaligned_be16(mask, response_content + 1);
+ put_unaligned_be32(table_len, response_content + 3);
+ *response_len = 7;
+ return OMCI_RESULT_SUCCESS;
+}
+
+static bool
+omci_agent_me_type_supported_locked(struct omci_device *odev, u16 class_id)
+{
+ u8 *table;
+ size_t table_len;
+ size_t offset;
+ bool found = false;
+
+ if (omci_agent_build_me_type_table_locked(odev, &table, &table_len))
+ return false;
+ for (offset = 0; offset + sizeof(u16) <= table_len;
+ offset += sizeof(u16)) {
+ if (get_unaligned_be16(table + offset) == class_id) {
+ found = true;
+ break;
+ }
+ }
+ kfree(table);
+
+ return found;
+}
+
+static u8 omci_agent_me_support(const struct omci_me_desc *desc)
+{
+ if (!desc)
+ return 3;
+
+ switch (desc->support) {
+ case OMCI_CLASS_SUPPORT_NATIVE:
+ case OMCI_CLASS_SUPPORT_PROVISIONED:
+ return 1;
+ case OMCI_CLASS_SUPPORT_PARSED:
+ case OMCI_CLASS_SUPPORT_SHADOW:
+ return 3;
+ default:
+ return 2;
+ }
+}
+
+static u8 omci_agent_me_access(const struct omci_me_desc *desc)
+{
+ bool onu_created = desc && (desc->flags & OMCI_ME_F_ONU_CREATED);
+ bool olt_created = desc && (desc->actions & OMCI_ME_ACTION_CREATE);
+
+ if (onu_created && olt_created)
+ return 3;
+ if (olt_created)
+ return 2;
+ if (onu_created)
+ return 1;
+
+ return 3;
+}
+
+static u32 omci_agent_me_actions(const struct omci_me_desc *desc,
+ u16 class_id)
+{
+ if (desc)
+ return desc->actions;
+
+ switch (class_id) {
+ case OMCI_CLASS_MAC_BRIDGE_SERVICE_PROFILE:
+ case OMCI_CLASS_MAC_BRIDGE_CONFIG_DATA:
+ case OMCI_CLASS_GAL_ETHERNET_PROFILE:
+ return OMCI_ME_ACTION_CREATE | OMCI_ME_ACTION_DELETE |
+ OMCI_ME_ACTION_GET | OMCI_ME_ACTION_SET;
+ default:
+ return OMCI_ME_ACTION_GET | OMCI_ME_ACTION_SET;
+ }
+}
+
+static int
+omci_build_attr_ids_locked(struct omci_device *odev, u16 class_id,
+ u8 **table, size_t *table_len)
+{
+ const struct omci_me_desc *desc = omci_me_lookup(&odev->agent, class_id);
+ u8 *data;
+ unsigned int i;
+ int ret;
+
+ if (!desc || !desc->num_attrs) {
+ data = kmalloc(1, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ *table = data;
+ *table_len = 0;
+ return 0;
+ }
+
+ data = kmalloc_array(desc->num_attrs, sizeof(u16), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ for (i = 0; i < desc->num_attrs; i++) {
+ u16 attr_id;
+
+ ret = omci_me_attr_id(&odev->agent, class_id, i, &attr_id);
+ if (ret) {
+ kfree(data);
+ return ret;
+ }
+ put_unaligned_be16(attr_id, data + i * sizeof(u16));
+ }
+ *table = data;
+ *table_len = desc->num_attrs * sizeof(u16);
+ return 0;
+}
+
+static int
+omci_build_instances_locked(struct omci_device *odev, u16 class_id,
+ u8 **table, size_t *table_len)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_mib_object *object;
+ unsigned long index;
+ unsigned int count = 0;
+ u8 *data;
+
+ if (class_id == OMCI_CLASS_MANAGED_ENTITY) {
+ return omci_agent_build_me_type_table_locked(odev, table,
+ table_len);
+ }
+ if (class_id == OMCI_CLASS_ATTRIBUTE) {
+ unsigned int attr_count = omci_me_attr_count(agent);
+ unsigned int i;
+
+ data = kmalloc_array(max(attr_count, 1U), sizeof(u16),
+ GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ for (i = 0; i < attr_count; i++)
+ put_unaligned_be16(i + 1, data + i * sizeof(u16));
+ *table = data;
+ *table_len = attr_count * sizeof(u16);
+ return 0;
+ }
+ if (class_id == OMCI_CLASS_OMCI) {
+ data = kmalloc(sizeof(u16), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ put_unaligned_be16(0, data);
+ *table = data;
+ *table_len = sizeof(u16);
+ return 0;
+ }
+
+ xa_for_each(&agent->mib, index, object)
+ if (object->class_id == class_id &&
+ omci_mib_object_active(agent, object))
+ count++;
+ data = kmalloc_array(max(count, 1U), sizeof(u16), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ count = 0;
+ xa_for_each(&agent->mib, index, object) {
+ if (object->class_id != class_id ||
+ !omci_mib_object_active(agent, object))
+ continue;
+ put_unaligned_be16(object->entity_id,
+ data + count * sizeof(u16));
+ count++;
+ }
+ *table = data;
+ *table_len = count * sizeof(u16);
+ return 0;
+}
+
+static int omci_agent_empty_table(u8 **table, size_t *table_len)
+{
+ *table = kmalloc(1, GFP_KERNEL);
+ if (!*table)
+ return -ENOMEM;
+ *table_len = 0;
+ return 0;
+}
+
+static u8 omci_agent_get_managed_entity_locked(struct omci_device *odev,
+ u16 entity_id, u16 mask,
+ u8 *response_content,
+ size_t response_capacity,
+ size_t *response_len)
+{
+ struct omci_agent *agent = &odev->agent;
+ const struct omci_me_desc *desc;
+ size_t used = 3;
+ u16 encoded = 0;
+ u8 *table = NULL;
+ size_t table_len = 0;
+ unsigned int bit;
+ int ret;
+
+ if (!mask || mask & ~GENMASK(15, 8))
+ return OMCI_RESULT_PARAMETER_ERROR;
+ if (!omci_agent_me_type_supported_locked(odev, entity_id))
+ return OMCI_RESULT_UNKNOWN_INSTANCE;
+ desc = omci_me_lookup(agent, entity_id);
+
+ if (mask & OMCI_MANAGED_ENTITY_TABLE_MASKS) {
+ if (hweight16(mask) != 1 || response_capacity < 7)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ switch (mask) {
+ case OMCI_MANAGED_ENTITY_ATTR_TABLE_MASK:
+ ret = omci_build_attr_ids_locked(odev, entity_id, &table,
+ &table_len);
+ break;
+ case OMCI_MANAGED_ENTITY_INSTANCES_MASK:
+ ret = omci_build_instances_locked(odev, entity_id,
+ &table, &table_len);
+ break;
+ default:
+ ret = omci_agent_empty_table(&table, &table_len);
+ break;
+ }
+ if (ret)
+ return OMCI_RESULT_PROCESSING_ERROR;
+ omci_set_table_snapshot_locked(agent, OMCI_CLASS_MANAGED_ENTITY,
+ entity_id, mask, table, table_len);
+ memset(response_content, 0, response_capacity);
+ response_content[0] = OMCI_RESULT_SUCCESS;
+ put_unaligned_be16(mask, response_content + 1);
+ put_unaligned_be32(table_len, response_content + 3);
+ *response_len = 7;
+ return OMCI_RESULT_SUCCESS;
+ }
+
+ memset(response_content, 0, response_capacity);
+ response_content[0] = OMCI_RESULT_SUCCESS;
+ for (bit = 0; bit < 16; bit++) {
+ u16 attr_mask = BIT(15 - bit);
+
+ if (!(mask & attr_mask))
+ continue;
+ switch (attr_mask) {
+ case OMCI_MANAGED_ENTITY_NAME_MASK: {
+ const char *name = omci_me_class_name(entity_id);
+ size_t len = strnlen(name, OMCI_CAPABILITY_NAME_LEN);
+
+ if (used + OMCI_CAPABILITY_NAME_LEN > response_capacity)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ memcpy(response_content + used, name, len);
+ used += OMCI_CAPABILITY_NAME_LEN;
+ break;
+ }
+ case OMCI_MANAGED_ENTITY_ACCESS_MASK:
+ if (used + 1 > response_capacity)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ response_content[used++] = omci_agent_me_access(desc);
+ break;
+ case OMCI_MANAGED_ENTITY_ACTIONS_MASK:
+ if (used + 4 > response_capacity)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ put_unaligned_be32(omci_agent_me_actions(desc, entity_id),
+ response_content + used);
+ used += 4;
+ break;
+ case OMCI_MANAGED_ENTITY_SUPPORT_MASK:
+ if (used + 1 > response_capacity)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ response_content[used++] = omci_agent_me_support(desc);
+ break;
+ default:
+ return OMCI_RESULT_PARAMETER_ERROR;
+ }
+ encoded |= attr_mask;
+ }
+ put_unaligned_be16(encoded, response_content + 1);
+ *response_len = used;
+ return OMCI_RESULT_SUCCESS;
+}
+
+static bool omci_agent_attr_is_table(u16 class_id, u16 mask)
+{
+ if (class_id == OMCI_CLASS_OMCI)
+ return mask & (OMCI_OMCI_ME_TYPE_TABLE_MASK |
+ OMCI_OMCI_MESSAGE_TYPE_TABLE_MASK);
+ if (class_id == OMCI_CLASS_MANAGED_ENTITY)
+ return mask & OMCI_MANAGED_ENTITY_TABLE_MASKS;
+ if (class_id == OMCI_CLASS_ATTRIBUTE)
+ return mask & OMCI_ATTRIBUTE_CODE_POINTS_MASK;
+ if (class_id == OMCI_CLASS_EXTENDED_VLAN)
+ return mask == BIT(8);
+
+ return false;
+}
+
+static u8 omci_agent_attr_format(u16 class_id,
+ const struct omci_attr_desc *attr)
+{
+ if (omci_agent_attr_is_table(class_id, attr->mask))
+ return OMCI_ATTRIBUTE_FORMAT_TABLE;
+ if (attr->len > 4)
+ return OMCI_ATTRIBUTE_FORMAT_STRING;
+ if (attr->len == 4 && attr->mask == GENMASK(15, 0))
+ return OMCI_ATTRIBUTE_FORMAT_BIT_FIELD;
+
+ return OMCI_ATTRIBUTE_FORMAT_UNSIGNED;
+}
+
+static u8 omci_agent_get_attribute_locked(struct omci_device *odev,
+ u16 entity_id, u16 mask,
+ u8 *response_content,
+ size_t response_capacity,
+ size_t *response_len)
+{
+ struct omci_agent *agent = &odev->agent;
+ const struct omci_me_desc *desc;
+ const struct omci_attr_desc *attr;
+ unsigned int attr_index;
+ size_t used = 3;
+ u16 class_id;
+ u16 encoded = 0;
+ unsigned int bit;
+ u32 upper;
+ int ret;
+
+ if (!mask || mask & ~GENMASK(15, 7))
+ return OMCI_RESULT_PARAMETER_ERROR;
+ ret = omci_me_attr_get(agent, entity_id, &class_id, &attr_index,
+ &desc, &attr);
+ if (ret)
+ return OMCI_RESULT_UNKNOWN_INSTANCE;
+
+ if (mask & OMCI_ATTRIBUTE_TABLE_MASKS) {
+ u8 *table;
+ size_t table_len;
+
+ if (hweight16(mask) != 1 || response_capacity < 7)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ ret = omci_agent_empty_table(&table, &table_len);
+ if (ret)
+ return OMCI_RESULT_PROCESSING_ERROR;
+ omci_set_table_snapshot_locked(agent, OMCI_CLASS_ATTRIBUTE,
+ entity_id, mask, table, table_len);
+ memset(response_content, 0, response_capacity);
+ response_content[0] = OMCI_RESULT_SUCCESS;
+ put_unaligned_be16(mask, response_content + 1);
+ put_unaligned_be32(table_len, response_content + 3);
+ *response_len = 7;
+ return OMCI_RESULT_SUCCESS;
+ }
+
+ memset(response_content, 0, response_capacity);
+ response_content[0] = OMCI_RESULT_SUCCESS;
+ for (bit = 0; bit < 16; bit++) {
+ u16 attr_mask = BIT(15 - bit);
+
+ if (!(mask & attr_mask))
+ continue;
+ switch (attr_mask) {
+ case OMCI_ATTRIBUTE_NAME_MASK: {
+ char name[OMCI_CAPABILITY_NAME_LEN + 1];
+
+ if (used + OMCI_CAPABILITY_NAME_LEN > response_capacity)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ snprintf(name, sizeof(name), "%u attribute %u",
+ class_id, attr_index + 1);
+ memcpy(response_content + used, name,
+ strnlen(name, OMCI_CAPABILITY_NAME_LEN));
+ used += OMCI_CAPABILITY_NAME_LEN;
+ break;
+ }
+ case OMCI_ATTRIBUTE_SIZE_MASK:
+ if (used + 2 > response_capacity)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ put_unaligned_be16(attr->len, response_content + used);
+ used += 2;
+ break;
+ case OMCI_ATTRIBUTE_ACCESS_MASK:
+ if (used + 1 > response_capacity)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ response_content[used++] = attr->access & 0x7;
+ break;
+ case OMCI_ATTRIBUTE_FORMAT_MASK:
+ if (used + 1 > response_capacity)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ response_content[used++] =
+ omci_agent_attr_format(class_id, attr);
+ break;
+ case OMCI_ATTRIBUTE_LOWER_LIMIT_MASK:
+ case OMCI_ATTRIBUTE_BIT_FIELD_MASK:
+ if (used + 4 > response_capacity)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ put_unaligned_be32(0, response_content + used);
+ used += 4;
+ break;
+ case OMCI_ATTRIBUTE_UPPER_LIMIT_MASK:
+ if (used + 4 > response_capacity)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ upper = attr->len >= sizeof(u32) ? U32_MAX :
+ GENMASK(attr->len * 8 - 1, 0);
+ put_unaligned_be32(upper, response_content + used);
+ used += 4;
+ break;
+ case OMCI_ATTRIBUTE_SUPPORT_MASK:
+ if (used + 1 > response_capacity)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ response_content[used++] = 3;
+ break;
+ default:
+ return OMCI_RESULT_PARAMETER_ERROR;
+ }
+ encoded |= attr_mask;
+ }
+ put_unaligned_be16(encoded, response_content + 1);
+ *response_len = used;
+ return OMCI_RESULT_SUCCESS;
+}
+
+static u8 omci_agent_get_next_locked(struct omci_device *odev,
+ u16 class_id, u16 entity_id,
+ u8 device_id, const u8 *content,
+ size_t content_len, u8 *response_content,
+ size_t response_capacity,
+ size_t *response_len)
+{
+ struct omci_agent *agent = &odev->agent;
+ size_t fragment_len;
+ size_t copy_len;
+ size_t offset;
+ u16 sequence;
+ u16 mask;
+
+ if (content_len < 4 || response_capacity < 3)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ if (!omci_me_active(agent, class_id))
+ return OMCI_RESULT_UNKNOWN_ME;
+ if (!omci_me_action_allowed(agent, class_id, OMCI_MSG_TYPE_GET_NEXT))
+ return OMCI_RESULT_NOT_SUPPORTED;
+ mask = get_unaligned_be16(content);
+ sequence = get_unaligned_be16(content + 2);
+ if (hweight16(mask) != 1)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ if (!agent->table_snapshot ||
+ time_after(jiffies, agent->table_snapshot_jiffies +
+ OMCI_TABLE_SNAPSHOT_TIMEOUT) ||
+ agent->table_snapshot_class_id != class_id ||
+ agent->table_snapshot_entity_id != entity_id ||
+ agent->table_snapshot_mask != mask)
+ return OMCI_RESULT_PARAMETER_ERROR;
+
+ fragment_len = device_id == OMCI_BASELINE_DEV_ID ?
+ OMCI_BASELINE_TABLE_FRAGMENT_LEN : response_capacity - 3;
+ if (sequence > SIZE_MAX / fragment_len)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ offset = (size_t)sequence * fragment_len;
+ if (offset >= agent->table_snapshot_len)
+ return OMCI_RESULT_PARAMETER_ERROR;
+
+ copy_len = min(fragment_len, agent->table_snapshot_len - offset);
+ memset(response_content, 0, response_capacity);
+ response_content[0] = OMCI_RESULT_SUCCESS;
+ put_unaligned_be16(mask, response_content + 1);
+ memcpy(response_content + 3, agent->table_snapshot + offset, copy_len);
+ *response_len = 3 + copy_len;
+ agent->table_snapshot_jiffies = jiffies;
+ return OMCI_RESULT_SUCCESS;
+}
+
+static bool omci_mib_object_uploadable(const struct omci_agent *agent,
+ const struct omci_mib_object *object)
+{
+ const struct omci_me_desc *desc;
+
+ if (!omci_mib_object_active(agent, object))
+ return false;
+ desc = omci_me_lookup(agent, object->class_id);
+
+ return !desc || !(desc->flags & OMCI_ME_F_NO_MIB_UPLOAD);
+}
+
+static unsigned int omci_mib_count_locked(struct omci_agent *agent)
+{
+ struct omci_mib_object *object;
+ unsigned long index;
+ unsigned int count = 0;
+
+ xa_for_each(&agent->mib, index, object)
+ if (omci_mib_object_uploadable(agent, object))
+ count++;
+ return count;
+}
+
+static void omci_agent_reset_olt_objects_locked(struct omci_agent *agent)
+{
+ struct omci_mib_object *object;
+ unsigned long index;
+
+ xa_for_each(&agent->mib, index, object) {
+ if (object->origin != OMCI_MIB_ORIGIN_OLT)
+ continue;
+ xa_erase(&agent->mib, index);
+ kfree(object);
+ }
+}
+
+static int omci_agent_mib_reset_locked(struct omci_device *odev, bool all,
+ bool *profile_changed)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_mib_object *object;
+ unsigned long index;
+ int reset_ret;
+ int ret;
+
+ omci_agent_reset_table_snapshot_locked(agent);
+ reset_ret = omci_agent_clear_services_locked(odev);
+
+ if (all) {
+ xa_for_each(&agent->mib, index, object) {
+ xa_erase(&agent->mib, index);
+ kfree(object);
+ }
+ } else {
+ omci_agent_reset_olt_objects_locked(agent);
+ }
+
+ /*
+ * A MIB reset restores ONU-created MEs to their initial values. Rebuild
+ * them as well as the selected profile's vendor MEs so repeated resets
+ * expose the same MIB and capability table as a fresh agent.
+ */
+ ret = omci_agent_populate_defaults(odev);
+ if (!ret)
+ ret = omci_agent_seed_profile_locked(odev,
+ agent->profile_effective);
+ if (!ret)
+ ret = omci_agent_profile_refresh_locked(odev, NULL, profile_changed);
+
+ omci_agent_set_mib_sync_locked(agent, 0);
+ agent->upload_index = 0;
+ omci_agent_reset_duplicate_locked(agent);
+
+ /*
+ * omci_agent_clear_services_locked() above tore down every applied
+ * service. The MIB objects it left in place (or just rebuilt) may
+ * still describe a complete datapath - e.g. a profile's default-
+ * seeded VEIP bridge, which a MIB Reset never touches since it only
+ * erases OLT-origin objects. Without this, a MIB Reset (which every
+ * OLT sends as a normal part of post-activation provisioning)
+ * leaves the ONU in O5 with OMCI up and no datapath, indefinitely,
+ * until some unrelated event happens to trigger reconcile again.
+ */
+ if (!ret)
+ omci_agent_reconcile_services_locked(odev);
+
+ return ret ?: reset_ret;
+}
+
+static int omci_agent_hw_update(struct omci_device *odev,
+ struct omci_mib_object *object,
+ u8 action, const u8 *content)
+{
+ const struct omci_device_ops *ops = odev->ops;
+ const struct omci_me_desc *desc;
+ bool ignore_unsupported_uni;
+ bool enable;
+ u16 entity_id;
+ u16 value;
+ int ret;
+
+ desc = omci_me_lookup(&odev->agent, object->class_id);
+ if (action != OMCI_MSG_TYPE_DELETE && desc &&
+ (desc->flags & OMCI_ME_F_DATAPATH) &&
+ desc->support == OMCI_CLASS_SUPPORT_SHADOW)
+ return -EOPNOTSUPP;
+
+ switch (object->class_id) {
+ case OMCI_CLASS_TCONT:
+ if (!ops->set_tcont)
+ return 0;
+ if (action == OMCI_MSG_TYPE_DELETE)
+ return ops->set_tcont(odev, object->entity_id, 0xffff,
+ false);
+ value = get_unaligned_be16(object->data);
+ return ops->set_tcont(odev, object->entity_id, value, true);
+ case OMCI_CLASS_GEM_PORT_CTP:
+ if (!ops->set_gem_port)
+ return 0;
+ return ops->set_gem_port(odev, object->entity_id,
+ get_unaligned_be16(object->data),
+ get_unaligned_be16(object->data + 2),
+ object->data[4],
+ action != OMCI_MSG_TYPE_DELETE, false);
+ case OMCI_CLASS_PPTP_ETHERNET_UNI:
+ case OMCI_CLASS_VEIP:
+ if (!ops->set_uni)
+ return 0;
+ if (action == OMCI_MSG_TYPE_DELETE)
+ enable = false;
+ else if (object->class_id == OMCI_CLASS_PPTP_ETHERNET_UNI)
+ enable = object->data[4] == 0;
+ else
+ enable = object->data[0] == 0;
+ entity_id = omci_profile_normalize_uni_entity(
+ odev->agent.profile_effective, object->entity_id);
+ ret = ops->set_uni(odev, entity_id, enable);
+ ignore_unsupported_uni = odev->agent.profile_quirks &
+ OMCI_OLT_QUIRK_IGNORE_UNSUPPORTED_UNI;
+ if (ret == -EOPNOTSUPP && ignore_unsupported_uni)
+ return 0;
+ return ret;
+ default:
+ return 0;
+ }
+}
+
+static bool
+omci_agent_datapath_class(const struct omci_agent *agent, u16 class_id)
+{
+ const struct omci_me_desc *desc = omci_me_lookup(agent, class_id);
+
+ return desc && (desc->flags & OMCI_ME_F_DATAPATH);
+}
+
+static void omci_agent_free_service_array(struct xarray *services)
+{
+ struct omci_service_state *state;
+ unsigned long index;
+
+ xa_for_each(services, index, state) {
+ xa_erase(services, index);
+ kfree(state);
+ }
+}
+
+static int omci_agent_clear_services_locked(struct omci_device *odev)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_service_state *state;
+ unsigned long index;
+ int first_error = 0;
+ int ret;
+
+ xa_for_each(&agent->services, index, state) {
+ if (odev->ops->delete_service) {
+ ret = odev->ops->delete_service(odev, state->config.cookie);
+ if (ret && ret != -ENOENT && !first_error)
+ first_error = ret;
+ }
+ xa_erase(&agent->services, index);
+ kfree(state);
+ }
+
+ return first_error;
+}
+
+static int omci_agent_stage_service(struct xarray *services,
+ const struct omci_service_config *service)
+{
+ struct omci_service_state *state;
+ void *old;
+
+ state = kmalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return -ENOMEM;
+ state->config = *service;
+
+ old = xa_store(services, service->cookie, state, GFP_KERNEL);
+ if (xa_is_err(old)) {
+ kfree(state);
+ return xa_err(old);
+ }
+ kfree(old);
+ return 0;
+}
+
+static int omci_agent_apply_services_locked(struct omci_device *odev,
+ struct xarray *desired)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_service_state *state;
+ struct omci_service_state *old_state;
+ unsigned long index;
+ int ret = 0;
+
+ if (!xa_empty(desired) && !odev->ops->replace_service)
+ return -EOPNOTSUPP;
+
+ /* Reserve all new cookies before changing hardware state. */
+ xa_for_each(desired, index, state) {
+ if (xa_load(&agent->services, index))
+ continue;
+ ret = xa_reserve(&agent->services, index, GFP_KERNEL);
+ if (ret)
+ goto release_reservations;
+ }
+
+ /* Add or replace all desired rules while the previous set remains live. */
+ xa_for_each(desired, index, state) {
+ ret = odev->ops->replace_service(odev, &state->config);
+ if (ret) {
+ dev_warn(odev->parent,
+ "OMCI install failed for service %#x (UNI %#x GEM %u T-CONT %#x
queue %u): %d; rolling back\n",
+ state->config.cookie,
+ state->config.uni_entity_id,
+ state->config.gem_port_id,
+ state->config.tcont_entity_id,
+ state->config.queue, ret);
+ goto rollback_hardware;
+ }
+ }
+
+ /* Delete rules that are no longer part of the resolved service graph. */
+ xa_for_each(&agent->services, index, old_state) {
+ if (xa_load(desired, index))
+ continue;
+ if (!odev->ops->delete_service)
+ continue;
+ ret = odev->ops->delete_service(odev, old_state->config.cookie);
+ if (ret && ret != -ENOENT)
+ goto rollback_hardware;
+ }
+
+ /* Hardware is consistent; replace the software snapshot. */
+ omci_agent_free_service_array(&agent->services);
+ xa_for_each(desired, index, state) {
+ void *entry;
+
+ xa_erase(desired, index);
+ entry = xa_store(&agent->services, index, state, GFP_KERNEL);
+ if (WARN_ON(xa_is_err(entry))) {
+ kfree(state);
+ continue;
+ }
+ kfree(entry);
+ }
+ return 0;
+
+rollback_hardware:
+ /* Restore every old rule, including rules deleted before a failure. */
+ if (odev->ops->replace_service)
+ xa_for_each(&agent->services, index, old_state)
+ odev->ops->replace_service(odev, &old_state->config);
+
+ /* Remove newly introduced cookies that were not in the old snapshot. */
+ if (odev->ops->delete_service)
+ xa_for_each(desired, index, state)
+ if (!xa_load(&agent->services, index))
+ odev->ops->delete_service(odev,
+ state->config.cookie);
+
+release_reservations:
+ xa_for_each(desired, index, state)
+ if (!xa_load(&agent->services, index))
+ xa_release(&agent->services, index);
+ return ret;
+}
+
+static bool omci_agent_uni_enabled(struct omci_agent *agent, u8 tp_type,
+ u16 entity_id)
+{
+ struct omci_mib_object *object;
+
+ if (tp_type == OMCI_BRIDGE_TP_PPTP_ETH_UNI) {
+ object = omci_mib_lookup(agent, OMCI_CLASS_PPTP_ETHERNET_UNI,
+ entity_id);
+ return object && object->data[4] == 0;
+ }
+ if (tp_type == OMCI_BRIDGE_TP_VEIP) {
+ object = omci_mib_lookup(agent, OMCI_CLASS_VEIP, entity_id);
+ return object && object->data[0] == 0;
+ }
+
+ return false;
+}
+
+static u32 omci_agent_service_cookie(u16 lan_port, u16 gem_ctp,
+ u16 gem_port, u16 selector)
+{
+ u32 cookie;
+
+ cookie = jhash_3words(((u32)lan_port << 16) | gem_ctp,
+ ((u32)gem_port << 16) | selector,
+ 0x4f4d4349, 0);
+
+ return cookie ?: 1;
+}
+
+static bool omci_agent_service_vlan_valid(u16 vid)
+{
+ return vid > 0 && vid < VLAN_VID_MASK;
+}
+
+static int
+omci_agent_stage_service_rule_locked(struct omci_device *odev,
+ struct xarray *services,
+ u16 lan_port_entity,
+ u16 uni_entity, u16 gem_iwtp_entity,
+ u8 mapper_pcp, bool mapper_pcp_valid,
+ const struct omci_vlan_filter_entry *filter,
+ const struct omci_extended_vlan_rule *ext_rule,
+ u16 selector, bool *default_installed,
+ bool multicast, u16 ani_entity_id,
+ bool ani_valid)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_mib_object *iwtp;
+ struct omci_mib_object *gem;
+ struct omci_mib_object *tcont;
+ struct omci_service_config service = {};
+ u16 gem_ctp_entity;
+
+ iwtp = omci_mib_lookup(agent, OMCI_CLASS_GEM_IWTP, gem_iwtp_entity);
+ if (!iwtp)
+ return -ENOENT;
+ gem_ctp_entity = get_unaligned_be16(iwtp->data);
+ gem = omci_mib_lookup(agent, OMCI_CLASS_GEM_PORT_CTP, gem_ctp_entity);
+ if (!gem)
+ return -ENOENT;
+ if (gem->data[4] == OMCI_GEM_PORT_DIRECTION_ANI_TO_UNI)
+ return 0;
+
+ service.uni_entity_id = omci_profile_normalize_uni_entity(
+ agent->profile_effective, uni_entity);
+ service.gem_ctp_entity_id = gem_ctp_entity;
+ service.gem_port_id = get_unaligned_be16(gem->data);
+ service.tcont_entity_id = get_unaligned_be16(gem->data + 2);
+ tcont = omci_mib_lookup(agent, OMCI_CLASS_TCONT,
+ service.tcont_entity_id);
+ if (!tcont || get_unaligned_be16(tcont->data) == 0xffff)
+ return -ENOENT;
+ /* Carry the Alloc-ID: the backend may need it to rebuild the channel. */
+ service.alloc_id = get_unaligned_be16(tcont->data);
+ service.direction = gem->data[4];
+ service.multicast = multicast;
+ service.multicast_ani_entity_id = ani_entity_id;
+ service.multicast_ani_valid = ani_valid;
+ if (mapper_pcp_valid) {
+ service.pcp = mapper_pcp;
+ service.pcp_valid = true;
+ service.queue = mapper_pcp;
+ }
+ if (filter) {
+ service.vlan_id = filter->vid;
+ service.vlan_valid =
+ omci_agent_service_vlan_valid(filter->vid);
+ service.pcp = filter->pbit;
+ service.pcp_valid = true;
+ service.queue = filter->pbit;
+ }
+ if (ext_rule) {
+ struct omci_extended_vlan_rule rule = *ext_rule;
+ bool network_vlan = false;
+
+ omci_profile_normalize_vlan_rule(agent->profile_effective, &rule);
+ memcpy(service.vlan_treatment, rule.raw + 8,
+ sizeof(service.vlan_treatment));
+
+ /*
+ * pon0 exposes the ANI/network side of the service. Class 171
+ * therefore selects the VLAN after the OMCI treatment, not the
+ * customer-side filter VLAN. Userspace creates that network VLAN
+ * directly (for example pon0.1894), so the backend only needs the
+ * resulting classifier and does not need to rewrite the skb.
+ */
+ if (omci_agent_service_vlan_valid(rule.treat_inner_vid)) {
+ service.vlan_id = rule.treat_inner_vid;
+ network_vlan = true;
+ } else if (omci_agent_service_vlan_valid(rule.treat_outer_vid)) {
+ service.vlan_id = rule.treat_outer_vid;
+ network_vlan = true;
+ } else if (omci_agent_service_vlan_valid(rule.filter_inner_vid)) {
+ service.vlan_id = rule.filter_inner_vid;
+ network_vlan = true;
+ } else if (omci_agent_service_vlan_valid(rule.filter_outer_vid)) {
+ service.vlan_id = rule.filter_outer_vid;
+ network_vlan = true;
+ }
+ service.vlan_valid = network_vlan;
+
+ if (rule.filter_inner_pbit <= 7) {
+ service.pcp = rule.filter_inner_pbit;
+ service.pcp_valid = true;
+ service.queue = rule.filter_inner_pbit;
+ } else if (rule.filter_outer_pbit <= 7) {
+ service.pcp = rule.filter_outer_pbit;
+ service.pcp_valid = true;
+ service.queue = rule.filter_outer_pbit;
+ }
+
+ /*
+ * Preparatory Class 171 rules can describe only a customer-side
+ * rewrite and still carry no host-facing VLAN. They are valid OMCI
+ * state, but they must not make the whole mapper Set fail.
+ */
+ if (!network_vlan)
+ return -EOPNOTSUPP;
+ }
+ if (!service.vlan_valid && !service.pcp_valid && !*default_installed) {
+ service.default_service = true;
+ *default_installed = true;
+ }
+ service.cookie = omci_agent_service_cookie(lan_port_entity,
+ gem_ctp_entity, service.gem_port_id, selector);
+
+ /*
+ * Trace every rule the resolver wants, not only the ones the backend
+ * accepts, so an install failure can be attributed to a specific rule.
+ */
+ dev_dbg(odev->parent,
+ "OMCI stage service %#x: UNI %#x (raw %#x) LAN port %#x GEM %u
T-CONT %#x queue %u PCP %s%u VLAN %s%u selector %u%s%s\n",
+ service.cookie, service.uni_entity_id, uni_entity,
+ lan_port_entity, service.gem_port_id, service.tcont_entity_id,
+ service.queue, service.pcp_valid ? "" : "any/", service.pcp,
+ service.vlan_valid ? "" : "any/", service.vlan_id, selector,
+ service.multicast ? " multicast" : "",
+ service.default_service ? " default" : "");
+
+ return omci_agent_stage_service(services, &service);
+}
+
+static bool
+omci_agent_vlan_associated(const struct omci_mib_object *object,
+ u16 lan_port_entity, u16 uni_entity,
+ u16 bridge_port_entity, u16 service_entity)
+{
+ if (object->class_id == OMCI_CLASS_VLAN_TAGGING_FILTER_DATA)
+ return object->entity_id == lan_port_entity ||
+ object->entity_id == uni_entity ||
+ object->entity_id == bridge_port_entity ||
+ object->entity_id == service_entity;
+ if (object->class_id == OMCI_CLASS_EXTENDED_VLAN)
+ return object->extended_vlan.valid &&
+ (object->extended_vlan.associated_me == lan_port_entity ||
+ object->extended_vlan.associated_me == uni_entity ||
+ object->extended_vlan.associated_me == bridge_port_entity ||
+ object->extended_vlan.associated_me == service_entity ||
+ object->entity_id == lan_port_entity ||
+ object->entity_id == bridge_port_entity ||
+ object->entity_id == service_entity);
+
+ return false;
+}
+
+static int
+omci_agent_stage_path_locked(struct omci_device *odev,
+ struct xarray *services,
+ const struct omci_mib_object *lan_port,
+ u16 uni_entity, u16 bridge_port_entity,
+ u16 service_entity, u16 gem_iwtp_entity,
+ u8 pcp, bool pcp_valid, bool multicast,
+ u16 ani_entity_id, bool ani_valid,
+ bool *default_installed)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_mib_object *object;
+ unsigned long index;
+ u16 selector = pcp_valid ? 0x100 | pcp : 0;
+ bool installed = false;
+ int ret;
+
+ xa_for_each(&agent->mib, index, object) {
+ unsigned int i;
+
+ if (!omci_mib_object_active(agent, object) ||
+ !omci_agent_vlan_associated(object, lan_port->entity_id,
+ uni_entity, bridge_port_entity,
+ service_entity))
+ continue;
+ if (object->class_id == OMCI_CLASS_VLAN_TAGGING_FILTER_DATA &&
+ object->vlan_filter.valid) {
+ for (i = 0; i < object->vlan_filter.num_entries; i++) {
+ ret = omci_agent_stage_service_rule_locked(
+ odev, services, lan_port->entity_id,
+ uni_entity, gem_iwtp_entity, pcp, pcp_valid,
+ &object->vlan_filter.entries[i], NULL,
+ selector + i + 1, default_installed,
+ multicast, ani_entity_id, ani_valid);
+ if (ret == -EOPNOTSUPP)
+ continue;
+ if (ret)
+ return ret;
+ installed = true;
+ }
+ } else if (object->class_id == OMCI_CLASS_EXTENDED_VLAN &&
+ object->extended_vlan.valid) {
+ for (i = 0; i < object->extended_vlan.rule_count; i++) {
+ ret = omci_agent_stage_service_rule_locked(
+ odev, services, lan_port->entity_id,
+ uni_entity, gem_iwtp_entity, pcp, pcp_valid,
+ NULL, &object->extended_vlan.rules[i],
+ selector + i + 0x20, default_installed,
+ multicast, ani_entity_id, ani_valid);
+ if (ret == -EOPNOTSUPP)
+ continue;
+ if (ret)
+ return ret;
+ installed = true;
+ }
+ }
+ }
+
+ if (installed)
+ return 0;
+
+ return omci_agent_stage_service_rule_locked(
+ odev, services, lan_port->entity_id, uni_entity, gem_iwtp_entity,
+ pcp, pcp_valid, NULL, NULL, selector, default_installed,
+ multicast, ani_entity_id, ani_valid);
+}
+
+static int
+omci_agent_resolve_bridge_locked(struct omci_device *odev,
+ struct xarray *services,
+ const struct omci_mib_object *lan_port,
+ bool *default_installed)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_mib_object *wan_port;
+ struct omci_mib_object *mapper;
+ unsigned long index;
+ u16 bridge_id = get_unaligned_be16(lan_port->data);
+ u16 uni_entity = get_unaligned_be16(lan_port->data + 4);
+ u8 lan_type = lan_port->data[3];
+ int ret;
+
+ if (!omci_agent_uni_enabled(agent, lan_type, uni_entity))
+ return 0;
+
+ xa_for_each(&agent->mib, index, wan_port) {
+ u16 mapper_entity;
+ u16 gem_iwtp_entity;
+ u8 wan_type;
+ unsigned int pcp;
+
+ if (wan_port->class_id !=
+ OMCI_CLASS_MAC_BRIDGE_PORT_CONFIG_DATA ||
+ wan_port == lan_port ||
+ get_unaligned_be16(wan_port->data) != bridge_id)
+ continue;
+ wan_type = wan_port->data[3];
+ if (wan_type == OMCI_BRIDGE_TP_GEM_IWTP ||
+ wan_type == OMCI_BRIDGE_TP_MULTICAST_GEM_IWTP) {
+ bool multicast =
+ wan_type == OMCI_BRIDGE_TP_MULTICAST_GEM_IWTP;
+ bool ani_valid = false;
+ u16 ani_entity_id = 0;
+
+ gem_iwtp_entity = get_unaligned_be16(wan_port->data + 4);
+ if (multicast &&
+ !omci_profile_resolve_multicast_ani(
+ agent->profile_effective, wan_port->entity_id,
+ &ani_entity_id))
+ ani_valid = true;
+ ret = omci_agent_stage_path_locked(
+ odev, services, lan_port, uni_entity,
+ wan_port->entity_id, gem_iwtp_entity,
+ gem_iwtp_entity, 0, false, multicast,
+ ani_entity_id, ani_valid, default_installed);
+ if (ret && ret != -ENOENT)
+ return ret;
+ continue;
+ }
+ if (wan_type != OMCI_BRIDGE_TP_8021P_MAPPER)
+ continue;
+ mapper_entity = get_unaligned_be16(wan_port->data + 4);
+ mapper = omci_mib_lookup(agent, OMCI_CLASS_8021P_MAPPER,
+ mapper_entity);
+ if (!mapper)
+ continue;
+ for (pcp = 0; pcp < 8; pcp++) {
+ gem_iwtp_entity = get_unaligned_be16(mapper->data + 2 +
+ pcp * 2);
+ if (!gem_iwtp_entity || gem_iwtp_entity == 0xffff)
+ continue;
+ ret = omci_agent_stage_path_locked(
+ odev, services, lan_port, uni_entity,
+ wan_port->entity_id, mapper_entity,
+ gem_iwtp_entity, pcp, true, false, 0, false,
+ default_installed);
+ if (ret && ret != -ENOENT)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int omci_agent_reconcile_services_locked(struct omci_device *odev)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_mib_object *object;
+ struct xarray desired;
+ unsigned long index;
+ bool default_installed = false;
+ int ret;
+
+ xa_init(&desired);
+ xa_for_each(&agent->mib, index, object) {
+ u8 tp_type;
+
+ if (!omci_mib_object_active(agent, object) ||
+ object->class_id != OMCI_CLASS_MAC_BRIDGE_PORT_CONFIG_DATA)
+ continue;
+ tp_type = object->data[3];
+ if (tp_type != OMCI_BRIDGE_TP_PPTP_ETH_UNI &&
+ tp_type != OMCI_BRIDGE_TP_VEIP)
+ continue;
+ ret = omci_agent_resolve_bridge_locked(
+ odev, &desired, object, &default_installed);
+ if (ret)
+ goto out;
+ }
+
+ ret = omci_agent_apply_services_locked(odev, &desired);
+
+out:
+ omci_agent_free_service_array(&desired);
+ xa_destroy(&desired);
+ return ret;
+}
+
+static struct omci_mib_object *
+omci_get_or_create_locked(struct omci_agent *agent, u16 class_id,
+ u16 entity_id, bool create)
+{
+ struct omci_mib_object *object;
+ struct omci_mib_object *stored;
+ int ret;
+
+ stored = omci_mib_lookup(agent, class_id, entity_id);
+ if (stored || !create)
+ return stored;
+
+ object = kzalloc(sizeof(*object), GFP_KERNEL);
+ if (!object)
+ return NULL;
+ object->class_id = class_id;
+ object->entity_id = entity_id;
+ object->origin = OMCI_MIB_ORIGIN_OLT;
+
+ ret = omci_mib_store_locked(agent, object);
+ kfree(object);
+ if (ret)
+ return NULL;
+
+ return omci_mib_lookup(agent, class_id, entity_id);
+}
+
+static u8 omci_agent_create_locked(struct omci_device *odev,
+ u16 class_id, u16 entity_id,
+ const u8 *content, size_t content_len)
+{
+ struct omci_agent *agent = &odev->agent;
+ const struct omci_me_desc *desc;
+ struct omci_mib_object *stored = NULL;
+ struct omci_mib_object *object;
+ u8 result = OMCI_RESULT_SUCCESS;
+ bool hardware_applied = false;
+ int ret;
+
+ if (!omci_me_active(agent, class_id))
+ return OMCI_RESULT_UNKNOWN_ME;
+ if (!omci_me_action_allowed(agent, class_id, OMCI_MSG_TYPE_CREATE))
+ return OMCI_RESULT_NOT_SUPPORTED;
+ if (omci_mib_lookup(agent, class_id, entity_id))
+ return OMCI_RESULT_INSTANCE_EXISTS;
+
+ desc = omci_me_lookup(agent, class_id);
+ object = kzalloc(sizeof(*object), GFP_KERNEL);
+ if (!object)
+ return OMCI_RESULT_DEVICE_BUSY;
+ object->class_id = class_id;
+ object->entity_id = entity_id;
+ object->origin = OMCI_MIB_ORIGIN_OLT;
+ object->owner_profile = desc && (desc->flags & OMCI_ME_F_VENDOR) ?
+ agent->profile_effective : OMCI_OLT_PROFILE_UNSPEC;
+ object->data_len = desc ? desc->data_len :
+ min_t(size_t, content_len, sizeof(object->data));
+
+ if (desc) {
+ ret = omci_me_decode_create(desc, object, content, content_len);
+ } else {
+ memcpy(object->data, content,
+ min_t(size_t, content_len, sizeof(object->data)));
+ object->attr_mask = 0xffff;
+ ret = 0;
+ }
+ if (ret) {
+ result = OMCI_RESULT_PARAMETER_ERROR;
+ goto out;
+ }
+ ret = omci_mib_parse_create(object, object->data);
+ if (ret) {
+ result = OMCI_RESULT_PARAMETER_ERROR;
+ goto out;
+ }
+ ret = omci_agent_hw_update(odev, object, OMCI_MSG_TYPE_CREATE,
+ content);
+ if (ret) {
+ result = OMCI_RESULT_PROCESSING_ERROR;
+ goto out;
+ }
+ hardware_applied = true;
+ ret = omci_mib_store_locked(agent, object);
+ if (ret) {
+ result = OMCI_RESULT_DEVICE_BUSY;
+ goto rollback;
+ }
+ stored = omci_mib_lookup(agent, class_id, entity_id);
+ if (omci_agent_datapath_class(agent, class_id)) {
+ ret = omci_agent_reconcile_services_locked(odev);
+ if (ret) {
+ result = OMCI_RESULT_PROCESSING_ERROR;
+ goto rollback;
+ }
+ }
+ omci_agent_increment_mib_sync_locked(agent);
+ goto out;
+
+rollback:
+ if (stored) {
+ xa_erase(&agent->mib, omci_mib_key(class_id, entity_id));
+ kfree(stored);
+ }
+ if (hardware_applied)
+ omci_agent_hw_update(odev, object, OMCI_MSG_TYPE_DELETE,
+ object->data);
+out:
+ kfree(object);
+ return result;
+}
+
+static u8 omci_agent_set_locked(struct omci_device *odev, u16 class_id,
+ u16 entity_id, u8 action, const u8 *content,
+ size_t content_len, bool *profile_changed)
+{
+ struct omci_agent *agent = &odev->agent;
+ const struct omci_me_desc *desc;
+ struct omci_mib_object *previous = NULL;
+ struct omci_mib_object *object;
+ struct omci_mib_object *existing;
+ bool hardware_applied = false;
+ bool allow_create;
+ bool existed;
+ u16 mask;
+ int ret;
+
+ if (content_len < 2)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ if (!omci_me_active(agent, class_id))
+ return OMCI_RESULT_UNKNOWN_ME;
+ if (!omci_me_action_allowed(agent, class_id, action))
+ return OMCI_RESULT_NOT_SUPPORTED;
+
+ mask = get_unaligned_be16(content);
+ desc = omci_me_lookup(agent, class_id);
+ allow_create = agent->permissive || agent->fake_omci ||
+ omci_agent_profile_has_quirk(agent,
+ OMCI_OLT_QUIRK_ALLOW_SET_CREATE);
+ existing = omci_mib_lookup(agent, class_id, entity_id);
+ existed = !!existing;
+ if (existing) {
+ previous = kmemdup(existing, sizeof(*previous), GFP_KERNEL);
+ if (!previous)
+ return OMCI_RESULT_DEVICE_BUSY;
+ }
+ object = omci_get_or_create_locked(agent, class_id, entity_id,
+ allow_create);
+ if (!object) {
+ ret = allow_create ? OMCI_RESULT_DEVICE_BUSY :
+ OMCI_RESULT_UNKNOWN_INSTANCE;
+ goto out_free_previous;
+ }
+ if (!object->data_len)
+ object->data_len = desc ? desc->data_len : sizeof(object->data);
+ if (!object->owner_profile && desc && (desc->flags & OMCI_ME_F_VENDOR))
+ object->owner_profile = agent->profile_effective;
+
+ if (desc) {
+ ret = omci_me_decode_set(desc, object, mask, content + 2,
+ content_len - 2);
+ } else if (class_id == OMCI_CLASS_PRIORITY_QUEUE ||
+ class_id == OMCI_CLASS_TRAFFIC_SCHEDULER) {
+ ret = omci_mib_parse_set(object, mask, content + 2,
+ content_len - 2);
+ } else {
+ memcpy(object->data, content + 2,
+ min_t(size_t, content_len - 2, sizeof(object->data)));
+ object->attr_mask |= mask;
+ ret = 0;
+ }
+ if (!ret && desc && (class_id == OMCI_CLASS_OLT_G ||
+ class_id == OMCI_CLASS_VLAN_TAGGING_FILTER_DATA ||
+ class_id == OMCI_CLASS_EXTENDED_VLAN))
+ ret = omci_mib_parse_set(object, mask, content + 2,
+ content_len - 2);
+ if (!ret && !desc && class_id == OMCI_CLASS_OLT_G)
+ ret = omci_mib_parse_set(object, mask, content + 2,
+ content_len - 2);
+ if (ret)
+ goto rollback_parameter;
+
+ /* Keep ONU-created instances across a subsequent MIB reset. */
+ if (!existed)
+ object->origin = OMCI_MIB_ORIGIN_OLT;
+ if (class_id == OMCI_CLASS_OLT_G) {
+ ret = omci_agent_profile_refresh_locked(odev, &object->olt_g,
+ profile_changed);
+ if (ret)
+ goto rollback_processing;
+ }
+ ret = omci_agent_hw_update(odev, object, action, content);
+ if (ret)
+ goto rollback_processing;
+ hardware_applied = true;
+ if (omci_agent_datapath_class(agent, class_id)) {
+ ret = omci_agent_reconcile_services_locked(odev);
+ if (ret)
+ goto rollback_processing;
+ }
+ omci_agent_increment_mib_sync_locked(agent);
+ ret = OMCI_RESULT_SUCCESS;
+ goto out_free_previous;
+
+rollback_parameter:
+ if (existed) {
+ *object = *previous;
+ } else {
+ xa_erase(&agent->mib, omci_mib_key(class_id, entity_id));
+ kfree(object);
+ }
+ ret = ret == -ENOSPC ? OMCI_RESULT_DEVICE_BUSY :
+ OMCI_RESULT_PARAMETER_ERROR;
+ goto out_free_previous;
+
+rollback_processing:
+ if (existed) {
+ *object = *previous;
+ if (hardware_applied)
+ omci_agent_hw_update(odev, object, OMCI_MSG_TYPE_SET,
+ object->data);
+ } else {
+ xa_erase(&agent->mib, omci_mib_key(class_id, entity_id));
+ if (hardware_applied)
+ omci_agent_hw_update(odev, object, OMCI_MSG_TYPE_DELETE,
+ object->data);
+ kfree(object);
+ }
+ ret = OMCI_RESULT_PROCESSING_ERROR;
+
+out_free_previous:
+ kfree(previous);
+ return ret;
+}
+
+static u8 omci_agent_delete_locked(struct omci_device *odev, u16 class_id,
+ u16 entity_id, bool *profile_changed)
+{
+ struct omci_agent *agent = &odev->agent;
+ const struct omci_me_desc *desc;
+ struct omci_mib_object *object;
+ u8 restore_action;
+ int ret;
+
+ if (!omci_me_active(agent, class_id))
+ return OMCI_RESULT_UNKNOWN_ME;
+ if (!omci_me_action_allowed(agent, class_id, OMCI_MSG_TYPE_DELETE))
+ return OMCI_RESULT_NOT_SUPPORTED;
+ object = omci_mib_lookup(agent, class_id, entity_id);
+ if (!object)
+ return OMCI_RESULT_UNKNOWN_INSTANCE;
+ if (object->origin == OMCI_MIB_ORIGIN_DEFAULT)
+ return OMCI_RESULT_NOT_SUPPORTED;
+
+ desc = omci_me_lookup(agent, class_id);
+ restore_action = desc && (desc->flags & OMCI_ME_F_ONU_CREATED) ?
+ OMCI_MSG_TYPE_SET : OMCI_MSG_TYPE_CREATE;
+ object->pending_delete = true;
+
+ if (class_id == OMCI_CLASS_OLT_G) {
+ ret = omci_agent_profile_refresh_locked(odev, NULL,
+ profile_changed);
+ if (ret)
+ goto rollback_mib;
+ }
+ if (omci_agent_datapath_class(agent, class_id)) {
+ ret = omci_agent_reconcile_services_locked(odev);
+ if (ret)
+ goto rollback_profile;
+ }
+ ret = omci_agent_hw_update(odev, object, OMCI_MSG_TYPE_DELETE,
+ object->data);
+ if (ret)
+ goto rollback_services;
+
+ xa_erase(&agent->mib, omci_mib_key(class_id, entity_id));
+ kfree(object);
+ omci_agent_increment_mib_sync_locked(agent);
+ return OMCI_RESULT_SUCCESS;
+
+rollback_services:
+ object->pending_delete = false;
+ omci_agent_hw_update(odev, object, restore_action, object->data);
+ omci_agent_reconcile_services_locked(odev);
+ return OMCI_RESULT_PROCESSING_ERROR;
+rollback_profile:
+ if (class_id == OMCI_CLASS_OLT_G)
+ omci_agent_profile_refresh_locked(odev, &object->olt_g, NULL);
+rollback_mib:
+ object->pending_delete = false;
+ return OMCI_RESULT_PROCESSING_ERROR;
+}
+
+static s16 omci_power_nw_to_0p002_db(u32 power_nw, s32 offset)
+{
+ s64 value;
+
+ if (!power_nw)
+ return -32768;
+
+ value = DIV_ROUND_CLOSEST_ULL((u64)intlog10(power_nw) *
+ OMCI_OPTICAL_LEVEL_SCALE * 10,
+ 1U << 24);
+ value -= offset;
+
+ return clamp_t(s64, value, -32768, 32767);
+}
+
+static s16 omci_power_nw_to_ani_g_level(u32 power_nw)
+{
+ return omci_power_nw_to_0p002_db(power_nw,
+ OMCI_NW_TO_DBM_0P002_OFFSET);
+}
+
+static s16 omci_power_nw_to_test_level(u32 power_nw)
+{
+ return omci_power_nw_to_0p002_db(power_nw,
+ OMCI_NW_TO_DBUW_0P002_OFFSET);
+}
+
+static s16 omci_temperature_mc_to_test_level(s32 temperature_mc)
+{
+ s64 value;
+
+ value = DIV_S64_ROUND_CLOSEST((s64)temperature_mc * 256, 1000);
+
+ return clamp_t(s64, value, -32768, 32767);
+}
+
+static u16 omci_voltage_uv_to_test_level(u32 voltage_uv)
+{
+ return min_t(u32, DIV_ROUND_CLOSEST(voltage_uv, 20000), 65535);
+}
+
+static u16 omci_bias_ua_to_test_level(u32 bias_ua)
+{
+ return min_t(u32, DIV_ROUND_CLOSEST(bias_ua, 2), 65535);
+}
+
+static u8 omci_agent_get_ani_g_telemetry(struct omci_device *odev, u16 mask,
+ u8 *response_content,
+ size_t response_capacity,
+ size_t *response_len)
+{
+ struct omci_telemetry telemetry = {};
+ u8 *data = response_content + 3;
+ size_t used = 3;
+ u16 level;
+ int ret;
+
+ if (response_capacity < 3)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ memset(response_content, 0, response_capacity);
+ ret = odev->ops->get_telemetry(odev, &telemetry);
+ if (ret)
+ return OMCI_RESULT_PROCESSING_ERROR;
+
+ if ((mask & OMCI_ANI_G_RX_LEVEL_MASK) &&
+ !(telemetry.valid & OMCI_TELEMETRY_F_BOSA_RX_POWER))
+ goto attribute_failed;
+ if ((mask & OMCI_ANI_G_TX_LEVEL_MASK) &&
+ !(telemetry.valid & OMCI_TELEMETRY_F_BOSA_TX_POWER))
+ goto attribute_failed;
+ if (hweight16(mask & OMCI_ANI_G_OPTICAL_LEVEL_MASK) * sizeof(u16) >
+ response_capacity - used)
+ goto attribute_failed;
+
+ response_content[0] = OMCI_RESULT_SUCCESS;
+ put_unaligned_be16(mask, response_content + 1);
+ if (mask & OMCI_ANI_G_RX_LEVEL_MASK) {
+ level = (u16)omci_power_nw_to_ani_g_level(
+ telemetry.bosa_rx_power_nw);
+ put_unaligned_be16(level, data);
+ data += 2;
+ used += 2;
+ }
+ if (mask & OMCI_ANI_G_TX_LEVEL_MASK) {
+ level = (u16)omci_power_nw_to_ani_g_level(
+ telemetry.bosa_tx_power_nw);
+ put_unaligned_be16(level, data);
+ used += 2;
+ }
+ *response_len = used;
+ return OMCI_RESULT_SUCCESS;
+
+attribute_failed:
+ if (response_capacity < 5)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ response_content[0] = OMCI_RESULT_ATTRIBUTE_FAILED;
+ put_unaligned_be16(0, response_content + 1);
+ put_unaligned_be16(mask, response_content + 3);
+ *response_len = 5;
+ return OMCI_RESULT_ATTRIBUTE_FAILED;
+}
+
+static bool omci_agent_get_uses_ani_g_telemetry(u16 class_id, u16 entity_id,
+ u16 mask)
+{
+ return class_id == OMCI_CLASS_ANI_G &&
+ entity_id == OMCI_ANI_G_ENTITY_ID &&
+ mask &&
+ !(mask & ~OMCI_ANI_G_OPTICAL_LEVEL_MASK);
+}
+
+static u8 omci_agent_get_masked_object(const struct omci_mib_object *object,
+ const struct omci_get_attr_layout *layout,
+ size_t layout_len, u16 supported_mask,
+ u16 mask, u8 *response_content,
+ size_t response_capacity,
+ size_t *response_len)
+{
+ u8 *data = response_content + 3;
+ size_t available;
+ size_t used = 3;
+ unsigned int i;
+
+ if (response_capacity < 3)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ memset(response_content, 0, response_capacity);
+ available = response_capacity - used;
+ if (!mask || mask & ~supported_mask)
+ goto attribute_failed;
+
+ for (i = 0; i < layout_len; i++) {
+ const struct omci_get_attr_layout *attr = &layout[i];
+
+ if (!(mask & attr->mask))
+ continue;
+ if (attr->len > available)
+ goto attribute_failed;
+
+ if (attr->offset == OMCI_ATTR_VALUE_ZERO)
+ memset(data, 0, attr->len);
+ else
+ memcpy(data, object->data + attr->offset, attr->len);
+ data += attr->len;
+ used += attr->len;
+ available -= attr->len;
+ }
+
+ response_content[0] = OMCI_RESULT_SUCCESS;
+ put_unaligned_be16(mask, response_content + 1);
+ *response_len = used;
+ return OMCI_RESULT_SUCCESS;
+
+attribute_failed:
+ if (response_capacity < 5)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ response_content[0] = OMCI_RESULT_ATTRIBUTE_FAILED;
+ put_unaligned_be16(0, response_content + 1);
+ put_unaligned_be16(mask, response_content + 3);
+ *response_len = 5;
+ return OMCI_RESULT_ATTRIBUTE_FAILED;
+}
+
+static int
+omci_agent_refresh_nokia_optical(struct omci_device *odev,
+ struct omci_mib_object *object)
+{
+ struct omci_telemetry telemetry = {};
+ u16 value;
+ int ret;
+
+ if (!odev->ops->get_telemetry)
+ return -EOPNOTSUPP;
+ ret = odev->ops->get_telemetry(odev, &telemetry);
+ if (ret)
+ return ret;
+
+ object->data[0] = !!(telemetry.valid & OMCI_TELEMETRY_F_BOSA_VOLTAGE);
+ if (object->data[0]) {
+ value = min_t(u32, DIV_ROUND_CLOSEST(telemetry.bosa_voltage_uv,
+ 10000), U16_MAX);
+ put_unaligned_be16(value, object->data + 1);
+ }
+ object->data[3] = !!(telemetry.valid & OMCI_TELEMETRY_F_BOSA_RX_POWER);
+ if (object->data[3]) {
+ value = min_t(u32, DIV_ROUND_CLOSEST(telemetry.bosa_rx_power_nw,
+ 10000), U16_MAX);
+ put_unaligned_be16(value, object->data + 4);
+ }
+ object->data[6] = !!(telemetry.valid & OMCI_TELEMETRY_F_BOSA_TX_POWER);
+ if (object->data[6]) {
+ value = min_t(u32, DIV_ROUND_CLOSEST(telemetry.bosa_tx_power_nw,
+ 10000), U16_MAX);
+ put_unaligned_be16(value, object->data + 7);
+ }
+ object->data[9] = !!(telemetry.valid & OMCI_TELEMETRY_F_BOSA_BIAS);
+ if (object->data[9]) {
+ value = min_t(u64, DIV_ROUND_CLOSEST_ULL(
+ (u64)telemetry.bosa_bias_ua * 2, 1000000),
+ U16_MAX);
+ put_unaligned_be16(value, object->data + 10);
+ }
+ object->data[12] = !!(telemetry.valid &
+ OMCI_TELEMETRY_F_BOSA_TEMPERATURE);
+ if (object->data[12]) {
+ value = clamp_t(s32, DIV_S64_ROUND_CLOSEST(
+ telemetry.bosa_temperature_mc, 1000),
+ 0, U16_MAX);
+ put_unaligned_be16(value, object->data + 13);
+ }
+
+ return 0;
+}
+
+static u8 omci_agent_get_locked(struct omci_device *odev, u16 class_id,
+ u16 entity_id, const u8 *content,
+ size_t content_len, u8 *response_content,
+ size_t response_capacity,
+ size_t *response_len)
+{
+ struct omci_agent *agent = &odev->agent;
+ const struct omci_me_desc *desc;
+ struct omci_mib_object *object;
+ size_t encoded_len = 0;
+ u16 encoded_mask;
+ u16 mask;
+ int ret;
+
+ if (content_len < 2 || response_capacity < 1)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ mask = get_unaligned_be16(content);
+ if (!omci_me_active(agent, class_id))
+ return OMCI_RESULT_UNKNOWN_ME;
+ if (!omci_me_action_allowed(agent, class_id, OMCI_MSG_TYPE_GET))
+ return OMCI_RESULT_NOT_SUPPORTED;
+ if (class_id == OMCI_CLASS_OMCI)
+ return omci_agent_get_omci_table_locked(odev, entity_id, mask,
+ response_content,
+ response_capacity,
+ response_len);
+ if (class_id == OMCI_CLASS_MANAGED_ENTITY)
+ return omci_agent_get_managed_entity_locked(odev, entity_id,
+ mask, response_content, response_capacity, response_len);
+ if (class_id == OMCI_CLASS_ATTRIBUTE)
+ return omci_agent_get_attribute_locked(odev, entity_id, mask,
+ response_content, response_capacity, response_len);
+
+ if (omci_agent_get_uses_ani_g_telemetry(class_id, entity_id, mask) &&
+ odev->ops && odev->ops->get_telemetry)
+ return omci_agent_get_ani_g_telemetry(odev, mask,
+ response_content,
+ response_capacity,
+ response_len);
+
+ object = omci_get_or_create_locked(agent, class_id, entity_id,
+ agent->permissive ||
+ agent->fake_omci);
+ if (!object)
+ return OMCI_RESULT_UNKNOWN_INSTANCE;
+ if (class_id == OMCI_CLASS_NOKIA_OPTICAL_SUPERVISION &&
+ omci_agent_refresh_nokia_optical(odev, object))
+ return OMCI_RESULT_PROCESSING_ERROR;
+
+ switch (class_id) {
+ case OMCI_CLASS_ONU_G:
+ return omci_agent_get_masked_object(
+ object, omci_onu_g_attr_layout,
+ ARRAY_SIZE(omci_onu_g_attr_layout), OMCI_ONU_G_ATTR_MASK,
+ mask, response_content, response_capacity, response_len);
+ case OMCI_CLASS_ONU2_G:
+ return omci_agent_get_masked_object(
+ object, omci_onu2_g_attr_layout,
+ ARRAY_SIZE(omci_onu2_g_attr_layout), OMCI_ONU2_G_ATTR_MASK,
+ mask, response_content, response_capacity, response_len);
+ default:
+ break;
+ }
+
+ if (response_capacity < 3)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ memset(response_content, 0, response_capacity);
+ response_content[0] = OMCI_RESULT_SUCCESS;
+ if (class_id == OMCI_CLASS_PRIORITY_QUEUE) {
+ put_unaligned_be16(mask, response_content + 1);
+ ret = omci_priority_queue_serialize(object, mask,
+ response_content + 3,
+ response_capacity - 3,
+ &encoded_len);
+ if (ret)
+ goto attribute_failed;
+ } else if (class_id == OMCI_CLASS_TRAFFIC_SCHEDULER) {
+ put_unaligned_be16(mask, response_content + 1);
+ ret = omci_traffic_scheduler_serialize(object, mask,
+ response_content + 3,
+ response_capacity - 3,
+ &encoded_len);
+ if (ret)
+ goto attribute_failed;
+ } else {
+ desc = omci_me_lookup(agent, class_id);
+ if (!desc) {
+ encoded_len = min_t(size_t, object->data_len,
+ response_capacity - 3);
+ put_unaligned_be16(mask, response_content + 1);
+ memcpy(response_content + 3, object->data, encoded_len);
+ *response_len = 3 + encoded_len;
+ return OMCI_RESULT_SUCCESS;
+ }
+ ret = omci_me_encode_attributes(desc, object, mask,
+ response_content + 3,
+ response_capacity - 3,
+ &encoded_mask, &encoded_len);
+ if (ret || encoded_mask != mask)
+ goto attribute_failed;
+ put_unaligned_be16(encoded_mask, response_content + 1);
+ }
+ *response_len = 3 + encoded_len;
+ return OMCI_RESULT_SUCCESS;
+
+attribute_failed:
+ if (response_capacity < 5)
+ return OMCI_RESULT_PARAMETER_ERROR;
+ response_content[0] = OMCI_RESULT_ATTRIBUTE_FAILED;
+ put_unaligned_be16(0, response_content + 1);
+ put_unaligned_be16(mask, response_content + 3);
+ *response_len = 5;
+ return OMCI_RESULT_ATTRIBUTE_FAILED;
+}
+
+static void omci_test_result_put(u8 *field, u8 type, u16 value)
+{
+ field[0] = type;
+ put_unaligned_be16(value, field + 1);
+}
+
+static bool omci_agent_is_ani_g_test(const struct omci_wire_request *request)
+{
+ return (request->message_type & 0x1f) == OMCI_MSG_TYPE_TEST &&
+ request->class_id == OMCI_CLASS_ANI_G &&
+ request->entity_id == OMCI_ANI_G_ENTITY_ID;
+}
+
+static void omci_agent_log_wire(struct omci_device *odev,
+ const char *direction, const void *data,
+ size_t len)
+{
+ struct omci_wire_request request;
+ bool response;
+ u8 action;
+
+ if (omci_wire_decode(data, len, &request)) {
+ dev_warn(odev->parent, "OMCI %s invalid PDU: %*phN\n",
+ direction, (int)len, data);
+ return;
+ }
+
+ action = request.message_type & 0x1f;
+ response = request.message_type & 0x20;
+ if (action == OMCI_MSG_TYPE_MIB_UPLOAD && response &&
+ request.payload_len >= 2) {
+ dev_dbg(odev->parent,
+ "OMCI %s: tci=%#06x type=%#04x action=%u device=%#04x class=%u
entity=%#06x payload=%u command-count=%u\n",
+ direction, request.transaction_id, request.message_type,
+ action, request.device_id, request.class_id,
+ request.entity_id, request.payload_len,
+ get_unaligned_be16(request.payload));
+ } else if (action == OMCI_MSG_TYPE_MIB_UPLOAD_NEXT &&
+ request.payload_len >= 2) {
+ dev_dbg(odev->parent,
+ "OMCI %s: tci=%#06x type=%#04x action=%u device=%#04x class=%u
entity=%#06x payload=%u %s=%u\n",
+ direction, request.transaction_id, request.message_type,
+ action, request.device_id, request.class_id,
+ request.entity_id, request.payload_len,
+ response ? "returned-class" : "sequence",
+ get_unaligned_be16(request.payload));
+ } else if (action == OMCI_MSG_TYPE_GET_NEXT &&
+ request.payload_len >= (response ? 3 : 4)) {
+ u16 mask = response ? get_unaligned_be16(request.payload + 1) :
+ get_unaligned_be16(request.payload);
+ u16 sequence = response ? 0 :
+ get_unaligned_be16(request.payload + 2);
+
+ dev_dbg(odev->parent,
+ "OMCI %s: tci=%#06x type=%#04x action=%u device=%#04x class=%u
entity=%#06x payload=%u result=%u mask=%#06x sequence=%u\n",
+ direction, request.transaction_id, request.message_type,
+ action, request.device_id, request.class_id,
+ request.entity_id, request.payload_len,
+ response ? request.payload[0] : 0, mask, sequence);
+ } else {
+ u8 result = request.payload_len ? request.payload[0] : 0;
+
+ dev_dbg(odev->parent,
+ "OMCI %s: tci=%#06x type=%#04x action=%u device=%#04x class=%u
entity=%#06x payload=%u result=%u\n",
+ direction, request.transaction_id, request.message_type,
+ action, request.device_id, request.class_id,
+ request.entity_id, request.payload_len, result);
+ }
+ dev_dbg(odev->parent, "OMCI %s PDU: %*phN\n",
+ direction, (int)len, data);
+}
+
+static int
+omci_agent_send_ani_g_test_result(struct omci_device *odev,
+ const struct omci_wire_request *request)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_telemetry telemetry = {};
+ u8 pdu[OMCI_BASELINE_LEN_NO_MIC];
+ u8 content[17] = {};
+ size_t pdu_len;
+ u16 value;
+ int ret;
+
+ if (!odev->ops || !odev->ops->get_telemetry)
+ return -EOPNOTSUPP;
+
+ ret = odev->ops->get_telemetry(odev, &telemetry);
+ if (ret)
+ return ret;
+
+ if (telemetry.valid & OMCI_TELEMETRY_F_BOSA_VOLTAGE) {
+ value = omci_voltage_uv_to_test_level(telemetry.bosa_voltage_uv);
+ omci_test_result_put(content, OMCI_TEST_TYPE_VOLTAGE, value);
+ } else {
+ omci_test_result_put(content, OMCI_TEST_TYPE_UNSUPPORTED, 0);
+ }
+
+ if (telemetry.valid & OMCI_TELEMETRY_F_BOSA_RX_POWER) {
+ value = (u16)omci_power_nw_to_test_level(
+ telemetry.bosa_rx_power_nw);
+ omci_test_result_put(content + 3, OMCI_TEST_TYPE_RX_POWER,
+ value);
+ } else {
+ omci_test_result_put(content + 3, OMCI_TEST_TYPE_UNSUPPORTED, 0);
+ }
+
+ if (telemetry.valid & OMCI_TELEMETRY_F_BOSA_TX_POWER) {
+ value = (u16)omci_power_nw_to_test_level(
+ telemetry.bosa_tx_power_nw);
+ omci_test_result_put(content + 6, OMCI_TEST_TYPE_TX_POWER,
+ value);
+ } else {
+ omci_test_result_put(content + 6, OMCI_TEST_TYPE_UNSUPPORTED, 0);
+ }
+
+ if (telemetry.valid & OMCI_TELEMETRY_F_BOSA_BIAS) {
+ value = omci_bias_ua_to_test_level(telemetry.bosa_bias_ua);
+ omci_test_result_put(content + 9, OMCI_TEST_TYPE_BIAS, value);
+ } else {
+ omci_test_result_put(content + 9, OMCI_TEST_TYPE_UNSUPPORTED, 0);
+ }
+
+ if (telemetry.valid & OMCI_TELEMETRY_F_BOSA_TEMPERATURE) {
+ value = (u16)omci_temperature_mc_to_test_level(
+ telemetry.bosa_temperature_mc);
+ omci_test_result_put(content + 12, OMCI_TEST_TYPE_TEMPERATURE,
+ value);
+ } else {
+ omci_test_result_put(content + 12, OMCI_TEST_TYPE_UNSUPPORTED, 0);
+ }
+ put_unaligned_be16(0, content + 15);
+
+ ret = omci_wire_encode_response(request, OMCI_MSG_TYPE_TEST_RESULT,
+ content, sizeof(content), pdu,
+ sizeof(pdu), &pdu_len);
+ if (ret)
+ return ret;
+
+ omci_agent_log_wire(odev, "TX", pdu, pdu_len);
+ ret = omci_device_xmit(odev, pdu, pdu_len);
+ if (!ret)
+ atomic64_inc(&agent->responses);
+
+ return ret;
+}
+
+static int omci_agent_upload_next_locked(struct omci_agent *agent,
+ u16 sequence, u8 *content)
+{
+ const struct omci_me_desc *desc;
+ struct omci_mib_object *object;
+ unsigned long index;
+ size_t encoded_len = 0;
+ u16 encoded_mask = 0;
+ u16 upload_pos = 0;
+ int ret;
+
+ xa_for_each(&agent->mib, index, object) {
+ if (!omci_mib_object_uploadable(agent, object))
+ continue;
+ if (upload_pos++ == sequence)
+ goto found;
+ }
+
+ return -ENOENT;
+
+found:
+ put_unaligned_be16(object->class_id, content);
+ put_unaligned_be16(object->entity_id, content + 2);
+ desc = omci_me_lookup(agent, object->class_id);
+ if (desc) {
+ u16 mask = object->attr_mask & desc->mib_upload_mask;
+
+ ret = omci_me_encode_attributes(desc, object, mask,
+ content + 6, 26,
+ &encoded_mask, &encoded_len);
+ if (ret && mask)
+ return ret;
+ } else {
+ encoded_mask = object->attr_mask;
+ encoded_len = min_t(size_t, object->data_len ?: 26, 26);
+ memcpy(content + 6, object->data, encoded_len);
+ }
+ put_unaligned_be16(encoded_mask, content + 4);
+ if (encoded_len < 26)
+ memset(content + 6 + encoded_len, 0, 26 - encoded_len);
+ agent->upload_index = sequence + 1;
+ return 0;
+}
+
+static int
+omci_agent_build_response_locked(struct omci_device *odev,
+ const struct omci_wire_request *request,
+ u8 *content, size_t capacity,
+ size_t *content_len, bool *unsupported,
+ bool *fake, bool *profile_changed)
+{
+ struct omci_agent *agent = &odev->agent;
+ u8 action = request->message_type & 0x1f;
+ u16 class_id = request->class_id;
+ u16 entity_id = request->entity_id;
+ bool shadow_missing;
+ u16 sequence;
+ u16 mask = 0;
+ u8 result = OMCI_RESULT_SUCCESS;
+ int ret;
+
+ if (!capacity)
+ return -EMSGSIZE;
+ memset(content, 0, capacity);
+ shadow_missing = !omci_mib_lookup(agent, class_id, entity_id);
+ *unsupported = false;
+ *fake = false;
+ *profile_changed = false;
+ *content_len = 1;
+
+ switch (action) {
+ case OMCI_MSG_TYPE_CREATE:
+ result = omci_agent_create_locked(odev, class_id, entity_id,
+ request->payload,
+ request->payload_len);
+ if (omci_agent_should_fake_result(agent, class_id, result)) {
+ result = OMCI_RESULT_SUCCESS;
+ *fake = true;
+ }
+ content[0] = result;
+ break;
+ case OMCI_MSG_TYPE_DELETE:
+ result = omci_agent_delete_locked(odev, class_id, entity_id,
+ profile_changed);
+ if (omci_agent_should_fake_result(agent, class_id, result)) {
+ result = OMCI_RESULT_SUCCESS;
+ *fake = true;
+ }
+ content[0] = result;
+ break;
+ case OMCI_MSG_TYPE_SET:
+ case OMCI_MSG_TYPE_SET_TABLE:
+ result = omci_agent_set_locked(odev, class_id, entity_id, action,
+ request->payload,
+ request->payload_len,
+ profile_changed);
+ if (omci_agent_should_fake_result(agent, class_id, result)) {
+ result = OMCI_RESULT_SUCCESS;
+ *fake = true;
+ } else if (!result && omci_agent_fakes_unsupported(agent) &&
+ !agent->permissive && shadow_missing) {
+ *fake = true;
+ }
+ if (request->payload_len >= 2)
+ mask = get_unaligned_be16(request->payload);
+ if (capacity < 5)
+ return -EMSGSIZE;
+ content[0] = result;
+ put_unaligned_be16(0, content + 1);
+ put_unaligned_be16(result ? mask : 0, content + 3);
+ *content_len = 5;
+ break;
+ case OMCI_MSG_TYPE_GET:
+ case OMCI_MSG_TYPE_GET_CURRENT_DATA:
+ result = omci_agent_get_locked(odev, class_id, entity_id,
+ request->payload,
+ request->payload_len, content,
+ capacity, content_len);
+ if (omci_agent_should_fake_result(agent, class_id, result)) {
+ content[0] = OMCI_RESULT_SUCCESS;
+ *content_len = 1;
+ *fake = true;
+ } else if (!result && omci_agent_fakes_unsupported(agent) &&
+ !agent->permissive && shadow_missing) {
+ *fake = true;
+ }
+ if (result && !*fake) {
+ content[0] = result;
+ *content_len = 1;
+ }
+ break;
+ case OMCI_MSG_TYPE_GET_NEXT:
+ result = omci_agent_get_next_locked(odev, class_id, entity_id,
+ request->device_id,
+ request->payload,
+ request->payload_len,
+ content, capacity,
+ content_len);
+ if (result) {
+ content[0] = result;
+ *content_len = 1;
+ }
+ break;
+ case OMCI_MSG_TYPE_GET_ALL_ALARMS:
+ if (capacity < 3)
+ return -EMSGSIZE;
+ content[0] = OMCI_RESULT_SUCCESS;
+ put_unaligned_be16(0, content + 1);
+ *content_len = 3;
+ break;
+ case OMCI_MSG_TYPE_GET_ALL_ALARMS_NEXT:
+ content[0] = OMCI_RESULT_UNKNOWN_INSTANCE;
+ break;
+ case OMCI_MSG_TYPE_MIB_UPLOAD:
+ if (capacity < 2)
+ return -EMSGSIZE;
+ agent->upload_index = 0;
+ put_unaligned_be16(omci_mib_count_locked(agent), content);
+ *content_len = 2;
+ break;
+ case OMCI_MSG_TYPE_MIB_UPLOAD_NEXT:
+ if (request->payload_len < 2 || capacity < 32)
+ return -EMSGSIZE;
+ sequence = get_unaligned_be16(request->payload);
+ ret = omci_agent_upload_next_locked(agent, sequence, content);
+ if (ret)
+ memset(content, 0, 32);
+ *content_len = 32;
+ break;
+ case OMCI_MSG_TYPE_MIB_RESET:
+ if (omci_agent_mib_reset_locked(odev, false, profile_changed))
+ content[0] = OMCI_RESULT_PROCESSING_ERROR;
+ else
+ content[0] = OMCI_RESULT_SUCCESS;
+ break;
+ case OMCI_MSG_TYPE_SYNC_TIME:
+ case OMCI_MSG_TYPE_REBOOT:
+ case OMCI_MSG_TYPE_TEST:
+ content[0] = OMCI_RESULT_SUCCESS;
+ break;
+ case OMCI_MSG_TYPE_START_SW_DOWNLOAD:
+ case OMCI_MSG_TYPE_DOWNLOAD_SECTION:
+ case OMCI_MSG_TYPE_END_SW_DOWNLOAD:
+ case OMCI_MSG_TYPE_ACTIVATE_SW:
+ case OMCI_MSG_TYPE_COMMIT_SW:
+ default:
+ if (omci_agent_fakes_unsupported(agent)) {
+ content[0] = OMCI_RESULT_SUCCESS;
+ *fake = true;
+ } else {
+ content[0] = OMCI_RESULT_NOT_SUPPORTED;
+ *unsupported = true;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+static void omci_agent_reset_duplicate_locked(struct omci_agent *agent)
+{
+ dev_kfree_skb(agent->last_request);
+ dev_kfree_skb(agent->last_response);
+ agent->last_request = NULL;
+ agent->last_response = NULL;
+ agent->last_request_hash = 0;
+ agent->last_response_fake = false;
+}
+
+static int
+omci_agent_cache_exchange_locked(struct omci_agent *agent,
+ const struct sk_buff *request,
+ const void *response, size_t response_len,
+ bool fake)
+{
+ struct sk_buff *cached_request;
+ struct sk_buff *cached_response;
+
+ cached_request = alloc_skb(request->len, GFP_KERNEL);
+ if (!cached_request)
+ return -ENOMEM;
+ skb_put_data(cached_request, request->data, request->len);
+ cached_response = alloc_skb(response_len, GFP_KERNEL);
+ if (!cached_response) {
+ kfree_skb(cached_request);
+ return -ENOMEM;
+ }
+ skb_put_data(cached_response, response, response_len);
+
+ omci_agent_reset_duplicate_locked(agent);
+ agent->last_request = cached_request;
+ agent->last_response = cached_response;
+ agent->last_request_hash = jhash(request->data, request->len, 0);
+ agent->last_response_fake = fake;
+ return 0;
+}
+
+static void
+omci_agent_report_operational(struct omci_device *odev, bool operational)
+{
+ if (odev->ops->set_operational)
+ odev->ops->set_operational(odev, operational);
+ omci_device_notify(odev, OMCI_EVENT_OPERATIONAL_CHANGE);
+}
+
+void omci_agent_receive(struct omci_device *odev, const struct sk_buff *skb)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_wire_request request;
+ u8 *response = NULL;
+ u8 *content = NULL;
+ size_t response_len = 0;
+ size_t content_len = 0;
+ size_t content_capacity;
+ u32 request_hash;
+ bool unsupported = false;
+ bool duplicate = false;
+ bool fake = false;
+ bool profile_changed = false;
+ bool operational_changed = false;
+ bool channel_up;
+ bool ani_g_test;
+ int ret;
+
+ ret = omci_wire_decode(skb->data, skb->len, &request);
+ if (ret)
+ return;
+ omci_agent_log_wire(odev, "RX", skb->data, skb->len);
+ ani_g_test = omci_agent_is_ani_g_test(&request);
+ request_hash = jhash(skb->data, skb->len, 0);
+
+ response = kmalloc(OMCI_MAX_PDU_LEN, GFP_KERNEL);
+ content_capacity = request.device_id == OMCI_BASELINE_DEV_ID ? 32 :
+ OMCI_MAX_PDU_LEN - OMCI_EXTENDED_HEADER_LEN;
+ content = kzalloc(content_capacity, GFP_KERNEL);
+ if (!response || !content)
+ goto out;
+
+ mutex_lock(&agent->lock);
+ if (!agent->enabled) {
+ mutex_unlock(&agent->lock);
+ goto out;
+ }
+
+ if (agent->last_request && agent->last_response &&
+ agent->last_request_hash == request_hash &&
+ agent->last_request->len == skb->len &&
+ !memcmp(agent->last_request->data, skb->data, skb->len)) {
+ response_len = agent->last_response->len;
+ memcpy(response, agent->last_response->data, response_len);
+ fake = agent->last_response_fake;
+ duplicate = true;
+ } else {
+ ret = omci_agent_build_response_locked(
+ odev, &request, content, content_capacity, &content_len,
+ &unsupported, &fake, &profile_changed);
+ if (!ret)
+ ret = omci_wire_encode_response(
+ &request, request.message_type & 0x1f,
+ content, content_len, response, OMCI_MAX_PDU_LEN,
+ &response_len);
+ if (ret) {
+ mutex_unlock(&agent->lock);
+ goto out;
+ }
+ omci_agent_cache_exchange_locked(agent, skb, response,
+ response_len, fake);
+ }
+ mutex_unlock(&agent->lock);
+
+ omci_agent_log_wire(odev, "TX", response, response_len);
+ ret = omci_device_xmit(odev, response, response_len);
+ if (!ret) {
+ atomic64_inc(&agent->responses);
+ if (!duplicate && !fake && !unsupported) {
+ spin_lock_bh(&odev->state_lock);
+ channel_up = odev->channel_up;
+ spin_unlock_bh(&odev->state_lock);
+
+ mutex_lock(&agent->lock);
+ if (channel_up && agent->enabled && !agent->operational) {
+ agent->operational = true;
+ operational_changed = true;
+ }
+ mutex_unlock(&agent->lock);
+ }
+ if (ani_g_test) {
+ ret = omci_agent_send_ani_g_test_result(odev, &request);
+ if (ret)
+ dev_dbg(odev->parent,
+ "OMCI ANI-G test result unavailable: %d\n",
+ ret);
+ }
+ }
+ if (duplicate)
+ atomic64_inc(&agent->duplicates);
+ if (unsupported) {
+ atomic64_inc(&agent->unsupported);
+ omci_device_notify(odev, OMCI_EVENT_UNSUPPORTED);
+ }
+ if (fake)
+ atomic64_inc(&agent->fake_responses);
+ if (profile_changed)
+ omci_device_notify(odev, OMCI_EVENT_PROFILE_CHANGE);
+ if (operational_changed)
+ omci_agent_report_operational(odev, true);
+
+out:
+ kfree(content);
+ kfree(response);
+}
+
+int omci_agent_send_dying_gasp(struct omci_device *odev)
+{
+ struct omci_agent *agent = &odev->agent;
+ u8 pdu[OMCI_BASELINE_LEN_NO_MIC] = {};
+ u8 sequence;
+ bool enabled;
+ int ret;
+
+ mutex_lock(&agent->lock);
+ enabled = agent->enabled && agent->dying_gasp;
+ if (enabled) {
+ sequence = ++agent->alarm_sequence;
+ if (!sequence)
+ sequence = ++agent->alarm_sequence;
+ }
+ mutex_unlock(&agent->lock);
+ if (!enabled)
+ return -EOPNOTSUPP;
+
+ pdu[2] = OMCI_MSG_TYPE_ALARM_NOTIFICATION;
+ pdu[3] = OMCI_BASELINE_DEV_ID;
+ put_unaligned_be16(OMCI_CLASS_ONU_G, pdu + 4);
+ put_unaligned_be16(0, pdu + 6);
+ pdu[8 + OMCI_ONU_G_DYING_GASP_ALARM / 8] |=
+ BIT(7 - (OMCI_ONU_G_DYING_GASP_ALARM % 8));
+ pdu[OMCI_ALARM_SEQUENCE_OFFSET] = sequence;
+ put_unaligned_be32(40, pdu + 40);
+
+ ret = omci_device_xmit(odev, pdu, sizeof(pdu));
+ if (ret)
+ return ret;
+
+ omci_agent_log_wire(odev, "TX", pdu, sizeof(pdu));
+ omci_device_notify(odev, OMCI_EVENT_DYING_GASP);
+ return 0;
+}
+
+void omci_agent_channel_changed(struct omci_device *odev, bool valid)
+{
+ struct omci_agent *agent = &odev->agent;
+ bool operational_changed;
+
+ mutex_lock(&agent->lock);
+ operational_changed = agent->operational;
+ agent->operational = false;
+ omci_agent_reset_duplicate_locked(agent);
+ omci_agent_reset_table_snapshot_locked(agent);
+ agent->upload_index = 0;
+ if (!valid) {
+ agent->alarm_sequence = 0;
+ omci_agent_clear_services_locked(odev);
+ } else {
+ omci_agent_reconcile_services_locked(odev);
+ }
+ mutex_unlock(&agent->lock);
+
+ if (operational_changed)
+ omci_agent_report_operational(odev, false);
+}
+
+int omci_agent_put_status(struct sk_buff *msg, struct omci_device *odev)
+{
+ struct omci_agent *agent = &odev->agent;
+ u32 count;
+ u32 profile_quirks;
+ u16 sync;
+ u8 profile_configured;
+ u8 profile_effective;
+ u8 profile_forced;
+ u8 onu_type;
+ bool enabled;
+ bool permissive;
+ bool fake_omci;
+ bool dying_gasp;
+ bool operational;
+
+ mutex_lock(&agent->lock);
+ count = omci_mib_count_locked(agent);
+ sync = agent->mib_sync;
+ enabled = agent->enabled;
+ permissive = agent->permissive;
+ fake_omci = agent->fake_omci;
+ dying_gasp = agent->dying_gasp;
+ operational = agent->operational;
+ profile_configured = agent->config.olt_profile;
+ profile_effective = agent->profile_effective;
+ profile_forced = agent->config.olt_profile_force;
+ onu_type = agent->config.onu_type;
+ profile_quirks = agent->profile_quirks;
+ mutex_unlock(&agent->lock);
+
+ if (nla_put_u8(msg, OMCI_ATTR_AGENT_ENABLED, enabled) ||
+ nla_put_u8(msg, OMCI_ATTR_AGENT_OPERATIONAL, operational) ||
+ nla_put_u8(msg, OMCI_ATTR_AGENT_PERMISSIVE, permissive) ||
+ nla_put_u8(msg, OMCI_ATTR_AGENT_FAKE_OMCI, fake_omci) ||
+ nla_put_u8(msg, OMCI_ATTR_AGENT_DYING_GASP, dying_gasp) ||
+ nla_put_u8(msg, OMCI_ATTR_ONU_TYPE, onu_type) ||
+ nla_put_u8(msg, OMCI_ATTR_OLT_PROFILE_CONFIGURED,
+ profile_configured) ||
+ nla_put_u8(msg, OMCI_ATTR_OLT_PROFILE_EFFECTIVE,
+ profile_effective) ||
+ nla_put_u8(msg, OMCI_ATTR_OLT_PROFILE_FORCED, profile_forced) ||
+ nla_put_u32(msg, OMCI_ATTR_OLT_PROFILE_QUIRKS, profile_quirks) ||
+ nla_put_u16(msg, OMCI_ATTR_MIB_SYNC, sync) ||
+ nla_put_u32(msg, OMCI_ATTR_MIB_OBJECTS, count) ||
+ nla_put_u64_64bit(msg, OMCI_ATTR_AGENT_RESPONSES,
+ atomic64_read(&agent->responses), OMCI_ATTR_PAD) ||
+ nla_put_u64_64bit(msg, OMCI_ATTR_AGENT_DUPLICATES,
+ atomic64_read(&agent->duplicates), OMCI_ATTR_PAD) ||
+ nla_put_u64_64bit(msg, OMCI_ATTR_AGENT_UNSUPPORTED,
+ atomic64_read(&agent->unsupported), OMCI_ATTR_PAD) ||
+ nla_put_u64_64bit(msg, OMCI_ATTR_AGENT_FAKE_RESPONSES,
+ atomic64_read(&agent->fake_responses), OMCI_ATTR_PAD))
+ return -EMSGSIZE;
+ return 0;
+}
+
+int omci_agent_config_get(struct omci_device *odev, u16 key,
+ void *value, size_t *len)
+{
+ struct omci_agent *agent = &odev->agent;
+ const void *source = NULL;
+ size_t source_len = 0;
+ u8 scalar;
+
+ mutex_lock(&agent->lock);
+ switch (key) {
+ case OMCI_CONFIG_SERIAL_NUMBER:
+ source = agent->config.serial_number;
+ source_len = sizeof(agent->config.serial_number);
+ break;
+ case OMCI_CONFIG_VENDOR_ID:
+ source = agent->config.vendor_id;
+ source_len = sizeof(agent->config.vendor_id);
+ break;
+ case OMCI_CONFIG_VERSION:
+ case OMCI_CONFIG_HARDWARE_VERSION:
+ source = agent->config.version;
+ source_len = strnlen(agent->config.version,
+ sizeof(agent->config.version));
+ break;
+ case OMCI_CONFIG_SOFTWARE_VERSION_0:
+ source = agent->config.software_version[0];
+ source_len = strnlen(agent->config.software_version[0],
+ sizeof(agent->config.software_version[0]));
+ break;
+ case OMCI_CONFIG_SOFTWARE_VERSION_1:
+ source = agent->config.software_version[1];
+ source_len = strnlen(agent->config.software_version[1],
+ sizeof(agent->config.software_version[1]));
+ break;
+ case OMCI_CONFIG_EQUIPMENT_ID:
+ source = agent->config.equipment_id;
+ source_len = strnlen(agent->config.equipment_id,
+ sizeof(agent->config.equipment_id));
+ break;
+ case OMCI_CONFIG_PASSWORD:
+ source = agent->config.password;
+ source_len = sizeof(agent->config.password);
+ break;
+ case OMCI_CONFIG_TRAFFIC_MGMT_OPTION:
+ scalar = agent->config.traffic_mgmt_option;
+ source = &scalar;
+ source_len = sizeof(scalar);
+ break;
+ case OMCI_CONFIG_ONU_TYPE:
+ scalar = agent->config.onu_type;
+ source = &scalar;
+ source_len = sizeof(scalar);
+ break;
+ case OMCI_CONFIG_UNI_COUNT:
+ scalar = agent->config.uni_count;
+ source = &scalar;
+ source_len = sizeof(scalar);
+ break;
+ case OMCI_CONFIG_AGENT_ENABLED:
+ scalar = agent->enabled;
+ source = &scalar;
+ source_len = sizeof(scalar);
+ break;
+ case OMCI_CONFIG_AGENT_PERMISSIVE:
+ scalar = agent->permissive;
+ source = &scalar;
+ source_len = sizeof(scalar);
+ break;
+ case OMCI_CONFIG_AGENT_FAKE_OMCI:
+ scalar = agent->fake_omci;
+ source = &scalar;
+ source_len = sizeof(scalar);
+ break;
+ case OMCI_CONFIG_AGENT_DYING_GASP:
+ scalar = agent->dying_gasp;
+ source = &scalar;
+ source_len = sizeof(scalar);
+ break;
+ case OMCI_CONFIG_OMCC_VERSION:
+ scalar = agent->config.omcc_version;
+ source = &scalar;
+ source_len = sizeof(scalar);
+ break;
+ case OMCI_CONFIG_OLT_PROFILE:
+ scalar = agent->config.olt_profile;
+ source = &scalar;
+ source_len = sizeof(scalar);
+ break;
+ case OMCI_CONFIG_OLT_PROFILE_FORCE:
+ scalar = agent->config.olt_profile_force;
+ source = &scalar;
+ source_len = sizeof(scalar);
+ break;
+ default:
+ mutex_unlock(&agent->lock);
+ return -EINVAL;
+ }
+
+ if (*len < source_len) {
+ *len = source_len;
+ mutex_unlock(&agent->lock);
+ return -ENOSPC;
+ }
+ memcpy(value, source, source_len);
+ *len = source_len;
+ mutex_unlock(&agent->lock);
+ return 0;
+}
+
+static int
+__omci_agent_config_set_source(struct omci_device *odev, u16 key,
+ const void *value, size_t len, u8 source,
+ bool notify)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_identity identity;
+ struct omci_olt_g *olt;
+ u8 normalized[OMCI_MAX_CONFIG_VALUE];
+ size_t normalized_len = sizeof(normalized);
+ u8 old_profile_source;
+ u8 old_force_source;
+ u8 old_profile;
+ u8 old_force;
+ u8 scalar;
+ bool profile_key = false;
+ bool operational_changed = false;
+ bool changed = false;
+ int ret = 0;
+
+ if ((key >= OMCI_CONFIG_SERIAL_NUMBER &&
+ key <= OMCI_CONFIG_PASSWORD) ||
+ key == OMCI_CONFIG_HARDWARE_VERSION ||
+ key == OMCI_CONFIG_SOFTWARE_VERSION_0 ||
+ key == OMCI_CONFIG_SOFTWARE_VERSION_1) {
+ ret = omci_identity_normalize_config(key, value, len,
+ normalized, &normalized_len);
+ if (ret)
+ return ret;
+ value = normalized;
+ len = normalized_len;
+ }
+
+ mutex_lock(&agent->lock);
+ old_profile = agent->config.olt_profile;
+ old_force = agent->config.olt_profile_force;
+ old_profile_source = agent->config.olt_profile_source;
+ old_force_source = agent->config.olt_profile_force_source;
+ switch (key) {
+ case OMCI_CONFIG_SERIAL_NUMBER:
+ if (len != sizeof(agent->config.serial_number)) {
+ ret = -EINVAL;
+ break;
+ }
+ changed = memcmp(agent->config.serial_number, value, len);
+ memcpy(agent->config.serial_number, value, len);
+ agent->config.serial_source = source;
+ memcpy(agent->config.vendor_id, value,
+ sizeof(agent->config.vendor_id));
+ agent->config.vendor_source = source;
+ break;
+ case OMCI_CONFIG_VENDOR_ID:
+ if (len != sizeof(agent->config.vendor_id)) {
+ ret = -EINVAL;
+ break;
+ }
+ changed = memcmp(agent->config.vendor_id, value, len);
+ memcpy(agent->config.vendor_id, value, len);
+ agent->config.vendor_source = source;
+ memcpy(agent->config.serial_number, value,
+ sizeof(agent->config.vendor_id));
+ agent->config.serial_source = max(agent->config.serial_source,
+ source);
+ break;
+ case OMCI_CONFIG_VERSION:
+ if (len != sizeof(agent->config.version)) {
+ ret = -EINVAL;
+ break;
+ }
+ changed = memcmp(agent->config.version, value, len) ||
+ memcmp(agent->config.software_version[0], value, len) ||
+ memcmp(agent->config.software_version[1], value, len);
+ memcpy(agent->config.version, value, len);
+ memcpy(agent->config.software_version[0], value, len);
+ memcpy(agent->config.software_version[1], value, len);
+ agent->config.version_source = source;
+ agent->config.software_version_source[0] = source;
+ agent->config.software_version_source[1] = source;
+ break;
+ case OMCI_CONFIG_HARDWARE_VERSION:
+ if (len != sizeof(agent->config.version)) {
+ ret = -EINVAL;
+ break;
+ }
+ changed = memcmp(agent->config.version, value, len);
+ memcpy(agent->config.version, value, len);
+ agent->config.version_source = source;
+ break;
+ case OMCI_CONFIG_SOFTWARE_VERSION_0:
+ case OMCI_CONFIG_SOFTWARE_VERSION_1: {
+ unsigned int image = key == OMCI_CONFIG_SOFTWARE_VERSION_0 ? 0 : 1;
+
+ if (len != sizeof(agent->config.software_version[image])) {
+ ret = -EINVAL;
+ break;
+ }
+ changed = memcmp(agent->config.software_version[image], value, len);
+ memcpy(agent->config.software_version[image], value, len);
+ agent->config.software_version_source[image] = source;
+ break;
+ }
+ case OMCI_CONFIG_EQUIPMENT_ID:
+ if (!len || len > sizeof(agent->config.equipment_id)) {
+ ret = -EINVAL;
+ break;
+ }
+ changed = memcmp(agent->config.equipment_id, value, len) ||
+ memchr_inv(agent->config.equipment_id + len, 0,
+ sizeof(agent->config.equipment_id) - len);
+ memset(agent->config.equipment_id, 0,
+ sizeof(agent->config.equipment_id));
+ memcpy(agent->config.equipment_id, value, len);
+ agent->config.equipment_source = source;
+ break;
+ case OMCI_CONFIG_PASSWORD:
+ if (len != sizeof(agent->config.password)) {
+ ret = -EINVAL;
+ break;
+ }
+ changed = memcmp(agent->config.password, value, len);
+ memcpy(agent->config.password, value, len);
+ agent->config.password_source = source;
+ break;
+ case OMCI_CONFIG_OLT_PROFILE:
+ if (len != sizeof(scalar)) {
+ ret = -EINVAL;
+ break;
+ }
+ scalar = *(const u8 *)value;
+ if (!omci_profile_valid(scalar)) {
+ ret = -EINVAL;
+ break;
+ }
+ changed = agent->config.olt_profile != scalar;
+ agent->config.olt_profile = scalar;
+ agent->config.olt_profile_source = source;
+ profile_key = true;
+ break;
+ case OMCI_CONFIG_OLT_PROFILE_FORCE:
+ if (len != sizeof(scalar)) {
+ ret = -EINVAL;
+ break;
+ }
+ scalar = *(const u8 *)value;
+ if (!omci_profile_forceable(scalar)) {
+ ret = -EINVAL;
+ break;
+ }
+ changed = agent->config.olt_profile_force != scalar;
+ agent->config.olt_profile_force = scalar;
+ agent->config.olt_profile_force_source = source;
+ profile_key = true;
+ break;
+ case OMCI_CONFIG_AGENT_DYING_GASP:
+ if (len != sizeof(scalar)) {
+ ret = -EINVAL;
+ break;
+ }
+ scalar = *(const u8 *)value;
+ changed = agent->dying_gasp != !!scalar;
+ agent->dying_gasp = !!scalar;
+ agent->config.dying_gasp_source = source;
+ omci_agent_reset_duplicate_locked(agent);
+ break;
+ case OMCI_CONFIG_OMCC_VERSION:
+ if (len != sizeof(scalar)) {
+ ret = -EINVAL;
+ break;
+ }
+ scalar = *(const u8 *)value;
+ if (!scalar) {
+ ret = -EINVAL;
+ break;
+ }
+ changed = agent->config.omcc_version != scalar;
+ agent->config.omcc_version = scalar;
+ agent->config.omcc_version_source = source;
+ break;
+ case OMCI_CONFIG_TRAFFIC_MGMT_OPTION:
+ case OMCI_CONFIG_ONU_TYPE:
+ case OMCI_CONFIG_UNI_COUNT:
+ case OMCI_CONFIG_AGENT_ENABLED:
+ case OMCI_CONFIG_AGENT_PERMISSIVE:
+ case OMCI_CONFIG_AGENT_FAKE_OMCI:
+ if (len != sizeof(scalar)) {
+ ret = -EINVAL;
+ break;
+ }
+ scalar = *(const u8 *)value;
+ if (key == OMCI_CONFIG_TRAFFIC_MGMT_OPTION) {
+ changed = agent->config.traffic_mgmt_option != scalar;
+ agent->config.traffic_mgmt_option = scalar;
+ } else if (key == OMCI_CONFIG_ONU_TYPE) {
+ if (scalar > OMCI_ONU_TYPE_CBU) {
+ ret = -EINVAL;
+ break;
+ }
+ changed = agent->config.onu_type != scalar;
+ agent->config.onu_type = scalar;
+ agent->config.onu_type_source = source;
+ } else if (key == OMCI_CONFIG_UNI_COUNT) {
+ scalar = clamp_t(u8, scalar, 1, 16);
+ changed = agent->config.uni_count != scalar;
+ agent->config.uni_count = scalar;
+ } else if (key == OMCI_CONFIG_AGENT_ENABLED) {
+ changed = agent->enabled != !!scalar;
+ agent->enabled = !!scalar;
+ if (!agent->enabled && agent->operational) {
+ agent->operational = false;
+ operational_changed = true;
+ }
+ } else if (key == OMCI_CONFIG_AGENT_PERMISSIVE) {
+ changed = agent->permissive != !!scalar;
+ agent->permissive = !!scalar;
+ } else {
+ changed = agent->fake_omci != !!scalar;
+ agent->fake_omci = !!scalar;
+ }
+ omci_agent_reset_duplicate_locked(agent);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ if (!ret && profile_key) {
+ olt = omci_agent_olt_g_locked(agent);
+ ret = omci_agent_profile_refresh_locked(odev, olt, NULL);
+ if (ret) {
+ agent->config.olt_profile = old_profile;
+ agent->config.olt_profile_force = old_force;
+ agent->config.olt_profile_source = old_profile_source;
+ agent->config.olt_profile_force_source = old_force_source;
+ }
+ }
+ if (!ret && changed && !profile_key) {
+ omci_agent_refresh_identity_locked(agent);
+ omci_agent_reset_duplicate_locked(agent);
+ omci_agent_reset_table_snapshot_locked(agent);
+ agent->upload_index = 0;
+ }
+ if (!ret && notify && changed)
+ omci_agent_identity_state_locked(agent, &identity);
+ mutex_unlock(&agent->lock);
+ if (operational_changed)
+ omci_agent_report_operational(odev, false);
+ if (!ret && notify && changed && odev->ops->config_changed)
+ odev->ops->config_changed(odev, key, &identity);
+ return ret;
+}
+
+int omci_agent_config_set_source(struct omci_device *odev, u16 key,
+ const void *value, size_t len, u8 source)
+{
+ return __omci_agent_config_set_source(odev, key, value, len, source,
+ false);
+}
+
+int omci_agent_config_set(struct omci_device *odev, u16 key,
+ const void *value, size_t len)
+{
+ return omci_agent_config_set_userspace(odev, key, value, len,
+ OMCI_CONFIG_SOURCE_NETLINK);
+}
+
+int omci_agent_config_set_userspace(struct omci_device *odev, u16 key,
+ const void *value, size_t len, u8 source)
+{
+ if (source != OMCI_CONFIG_SOURCE_NETLINK &&
+ source != OMCI_CONFIG_SOURCE_SYSFS)
+ return -EINVAL;
+
+ return __omci_agent_config_set_source(odev, key, value, len, source,
+ true);
+}
+
+int omci_agent_config_source_get(struct omci_device *odev, u16 key, u8 *source)
+{
+ struct omci_agent *agent = &odev->agent;
+ int ret = 0;
+
+ if (!source)
+ return -EINVAL;
+ mutex_lock(&agent->lock);
+ switch (key) {
+ case OMCI_CONFIG_SERIAL_NUMBER:
+ *source = agent->config.serial_source;
+ break;
+ case OMCI_CONFIG_VENDOR_ID:
+ *source = agent->config.vendor_source;
+ break;
+ case OMCI_CONFIG_VERSION:
+ case OMCI_CONFIG_HARDWARE_VERSION:
+ *source = agent->config.version_source;
+ break;
+ case OMCI_CONFIG_SOFTWARE_VERSION_0:
+ *source = agent->config.software_version_source[0];
+ break;
+ case OMCI_CONFIG_SOFTWARE_VERSION_1:
+ *source = agent->config.software_version_source[1];
+ break;
+ case OMCI_CONFIG_EQUIPMENT_ID:
+ *source = agent->config.equipment_source;
+ break;
+ case OMCI_CONFIG_PASSWORD:
+ *source = agent->config.password_source;
+ break;
+ case OMCI_CONFIG_ONU_TYPE:
+ *source = agent->config.onu_type_source;
+ break;
+ case OMCI_CONFIG_OLT_PROFILE:
+ *source = agent->config.olt_profile_source;
+ break;
+ case OMCI_CONFIG_OLT_PROFILE_FORCE:
+ *source = agent->config.olt_profile_force_source;
+ break;
+ case OMCI_CONFIG_AGENT_DYING_GASP:
+ *source = agent->config.dying_gasp_source;
+ break;
+ case OMCI_CONFIG_OMCC_VERSION:
+ *source = agent->config.omcc_version_source;
+ break;
+ default:
+ *source = OMCI_CONFIG_SOURCE_UNSPEC;
+ ret = -ENOENT;
+ break;
+ }
+ mutex_unlock(&agent->lock);
+ return ret;
+}
+
+int omci_agent_mib_get(struct omci_device *odev, u16 class_id, u16 entity_id,
+ struct omci_mib_object *object)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_mib_object *stored;
+ int ret = 0;
+
+ mutex_lock(&agent->lock);
+ stored = omci_mib_lookup(agent, class_id, entity_id);
+ if (!stored || !omci_mib_object_active(agent, stored))
+ ret = -ENOENT;
+ else
+ memcpy(object, stored, sizeof(*object));
+ mutex_unlock(&agent->lock);
+ return ret;
+}
+
+int omci_agent_mib_set(struct omci_device *odev,
+ const struct omci_mib_object *object)
+{
+ struct omci_agent *agent = &odev->agent;
+ const struct omci_me_desc *desc;
+ struct omci_mib_object *stored;
+ struct omci_mib_object *local;
+ bool datapath;
+ int ret;
+
+ local = kmemdup(object, sizeof(*local), GFP_KERNEL);
+ if (!local)
+ return -ENOMEM;
+
+ mutex_lock(&agent->lock);
+ desc = omci_me_lookup(agent, local->class_id);
+ if (!omci_me_active(agent, local->class_id)) {
+ ret = -ENOENT;
+ goto unlock;
+ }
+ local->origin = OMCI_MIB_ORIGIN_LOCAL;
+ local->owner_profile = desc && (desc->flags & OMCI_ME_F_VENDOR) ?
+ agent->profile_effective : OMCI_OLT_PROFILE_UNSPEC;
+ if (!local->data_len)
+ local->data_len = desc ? desc->data_len : sizeof(local->data);
+ if (local->data_len > sizeof(local->data)) {
+ ret = -E2BIG;
+ goto unlock;
+ }
+ if (local->class_id == OMCI_CLASS_OLT_G) {
+ ret = omci_olt_g_parse_set(local, local->attr_mask, local->data,
+ local->data_len);
+ if (ret)
+ goto unlock;
+ } else if (local->class_id == OMCI_CLASS_VLAN_TAGGING_FILTER_DATA) {
+ omci_vlan_filter_parse_create(local, local->data);
+ } else if (local->class_id == OMCI_CLASS_EXTENDED_VLAN) {
+ omci_ext_vlan_parse_create(local, local->data);
+ if (local->attr_mask & OMCI_EXT_VLAN_TABLE_MASK)
+ omci_ext_vlan_update_rule(&local->extended_vlan,
+ local->data);
+ }
+
+ ret = omci_mib_store_locked(agent, local);
+ if (ret)
+ goto unlock;
+ stored = omci_mib_lookup(agent, local->class_id, local->entity_id);
+ if (local->class_id == OMCI_CLASS_OLT_G)
+ ret = omci_agent_profile_refresh_locked(odev, &stored->olt_g,
+ NULL);
+ datapath = omci_agent_datapath_class(agent, local->class_id);
+ if (!ret && datapath)
+ ret = omci_agent_reconcile_services_locked(odev);
+ if (!ret)
+ omci_agent_reset_duplicate_locked(agent);
+
+unlock:
+ mutex_unlock(&agent->lock);
+ kfree(local);
+ return ret;
+}
+
+int omci_agent_mib_delete(struct omci_device *odev, u16 class_id,
+ u16 entity_id)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_mib_object *object;
+ bool datapath;
+ int ret = 0;
+
+ mutex_lock(&agent->lock);
+ object = xa_erase(&agent->mib, omci_mib_key(class_id, entity_id));
+ if (!object) {
+ ret = -ENOENT;
+ goto unlock;
+ }
+ if (class_id == OMCI_CLASS_OLT_G)
+ ret = omci_agent_profile_refresh_locked(odev, NULL, NULL);
+ datapath = omci_agent_datapath_class(agent, class_id);
+ if (!ret && datapath)
+ ret = omci_agent_reconcile_services_locked(odev);
+ if (!ret)
+ omci_agent_reset_duplicate_locked(agent);
+
+unlock:
+ mutex_unlock(&agent->lock);
+ kfree(object);
+ return ret;
+}
+
+void omci_agent_mib_reset(struct omci_device *odev, bool all)
+{
+ struct omci_agent *agent = &odev->agent;
+
+ mutex_lock(&agent->lock);
+ omci_agent_mib_reset_locked(odev, all, NULL);
+ mutex_unlock(&agent->lock);
+}
+
+int omci_agent_mib_next(struct omci_device *odev, u32 index,
+ struct omci_mib_object *object, u32 *next_index,
+ const char **name)
+{
+ struct omci_agent *agent = &odev->agent;
+ struct omci_mib_object *stored;
+ unsigned long xa_index = index;
+ int ret = 0;
+
+ mutex_lock(&agent->lock);
+ for (;;) {
+ stored = xa_find(&agent->mib, &xa_index, ULONG_MAX, XA_PRESENT);
+ if (!stored) {
+ ret = -ENOENT;
+ break;
+ }
+ if (omci_mib_object_active(agent, stored))
+ break;
+ xa_index++;
+ }
+ if (!ret) {
+ memcpy(object, stored, sizeof(*object));
+ *next_index = xa_index + 1;
+ *name = omci_me_class_name(stored->class_id);
+ }
+ mutex_unlock(&agent->lock);
+ return ret;
+}
+
+int omci_device_reconcile_services(struct omci_device *odev)
+{
+ struct omci_agent *agent;
+ int ret;
+
+ if (!odev)
+ return -EINVAL;
+
+ agent = &odev->agent;
+ mutex_lock(&agent->lock);
+ ret = omci_agent_reconcile_services_locked(odev);
+ mutex_unlock(&agent->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(omci_device_reconcile_services);
diff --git a/net/xpon/omci/core.c b/net/xpon/omci/core.c
new file mode 100644
index 000000000000..f97d43a9f0eb
--- /dev/null
+++ b/net/xpon/omci/core.c
@@ -0,0 +1,1691 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Generic ONU Management and Control Interface core
+ *
+ * The core owns the in-kernel baseline OMCI agent, the operational MIB and
+ * an optional Generic Netlink observer and administration endpoint.
+ */
+
+#include <linux/atomic.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <linux/notifier.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
+#include <net/genetlink.h>
+#include <net/netlink.h>
+
+#include "../internal.h"
+#include "internal.h"
+
+static LIST_HEAD(omci_devices);
+static DEFINE_MUTEX(omci_devices_lock);
+static atomic_t omci_next_id = ATOMIC_INIT(0);
+static struct genl_family omci_genl_family;
+
+static const struct nla_policy omci_policy[OMCI_ATTR_MAX + 1] = {
+ [OMCI_ATTR_DEV_ID] = { .type = NLA_U32 },
+ [OMCI_ATTR_PDU] = {
+ .type = NLA_BINARY,
+ .len = OMCI_MAX_PDU_LEN,
+ },
+ [OMCI_ATTR_AGENT_ENABLED] = { .type = NLA_U8 },
+ [OMCI_ATTR_AGENT_PERMISSIVE] = { .type = NLA_U8 },
+ [OMCI_ATTR_AGENT_FAKE_OMCI] = { .type = NLA_U8 },
+ [OMCI_ATTR_AGENT_DYING_GASP] = { .type = NLA_U8 },
+ [OMCI_ATTR_ONU_TYPE] = { .type = NLA_U8 },
+ [OMCI_ATTR_CLASS_ID] = { .type = NLA_U16 },
+ [OMCI_ATTR_ENTITY_ID] = { .type = NLA_U16 },
+ [OMCI_ATTR_ATTR_MASK] = { .type = NLA_U16 },
+ [OMCI_ATTR_ATTR_DATA] = {
+ .type = NLA_BINARY,
+ .len = OMCI_MAX_ATTR_DATA,
+ },
+ [OMCI_ATTR_ORIGIN] = { .type = NLA_U8 },
+ [OMCI_ATTR_INDEX] = { .type = NLA_U32 },
+ [OMCI_ATTR_CONFIG_KEY] = { .type = NLA_U16 },
+ [OMCI_ATTR_CONFIG_VALUE] = {
+ .type = NLA_BINARY,
+ .len = OMCI_MAX_CONFIG_VALUE,
+ },
+ [OMCI_ATTR_VLAN_TAGGING_FILTER] = { .type = NLA_NESTED },
+ [OMCI_ATTR_EXTENDED_VLAN] = { .type = NLA_NESTED },
+ [OMCI_ATTR_CLASS_CATEGORY] = { .type = NLA_U8 },
+ [OMCI_ATTR_CLASS_SUPPORT] = { .type = NLA_U8 },
+ [OMCI_ATTR_CLASS_FLAGS] = { .type = NLA_U32 },
+ [OMCI_ATTR_CONFIG_SOURCE] = { .type = NLA_U8 },
+ [OMCI_ATTR_OLT_PROFILE_CONFIGURED] = { .type = NLA_U8 },
+ [OMCI_ATTR_OLT_PROFILE_FORCED] = { .type = NLA_U8 },
+};
+
+static struct omci_device *omci_find_locked(u32 id)
+{
+ struct omci_device *odev;
+
+ list_for_each_entry(odev, &omci_devices, list)
+ if (odev->id == id)
+ return odev;
+
+ return NULL;
+}
+
+static struct omci_device *omci_get_from_info(struct genl_info *info)
+{
+ u32 id = 0;
+
+ if (info->attrs[OMCI_ATTR_DEV_ID])
+ id = nla_get_u32(info->attrs[OMCI_ATTR_DEV_ID]);
+
+ return omci_find_locked(id);
+}
+
+static int omci_put_telemetry(struct sk_buff *msg, struct omci_device *odev)
+{
+ struct omci_telemetry telemetry = {};
+
+ if (odev->ops->get_telemetry)
+ odev->ops->get_telemetry(odev, &telemetry);
+
+ if (nla_put_u32(msg, OMCI_ATTR_TELEMETRY_VALID, telemetry.valid))
+ return -EMSGSIZE;
+
+ if ((telemetry.valid & OMCI_TELEMETRY_F_FEC_DOWNSTREAM) &&
+ nla_put_u8(msg, OMCI_ATTR_FEC_DOWNSTREAM,
+ telemetry.downstream_fec))
+ return -EMSGSIZE;
+ if ((telemetry.valid & OMCI_TELEMETRY_F_FEC_UPSTREAM) &&
+ nla_put_u8(msg, OMCI_ATTR_FEC_UPSTREAM, telemetry.upstream_fec))
+ return -EMSGSIZE;
+ if ((telemetry.valid & OMCI_TELEMETRY_F_BOSA_TEMPERATURE) &&
+ nla_put_s32(msg, OMCI_ATTR_BOSA_TEMPERATURE_MC,
+ telemetry.bosa_temperature_mc))
+ return -EMSGSIZE;
+ if ((telemetry.valid & OMCI_TELEMETRY_F_BOSA_VOLTAGE) &&
+ nla_put_u32(msg, OMCI_ATTR_BOSA_VOLTAGE_UV,
+ telemetry.bosa_voltage_uv))
+ return -EMSGSIZE;
+ if ((telemetry.valid & OMCI_TELEMETRY_F_BOSA_BIAS) &&
+ nla_put_u32(msg, OMCI_ATTR_BOSA_BIAS_UA, telemetry.bosa_bias_ua))
+ return -EMSGSIZE;
+ if ((telemetry.valid & OMCI_TELEMETRY_F_BOSA_TX_POWER) &&
+ nla_put_u32(msg, OMCI_ATTR_BOSA_TX_POWER_NW,
+ telemetry.bosa_tx_power_nw))
+ return -EMSGSIZE;
+ if ((telemetry.valid & OMCI_TELEMETRY_F_BOSA_RX_POWER) &&
+ nla_put_u32(msg, OMCI_ATTR_BOSA_RX_POWER_NW,
+ telemetry.bosa_rx_power_nw))
+ return -EMSGSIZE;
+ if ((telemetry.valid & OMCI_TELEMETRY_F_BOSA_ALARMS) &&
+ nla_put_u32(msg, OMCI_ATTR_BOSA_ALARMS, telemetry.bosa_alarms))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int omci_put_status(struct sk_buff *msg, struct omci_device *odev)
+{
+ u32 owner_portid;
+ u32 flags = 0;
+ u16 onu_id;
+ u16 gem_port_id;
+ u8 state;
+
+ mutex_lock(&odev->owner_lock);
+ owner_portid = odev->owner_portid;
+ mutex_unlock(&odev->owner_lock);
+
+ spin_lock_bh(&odev->state_lock);
+ onu_id = odev->onu_id;
+ gem_port_id = odev->gem_port_id;
+ state = odev->state;
+ if (odev->channel_up)
+ flags |= OMCI_F_CHANNEL_UP;
+ spin_unlock_bh(&odev->state_lock);
+
+ if (nla_put_u32(msg, OMCI_ATTR_DEV_ID, odev->id) ||
+ nla_put_u32(msg, OMCI_ATTR_IFINDEX, odev->ifindex) ||
+ nla_put_u16(msg, OMCI_ATTR_ONU_ID, onu_id) ||
+ nla_put_u16(msg, OMCI_ATTR_GEM_PORT_ID, gem_port_id) ||
+ nla_put_u8(msg, OMCI_ATTR_STATE, state) ||
+ nla_put_u32(msg, OMCI_ATTR_FLAGS, flags) ||
+ nla_put_u32(msg, OMCI_ATTR_CAPABILITIES, odev->capabilities) ||
+ nla_put_u32(msg, OMCI_ATTR_OWNER_PORTID, owner_portid) ||
+ nla_put_u64_64bit(msg, OMCI_ATTR_RX_PACKETS,
+ atomic64_read(&odev->rx_packets), OMCI_ATTR_PAD) ||
+ nla_put_u64_64bit(msg, OMCI_ATTR_RX_BYTES,
+ atomic64_read(&odev->rx_bytes), OMCI_ATTR_PAD) ||
+ nla_put_u64_64bit(msg, OMCI_ATTR_RX_DROPPED,
+ atomic64_read(&odev->rx_dropped), OMCI_ATTR_PAD) ||
+ nla_put_u64_64bit(msg, OMCI_ATTR_TX_PACKETS,
+ atomic64_read(&odev->tx_packets), OMCI_ATTR_PAD) ||
+ nla_put_u64_64bit(msg, OMCI_ATTR_TX_BYTES,
+ atomic64_read(&odev->tx_bytes), OMCI_ATTR_PAD) ||
+ nla_put_u64_64bit(msg, OMCI_ATTR_TX_ERRORS,
+ atomic64_read(&odev->tx_errors), OMCI_ATTR_PAD))
+ return -EMSGSIZE;
+
+ if (omci_put_telemetry(msg, odev))
+ return -EMSGSIZE;
+
+ return omci_agent_put_status(msg, odev);
+}
+
+static int omci_cmd_get(struct sk_buff *skb, struct genl_info *info)
+{
+ struct omci_device *odev;
+ struct sk_buff *msg;
+ void *hdr;
+ int ret;
+
+ mutex_lock(&omci_devices_lock);
+ odev = omci_get_from_info(info);
+ if (!odev) {
+ ret = -ENODEV;
+ goto out_unlock;
+ }
+
+ msg = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg) {
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
+
+ hdr = genlmsg_put_reply(msg, info, &omci_genl_family, 0,
+ OMCI_CMD_GET);
+ if (!hdr) {
+ ret = -EMSGSIZE;
+ goto free_msg;
+ }
+
+ ret = omci_put_status(msg, odev);
+ if (ret)
+ goto cancel_msg;
+
+ genlmsg_end(msg, hdr);
+ mutex_unlock(&omci_devices_lock);
+ return genlmsg_reply(msg, info);
+
+cancel_msg:
+ genlmsg_cancel(msg, hdr);
+free_msg:
+ nlmsg_free(msg);
+out_unlock:
+ mutex_unlock(&omci_devices_lock);
+ return ret;
+}
+
+static int omci_cmd_bind(struct sk_buff *skb, struct genl_info *info)
+{
+ struct net *net = genl_info_net(info);
+ struct omci_device *odev;
+ int ret = 0;
+
+ mutex_lock(&omci_devices_lock);
+ odev = omci_get_from_info(info);
+ if (!odev) {
+ ret = -ENODEV;
+ goto out_unlock_devices;
+ }
+
+ mutex_lock(&odev->owner_lock);
+ if (odev->owner_portid &&
+ (odev->owner_portid != info->snd_portid ||
+ odev->owner_net != net)) {
+ ret = -EBUSY;
+ goto out_unlock_owner;
+ }
+
+ if (!odev->owner_portid) {
+ odev->owner_net = get_net(net);
+ odev->owner_portid = info->snd_portid;
+ schedule_work(&odev->rx_work);
+ }
+
+out_unlock_owner:
+ mutex_unlock(&odev->owner_lock);
+out_unlock_devices:
+ mutex_unlock(&omci_devices_lock);
+ return ret;
+}
+
+static int omci_cmd_unbind(struct sk_buff *skb, struct genl_info *info)
+{
+ struct omci_device *odev;
+ struct net *owner_net = NULL;
+ int ret = 0;
+
+ mutex_lock(&omci_devices_lock);
+ odev = omci_get_from_info(info);
+ if (!odev) {
+ ret = -ENODEV;
+ goto out_unlock_devices;
+ }
+
+ mutex_lock(&odev->owner_lock);
+ if (odev->owner_portid != info->snd_portid ||
+ odev->owner_net != genl_info_net(info)) {
+ ret = -EPERM;
+ goto out_unlock_owner;
+ }
+
+ owner_net = odev->owner_net;
+ odev->owner_net = NULL;
+ odev->owner_portid = 0;
+
+out_unlock_owner:
+ mutex_unlock(&odev->owner_lock);
+out_unlock_devices:
+ mutex_unlock(&omci_devices_lock);
+ if (owner_net)
+ put_net(owner_net);
+ return ret;
+}
+
+int omci_validate_tx(struct omci_device *odev, struct sk_buff *skb)
+{
+ const u8 *data = skb->data;
+ u16 content_len;
+ u16 pdu_len;
+
+ if (skb->len < 4)
+ return -EMSGSIZE;
+
+ switch (data[3]) {
+ case OMCI_BASELINE_DEV_ID:
+ if (skb->len != OMCI_BASELINE_LEN_NO_MIC &&
+ skb->len != OMCI_BASELINE_LEN)
+ return -EMSGSIZE;
+ if ((odev->capabilities & OMCI_CAP_HW_MIC) &&
+ skb->len == OMCI_BASELINE_LEN)
+ skb_trim(skb, OMCI_BASELINE_LEN_NO_MIC);
+ break;
+ case OMCI_EXTENDED_DEV_ID:
+ if (skb->len < OMCI_EXTENDED_HEADER_LEN)
+ return -EMSGSIZE;
+ content_len = get_unaligned_be16(data + 8);
+ pdu_len = OMCI_EXTENDED_HEADER_LEN + content_len;
+ if (pdu_len > OMCI_MAX_PDU_LEN || skb->len < pdu_len)
+ return -EMSGSIZE;
+ if (skb->len > pdu_len) {
+ if (skb->len - pdu_len != OMCI_MIC_LEN)
+ return -EMSGSIZE;
+ if (odev->capabilities & OMCI_CAP_HW_MIC)
+ skb_trim(skb, pdu_len);
+ }
+ break;
+ default:
+ return -EPROTO;
+ }
+
+ return 0;
+}
+
+int omci_device_xmit(struct omci_device *odev, const void *data, size_t len)
+{
+ struct sk_buff *tx_skb;
+ u16 gem_port_id;
+ bool channel_up;
+ u32 tx_len;
+ int ret;
+
+ if (!READ_ONCE(odev->started))
+ return -ENETDOWN;
+
+ spin_lock_bh(&odev->state_lock);
+ gem_port_id = odev->gem_port_id;
+ channel_up = odev->channel_up;
+ spin_unlock_bh(&odev->state_lock);
+ if (!channel_up)
+ return -ENOLINK;
+
+ tx_skb = alloc_skb(len, GFP_KERNEL);
+ if (!tx_skb)
+ return -ENOMEM;
+ skb_put_data(tx_skb, data, len);
+ ret = omci_validate_tx(odev, tx_skb);
+ if (ret)
+ goto free_skb;
+
+ tx_len = tx_skb->len;
+ ret = odev->ops->xmit(odev, tx_skb, gem_port_id);
+ if (ret)
+ goto free_skb;
+
+ atomic64_inc(&odev->tx_packets);
+ atomic64_add(tx_len, &odev->tx_bytes);
+ return 0;
+
+free_skb:
+ atomic64_inc(&odev->tx_errors);
+ dev_kfree_skb_any(tx_skb);
+ return ret;
+}
+
+static int omci_cmd_tx(struct sk_buff *skb, struct genl_info *info)
+{
+ struct nlattr *pdu_attr = info->attrs[OMCI_ATTR_PDU];
+ struct omci_device *odev;
+ struct sk_buff *tx_skb;
+ u16 gem_port_id;
+ bool channel_up;
+ u32 tx_len;
+ int ret;
+
+ if (!pdu_attr)
+ return -EINVAL;
+
+ mutex_lock(&omci_devices_lock);
+ odev = omci_get_from_info(info);
+ if (!odev) {
+ ret = -ENODEV;
+ goto out_unlock_devices;
+ }
+
+ mutex_lock(&odev->owner_lock);
+ if (odev->owner_portid != info->snd_portid ||
+ odev->owner_net != genl_info_net(info)) {
+ ret = -EPERM;
+ goto out_unlock_owner;
+ }
+ mutex_unlock(&odev->owner_lock);
+
+ if (!READ_ONCE(odev->started)) {
+ ret = -ENETDOWN;
+ goto out_unlock_devices;
+ }
+
+ spin_lock_bh(&odev->state_lock);
+ gem_port_id = odev->gem_port_id;
+ channel_up = odev->channel_up;
+ spin_unlock_bh(&odev->state_lock);
+ if (!channel_up) {
+ ret = -ENOLINK;
+ goto out_unlock_devices;
+ }
+
+ tx_skb = alloc_skb(nla_len(pdu_attr), GFP_KERNEL);
+ if (!tx_skb) {
+ ret = -ENOMEM;
+ goto out_unlock_devices;
+ }
+
+ skb_put_data(tx_skb, nla_data(pdu_attr), nla_len(pdu_attr));
+ ret = omci_validate_tx(odev, tx_skb);
+ if (ret)
+ goto free_tx_skb;
+
+ tx_len = tx_skb->len;
+ ret = odev->ops->xmit(odev, tx_skb, gem_port_id);
+ if (ret)
+ goto free_tx_skb;
+
+ atomic64_inc(&odev->tx_packets);
+ atomic64_add(tx_len, &odev->tx_bytes);
+ mutex_unlock(&omci_devices_lock);
+ return 0;
+
+free_tx_skb:
+ atomic64_inc(&odev->tx_errors);
+ dev_kfree_skb_any(tx_skb);
+ goto out_unlock_devices;
+
+out_unlock_owner:
+ mutex_unlock(&odev->owner_lock);
+out_unlock_devices:
+ mutex_unlock(&omci_devices_lock);
+ return ret;
+}
+
+static int omci_put_olt_g(struct sk_buff *msg,
+ const struct omci_mib_object *object)
+{
+ const struct omci_olt_g *olt = &object->olt_g;
+ struct nlattr *nested;
+
+ if (object->class_id != OMCI_CLASS_OLT_G || !olt->valid)
+ return 0;
+
+ nested = nla_nest_start(msg, OMCI_ATTR_OLT_G);
+ if (!nested)
+ return -EMSGSIZE;
+ if ((olt->vendor_id_valid &&
+ nla_put_string(msg, OMCI_OLT_G_ATTR_VENDOR_ID, olt->vendor_id)) ||
+ (olt->equipment_id_valid &&
+ nla_put_string(msg, OMCI_OLT_G_ATTR_EQUIPMENT_ID,
+ olt->equipment_id)) ||
+ (olt->version_valid &&
+ nla_put_string(msg, OMCI_OLT_G_ATTR_VERSION, olt->version))) {
+ nla_nest_cancel(msg, nested);
+ return -EMSGSIZE;
+ }
+ nla_nest_end(msg, nested);
+
+ return 0;
+}
+
+static int omci_put_vlan_filter(struct sk_buff *msg,
+ const struct omci_mib_object *object)
+{
+ const struct omci_vlan_tagging_filter *filter = &object->vlan_filter;
+ struct nlattr *entries;
+ struct nlattr *nested;
+ unsigned int i;
+
+ if (object->class_id != OMCI_CLASS_VLAN_TAGGING_FILTER_DATA || !filter->valid)
+ return 0;
+
+ nested = nla_nest_start(msg, OMCI_ATTR_VLAN_TAGGING_FILTER);
+ if (!nested)
+ return -EMSGSIZE;
+ if (nla_put_u8(msg, OMCI_VLAN_FILTER_ATTR_FORWARD_OPERATION,
+ filter->forward_operation) ||
+ nla_put_u8(msg, OMCI_VLAN_FILTER_ATTR_NUMBER_OF_ENTRIES,
+ filter->num_entries))
+ goto cancel;
+
+ entries = nla_nest_start(msg, OMCI_VLAN_FILTER_ATTR_ENTRIES);
+ if (!entries)
+ goto cancel;
+ for (i = 0; i < filter->num_entries; i++) {
+ const struct omci_vlan_filter_entry *entry = &filter->entries[i];
+ struct nlattr *item;
+
+ item = nla_nest_start(msg, i + 1);
+ if (!item)
+ goto cancel_entries;
+ if (nla_put_u8(msg, OMCI_VLAN_FILTER_ENTRY_ATTR_INDEX, i) ||
+ nla_put_u16(msg, OMCI_VLAN_FILTER_ENTRY_ATTR_TCI,
+ entry->tci) ||
+ nla_put_u8(msg, OMCI_VLAN_FILTER_ENTRY_ATTR_PBIT,
+ entry->pbit) ||
+ nla_put_u8(msg, OMCI_VLAN_FILTER_ENTRY_ATTR_DEI,
+ entry->dei) ||
+ nla_put_u16(msg, OMCI_VLAN_FILTER_ENTRY_ATTR_VID,
+ entry->vid)) {
+ nla_nest_cancel(msg, item);
+ goto cancel_entries;
+ }
+ nla_nest_end(msg, item);
+ }
+ nla_nest_end(msg, entries);
+ nla_nest_end(msg, nested);
+
+ return 0;
+
+cancel_entries:
+ nla_nest_cancel(msg, entries);
+cancel:
+ nla_nest_cancel(msg, nested);
+ return -EMSGSIZE;
+}
+
+static int omci_put_ext_vlan_rule(struct sk_buff *msg,
+ const struct omci_extended_vlan_rule *rule,
+ unsigned int index)
+{
+ struct nlattr *nested;
+
+ nested = nla_nest_start(msg, index + 1);
+ if (!nested)
+ return -EMSGSIZE;
+ if (nla_put_u8(msg, OMCI_EXT_VLAN_RULE_ATTR_INDEX, index) ||
+ nla_put(msg, OMCI_EXT_VLAN_RULE_ATTR_RAW, sizeof(rule->raw),
+ rule->raw) ||
+ nla_put_u8(msg, OMCI_EXT_VLAN_RULE_ATTR_DELETE, rule->delete) ||
+ nla_put_u8(msg, OMCI_EXT_VLAN_RULE_ATTR_FILTER_OUTER_PBIT,
+ rule->filter_outer_pbit) ||
+ nla_put_u16(msg, OMCI_EXT_VLAN_RULE_ATTR_FILTER_OUTER_VID,
rule->filter_outer_vid) ||
+ nla_put_u8(msg, OMCI_EXT_VLAN_RULE_ATTR_FILTER_OUTER_TPID_DEI,
+ rule->filter_outer_tpid_dei) ||
+ nla_put_u8(msg, OMCI_EXT_VLAN_RULE_ATTR_FILTER_INNER_PBIT,
+ rule->filter_inner_pbit) ||
+ nla_put_u16(msg, OMCI_EXT_VLAN_RULE_ATTR_FILTER_INNER_VID,
rule->filter_inner_vid) ||
+ nla_put_u8(msg, OMCI_EXT_VLAN_RULE_ATTR_FILTER_INNER_TPID_DEI,
+ rule->filter_inner_tpid_dei) ||
+ nla_put_u8(msg, OMCI_EXT_VLAN_RULE_ATTR_FILTER_ETHERTYPE,
+ rule->filter_ethertype) ||
+ nla_put_u8(msg, OMCI_EXT_VLAN_RULE_ATTR_TAGS_TO_REMOVE,
+ rule->tags_to_remove) ||
+ nla_put_u8(msg, OMCI_EXT_VLAN_RULE_ATTR_TREAT_OUTER_PBIT,
+ rule->treat_outer_pbit) ||
+ nla_put_u16(msg, OMCI_EXT_VLAN_RULE_ATTR_TREAT_OUTER_VID,
rule->treat_outer_vid) ||
+ nla_put_u8(msg, OMCI_EXT_VLAN_RULE_ATTR_TREAT_OUTER_TPID_DEI,
+ rule->treat_outer_tpid_dei) ||
+ nla_put_u8(msg, OMCI_EXT_VLAN_RULE_ATTR_TREAT_INNER_PBIT,
+ rule->treat_inner_pbit) ||
+ nla_put_u16(msg, OMCI_EXT_VLAN_RULE_ATTR_TREAT_INNER_VID,
rule->treat_inner_vid) ||
+ nla_put_u8(msg, OMCI_EXT_VLAN_RULE_ATTR_TREAT_INNER_TPID_DEI,
+ rule->treat_inner_tpid_dei)) {
+ nla_nest_cancel(msg, nested);
+ return -EMSGSIZE;
+ }
+ nla_nest_end(msg, nested);
+
+ return 0;
+}
+
+static int omci_put_extended_vlan(struct sk_buff *msg,
+ const struct omci_mib_object *object)
+{
+ const struct omci_extended_vlan *vlan = &object->extended_vlan;
+ struct nlattr *rules;
+ struct nlattr *nested;
+ unsigned int i;
+
+ if (object->class_id != OMCI_CLASS_EXTENDED_VLAN || !vlan->valid)
+ return 0;
+
+ nested = nla_nest_start(msg, OMCI_ATTR_EXTENDED_VLAN);
+ if (!nested)
+ return -EMSGSIZE;
+ if (nla_put_u8(msg, OMCI_EXT_VLAN_ATTR_ASSOCIATION_TYPE,
+ vlan->association_type) ||
+ nla_put_u16(msg, OMCI_EXT_VLAN_ATTR_MAX_TABLE_SIZE,
vlan->max_table_size) ||
+ nla_put_u16(msg, OMCI_EXT_VLAN_ATTR_INPUT_TPID, vlan->input_tpid) ||
+ nla_put_u16(msg, OMCI_EXT_VLAN_ATTR_OUTPUT_TPID, vlan->output_tpid) ||
+ nla_put_u8(msg, OMCI_EXT_VLAN_ATTR_DOWNSTREAM_MODE,
+ vlan->downstream_mode) ||
+ nla_put_u16(msg, OMCI_EXT_VLAN_ATTR_ASSOCIATED_ME, vlan->associated_me) ||
+ nla_put(msg, OMCI_EXT_VLAN_ATTR_DSCP_TO_PBIT,
+ sizeof(vlan->dscp_to_pbit), vlan->dscp_to_pbit))
+ goto cancel;
+
+ rules = nla_nest_start(msg, OMCI_EXT_VLAN_ATTR_RULES);
+ if (!rules)
+ goto cancel;
+ for (i = 0; i < vlan->rule_count; i++)
+ if (omci_put_ext_vlan_rule(msg, &vlan->rules[i], i))
+ goto cancel_rules;
+ nla_nest_end(msg, rules);
+ nla_nest_end(msg, nested);
+
+ return 0;
+
+cancel_rules:
+ nla_nest_cancel(msg, rules);
+cancel:
+ nla_nest_cancel(msg, nested);
+ return -EMSGSIZE;
+}
+
+static int omci_put_mib_object(struct sk_buff *msg,
+ const struct omci_mib_object *object,
+ u32 next_index, const char *name)
+{
+ if (nla_put_u16(msg, OMCI_ATTR_CLASS_ID, object->class_id) ||
+ nla_put_u16(msg, OMCI_ATTR_ENTITY_ID, object->entity_id) ||
+ nla_put_u16(msg, OMCI_ATTR_ATTR_MASK, object->attr_mask) ||
+ nla_put(msg, OMCI_ATTR_ATTR_DATA, sizeof(object->data),
+ object->data) ||
+ nla_put_u8(msg, OMCI_ATTR_ORIGIN, object->origin) ||
+ nla_put_u32(msg, OMCI_ATTR_INDEX, next_index) ||
+ (name && nla_put_string(msg, OMCI_ATTR_NAME, name)))
+ return -EMSGSIZE;
+ if (omci_put_olt_g(msg, object) ||
+ omci_put_vlan_filter(msg, object) ||
+ omci_put_extended_vlan(msg, object))
+ return -EMSGSIZE;
+ return 0;
+}
+
+static int omci_reply_mib(struct genl_info *info, u8 command,
+ struct omci_device *odev,
+ const struct omci_mib_object *object,
+ u32 next_index, const char *name)
+{
+ struct sk_buff *msg;
+ void *hdr;
+ int ret;
+
+ msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+ hdr = genlmsg_put_reply(msg, info, &omci_genl_family, 0, command);
+ if (!hdr) {
+ nlmsg_free(msg);
+ return -EMSGSIZE;
+ }
+ if (nla_put_u32(msg, OMCI_ATTR_DEV_ID, odev->id)) {
+ ret = -EMSGSIZE;
+ goto cancel;
+ }
+ ret = omci_put_mib_object(msg, object, next_index, name);
+ if (ret)
+ goto cancel;
+ genlmsg_end(msg, hdr);
+ return genlmsg_reply(msg, info);
+
+cancel:
+ genlmsg_cancel(msg, hdr);
+ nlmsg_free(msg);
+ return ret;
+}
+
+static int omci_reply_class(struct genl_info *info, u8 command,
+ const struct omci_me_class *class, u32 next_index)
+{
+ struct sk_buff *msg;
+ void *hdr;
+
+ msg = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+ hdr = genlmsg_put_reply(msg, info, &omci_genl_family, 0, command);
+ if (!hdr)
+ goto message_too_large;
+ if (nla_put_u16(msg, OMCI_ATTR_CLASS_ID, class->class_id) ||
+ nla_put_string(msg, OMCI_ATTR_NAME, class->name) ||
+ nla_put_u8(msg, OMCI_ATTR_CLASS_CATEGORY, class->category) ||
+ nla_put_u8(msg, OMCI_ATTR_CLASS_SUPPORT, class->support) ||
+ nla_put_u32(msg, OMCI_ATTR_CLASS_FLAGS, class->flags) ||
+ nla_put_u32(msg, OMCI_ATTR_INDEX, next_index))
+ goto cancel;
+
+ genlmsg_end(msg, hdr);
+ return genlmsg_reply(msg, info);
+
+cancel:
+ genlmsg_cancel(msg, hdr);
+message_too_large:
+ nlmsg_free(msg);
+ return -EMSGSIZE;
+}
+
+static int omci_cmd_class_get(struct sk_buff *skb, struct genl_info *info)
+{
+ struct omci_me_class class;
+ struct omci_device *odev;
+ u16 class_id;
+ int ret;
+
+ if (!info->attrs[OMCI_ATTR_CLASS_ID])
+ return -EINVAL;
+ class_id = nla_get_u16(info->attrs[OMCI_ATTR_CLASS_ID]);
+
+ mutex_lock(&omci_devices_lock);
+ odev = omci_get_from_info(info);
+ if (!odev) {
+ ret = -ENODEV;
+ goto out;
+ }
+ ret = omci_me_class_get(odev, class_id, &class);
+ if (!ret)
+ ret = omci_reply_class(info, OMCI_CMD_CLASS_GET, &class, 0);
+out:
+ mutex_unlock(&omci_devices_lock);
+ return ret;
+}
+
+static int omci_cmd_class_next(struct sk_buff *skb, struct genl_info *info)
+{
+ struct omci_me_class class;
+ struct omci_device *odev;
+ u32 index = 0;
+ u32 next_index;
+ int ret;
+
+ if (info->attrs[OMCI_ATTR_INDEX])
+ index = nla_get_u32(info->attrs[OMCI_ATTR_INDEX]);
+
+ mutex_lock(&omci_devices_lock);
+ odev = omci_get_from_info(info);
+ if (!odev) {
+ ret = -ENODEV;
+ goto out;
+ }
+ ret = omci_me_class_next(odev, index, &class, &next_index);
+ if (!ret)
+ ret = omci_reply_class(info, OMCI_CMD_CLASS_NEXT, &class,
+ next_index);
+out:
+ mutex_unlock(&omci_devices_lock);
+ return ret;
+}
+
+static int omci_cmd_agent_set(struct sk_buff *skb, struct genl_info *info)
+{
+ struct omci_device *odev;
+ bool profile_changed = false;
+ u8 value;
+ int ret = 0;
+
+ mutex_lock(&omci_devices_lock);
+ odev = omci_get_from_info(info);
+ if (!odev) {
+ ret = -ENODEV;
+ goto out;
+ }
+ if (info->attrs[OMCI_ATTR_AGENT_ENABLED]) {
+ value = nla_get_u8(info->attrs[OMCI_ATTR_AGENT_ENABLED]);
+ ret = omci_agent_config_set(odev, OMCI_CONFIG_AGENT_ENABLED,
+ &value, sizeof(value));
+ if (ret)
+ goto out;
+ }
+ if (info->attrs[OMCI_ATTR_AGENT_PERMISSIVE]) {
+ value = nla_get_u8(info->attrs[OMCI_ATTR_AGENT_PERMISSIVE]);
+ ret = omci_agent_config_set(odev, OMCI_CONFIG_AGENT_PERMISSIVE,
+ &value, sizeof(value));
+ if (ret)
+ goto out;
+ }
+ if (info->attrs[OMCI_ATTR_AGENT_FAKE_OMCI]) {
+ value = nla_get_u8(info->attrs[OMCI_ATTR_AGENT_FAKE_OMCI]);
+ ret = omci_agent_config_set(odev, OMCI_CONFIG_AGENT_FAKE_OMCI,
+ &value, sizeof(value));
+ if (ret)
+ goto out;
+ }
+ if (info->attrs[OMCI_ATTR_AGENT_DYING_GASP]) {
+ value = nla_get_u8(info->attrs[OMCI_ATTR_AGENT_DYING_GASP]);
+ ret = omci_agent_config_set(odev,
+ OMCI_CONFIG_AGENT_DYING_GASP,
+ &value, sizeof(value));
+ if (ret)
+ goto out;
+ }
+ if (info->attrs[OMCI_ATTR_OLT_PROFILE_CONFIGURED]) {
+ value = nla_get_u8(info->attrs[OMCI_ATTR_OLT_PROFILE_CONFIGURED]);
+ ret = omci_agent_config_set(odev, OMCI_CONFIG_OLT_PROFILE,
+ &value, sizeof(value));
+ if (ret)
+ goto out;
+ profile_changed = true;
+ }
+ if (info->attrs[OMCI_ATTR_OLT_PROFILE_FORCED]) {
+ value = nla_get_u8(info->attrs[OMCI_ATTR_OLT_PROFILE_FORCED]);
+ ret = omci_agent_config_set(odev, OMCI_CONFIG_OLT_PROFILE_FORCE,
+ &value, sizeof(value));
+ if (ret)
+ goto out;
+ profile_changed = true;
+ }
+ if (!ret)
+ omci_device_notify(odev, profile_changed ?
+ OMCI_EVENT_PROFILE_CHANGE :
+ OMCI_EVENT_CONFIG_CHANGE);
+out:
+ mutex_unlock(&omci_devices_lock);
+ return ret;
+}
+
+static int omci_cmd_config_get(struct sk_buff *skb, struct genl_info *info)
+{
+ u8 value[OMCI_MAX_CONFIG_VALUE];
+ struct omci_device *odev;
+ struct sk_buff *msg;
+ size_t len = sizeof(value);
+ u16 key;
+ u8 source = OMCI_CONFIG_SOURCE_UNSPEC;
+ void *hdr;
+ int ret;
+
+ if (!info->attrs[OMCI_ATTR_CONFIG_KEY])
+ return -EINVAL;
+ key = nla_get_u16(info->attrs[OMCI_ATTR_CONFIG_KEY]);
+
+ mutex_lock(&omci_devices_lock);
+ odev = omci_get_from_info(info);
+ if (!odev) {
+ ret = -ENODEV;
+ goto out_unlock;
+ }
+ ret = omci_agent_config_get(odev, key, value, &len);
+ if (ret)
+ goto out_unlock;
+ omci_agent_config_source_get(odev, key, &source);
+
+ msg = genlmsg_new(NLMSG_DEFAULT_SIZE + len, GFP_KERNEL);
+ if (!msg) {
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
+ hdr = genlmsg_put_reply(msg, info, &omci_genl_family, 0,
+ OMCI_CMD_CONFIG_GET);
+ if (!hdr || nla_put_u32(msg, OMCI_ATTR_DEV_ID, odev->id) ||
+ nla_put_u16(msg, OMCI_ATTR_CONFIG_KEY, key) ||
+ nla_put(msg, OMCI_ATTR_CONFIG_VALUE, len, value) ||
+ nla_put_u8(msg, OMCI_ATTR_CONFIG_SOURCE, source)) {
+ nlmsg_free(msg);
+ ret = -EMSGSIZE;
+ goto out_unlock;
+ }
+ genlmsg_end(msg, hdr);
+ mutex_unlock(&omci_devices_lock);
+ return genlmsg_reply(msg, info);
+
+out_unlock:
+ mutex_unlock(&omci_devices_lock);
+ return ret;
+}
+
+static int omci_cmd_config_set(struct sk_buff *skb, struct genl_info *info)
+{
+ struct nlattr *value = info->attrs[OMCI_ATTR_CONFIG_VALUE];
+ struct omci_device *odev;
+ u16 key;
+ int ret;
+
+ if (!info->attrs[OMCI_ATTR_CONFIG_KEY] || !value)
+ return -EINVAL;
+ key = nla_get_u16(info->attrs[OMCI_ATTR_CONFIG_KEY]);
+
+ mutex_lock(&omci_devices_lock);
+ odev = omci_get_from_info(info);
+ if (!odev) {
+ ret = -ENODEV;
+ goto out;
+ }
+ ret = omci_agent_config_set(odev, key, nla_data(value),
+ nla_len(value));
+ if (!ret) {
+ u8 event = OMCI_EVENT_CONFIG_CHANGE;
+
+ if (key == OMCI_CONFIG_OLT_PROFILE ||
+ key == OMCI_CONFIG_OLT_PROFILE_FORCE)
+ event = OMCI_EVENT_PROFILE_CHANGE;
+ omci_device_notify(odev, event);
+ }
+out:
+ mutex_unlock(&omci_devices_lock);
+ return ret;
+}
+
+static int omci_cmd_mib_get(struct sk_buff *skb, struct genl_info *info)
+{
+ struct omci_mib_object *object;
+ struct omci_device *odev;
+ u16 class_id;
+ u16 entity_id;
+ int ret;
+
+ if (!info->attrs[OMCI_ATTR_CLASS_ID] ||
+ !info->attrs[OMCI_ATTR_ENTITY_ID])
+ return -EINVAL;
+ class_id = nla_get_u16(info->attrs[OMCI_ATTR_CLASS_ID]);
+ entity_id = nla_get_u16(info->attrs[OMCI_ATTR_ENTITY_ID]);
+
+ object = kzalloc(sizeof(*object), GFP_KERNEL);
+ if (!object)
+ return -ENOMEM;
+
+ mutex_lock(&omci_devices_lock);
+ odev = omci_get_from_info(info);
+ if (!odev) {
+ ret = -ENODEV;
+ goto out;
+ }
+ ret = omci_agent_mib_get(odev, class_id, entity_id, object);
+ if (!ret)
+ ret = omci_reply_mib(info, OMCI_CMD_MIB_GET, odev, object,
+ 0, omci_me_class_name(class_id));
+out:
+ mutex_unlock(&omci_devices_lock);
+ kfree(object);
+ return ret;
+}
+
+static int omci_cmd_mib_set(struct sk_buff *skb, struct genl_info *info)
+{
+ struct nlattr *data = info->attrs[OMCI_ATTR_ATTR_DATA];
+ struct omci_mib_object *object;
+ struct omci_device *odev;
+ int ret;
+
+ if (!info->attrs[OMCI_ATTR_CLASS_ID] ||
+ !info->attrs[OMCI_ATTR_ENTITY_ID])
+ return -EINVAL;
+
+ object = kzalloc(sizeof(*object), GFP_KERNEL);
+ if (!object)
+ return -ENOMEM;
+ object->origin = OMCI_MIB_ORIGIN_LOCAL;
+ object->class_id = nla_get_u16(info->attrs[OMCI_ATTR_CLASS_ID]);
+ object->entity_id = nla_get_u16(info->attrs[OMCI_ATTR_ENTITY_ID]);
+ object->attr_mask = info->attrs[OMCI_ATTR_ATTR_MASK] ?
+ nla_get_u16(info->attrs[OMCI_ATTR_ATTR_MASK]) : 0xffff;
+ if (data) {
+ object->data_len = min_t(size_t, nla_len(data),
+ sizeof(object->data));
+ memcpy(object->data, nla_data(data), object->data_len);
+ }
+
+ mutex_lock(&omci_devices_lock);
+ odev = omci_get_from_info(info);
+ if (!odev) {
+ ret = -ENODEV;
+ goto out;
+ }
+ ret = omci_agent_mib_set(odev, object);
+ if (!ret)
+ omci_device_notify(odev, OMCI_EVENT_MIB_CHANGE);
+out:
+ mutex_unlock(&omci_devices_lock);
+ kfree(object);
+ return ret;
+}
+
+static int omci_cmd_mib_delete(struct sk_buff *skb, struct genl_info *info)
+{
+ struct omci_device *odev;
+ u16 class_id;
+ u16 entity_id;
+ int ret;
+
+ if (!info->attrs[OMCI_ATTR_CLASS_ID] ||
+ !info->attrs[OMCI_ATTR_ENTITY_ID])
+ return -EINVAL;
+ class_id = nla_get_u16(info->attrs[OMCI_ATTR_CLASS_ID]);
+ entity_id = nla_get_u16(info->attrs[OMCI_ATTR_ENTITY_ID]);
+
+ mutex_lock(&omci_devices_lock);
+ odev = omci_get_from_info(info);
+ if (!odev) {
+ ret = -ENODEV;
+ goto out;
+ }
+ ret = omci_agent_mib_delete(odev, class_id, entity_id);
+ if (!ret)
+ omci_device_notify(odev, OMCI_EVENT_MIB_CHANGE);
+out:
+ mutex_unlock(&omci_devices_lock);
+ return ret;
+}
+
+static int omci_cmd_mib_reset(struct sk_buff *skb, struct genl_info *info)
+{
+ struct omci_device *odev;
+ int ret = 0;
+
+ mutex_lock(&omci_devices_lock);
+ odev = omci_get_from_info(info);
+ if (!odev) {
+ ret = -ENODEV;
+ goto out;
+ }
+ omci_agent_mib_reset(odev, true);
+ omci_device_notify(odev, OMCI_EVENT_MIB_CHANGE);
+out:
+ mutex_unlock(&omci_devices_lock);
+ return ret;
+}
+
+static int omci_cmd_mib_next(struct sk_buff *skb, struct genl_info *info)
+{
+ struct omci_mib_object *object;
+ struct omci_device *odev;
+ const char *name = NULL;
+ u32 index = 0;
+ u32 next_index;
+ int ret;
+
+ if (info->attrs[OMCI_ATTR_INDEX])
+ index = nla_get_u32(info->attrs[OMCI_ATTR_INDEX]);
+
+ object = kzalloc(sizeof(*object), GFP_KERNEL);
+ if (!object)
+ return -ENOMEM;
+
+ mutex_lock(&omci_devices_lock);
+ odev = omci_get_from_info(info);
+ if (!odev) {
+ ret = -ENODEV;
+ goto out;
+ }
+ ret = omci_agent_mib_next(odev, index, object, &next_index, &name);
+ if (!ret)
+ ret = omci_reply_mib(info, OMCI_CMD_MIB_NEXT, odev, object,
+ next_index, name);
+out:
+ mutex_unlock(&omci_devices_lock);
+ kfree(object);
+ return ret;
+}
+
+static const struct genl_ops omci_genl_ops[] = {
+ {
+ .cmd = OMCI_CMD_GET,
+ .policy = omci_policy,
+ .doit = omci_cmd_get,
+ },
+ {
+ .cmd = OMCI_CMD_BIND,
+ .flags = GENL_ADMIN_PERM,
+ .policy = omci_policy,
+ .doit = omci_cmd_bind,
+ },
+ {
+ .cmd = OMCI_CMD_UNBIND,
+ .flags = GENL_ADMIN_PERM,
+ .policy = omci_policy,
+ .doit = omci_cmd_unbind,
+ },
+ {
+ .cmd = OMCI_CMD_TX,
+ .flags = GENL_ADMIN_PERM,
+ .policy = omci_policy,
+ .doit = omci_cmd_tx,
+ },
+ {
+ .cmd = OMCI_CMD_AGENT_SET,
+ .flags = GENL_ADMIN_PERM,
+ .policy = omci_policy,
+ .doit = omci_cmd_agent_set,
+ },
+ {
+ .cmd = OMCI_CMD_CONFIG_GET,
+ .flags = GENL_ADMIN_PERM,
+ .policy = omci_policy,
+ .doit = omci_cmd_config_get,
+ },
+ {
+ .cmd = OMCI_CMD_CONFIG_SET,
+ .flags = GENL_ADMIN_PERM,
+ .policy = omci_policy,
+ .doit = omci_cmd_config_set,
+ },
+ {
+ .cmd = OMCI_CMD_MIB_GET,
+ .policy = omci_policy,
+ .doit = omci_cmd_mib_get,
+ },
+ {
+ .cmd = OMCI_CMD_MIB_SET,
+ .flags = GENL_ADMIN_PERM,
+ .policy = omci_policy,
+ .doit = omci_cmd_mib_set,
+ },
+ {
+ .cmd = OMCI_CMD_MIB_DELETE,
+ .flags = GENL_ADMIN_PERM,
+ .policy = omci_policy,
+ .doit = omci_cmd_mib_delete,
+ },
+ {
+ .cmd = OMCI_CMD_MIB_RESET,
+ .flags = GENL_ADMIN_PERM,
+ .policy = omci_policy,
+ .doit = omci_cmd_mib_reset,
+ },
+ {
+ .cmd = OMCI_CMD_MIB_NEXT,
+ .policy = omci_policy,
+ .doit = omci_cmd_mib_next,
+ },
+ {
+ .cmd = OMCI_CMD_CLASS_GET,
+ .policy = omci_policy,
+ .doit = omci_cmd_class_get,
+ },
+ {
+ .cmd = OMCI_CMD_CLASS_NEXT,
+ .policy = omci_policy,
+ .doit = omci_cmd_class_next,
+ },
+};
+
+static struct genl_family omci_genl_family __ro_after_init = {
+ .name = OMCI_GENL_NAME,
+ .version = OMCI_GENL_VERSION,
+ .maxattr = OMCI_ATTR_MAX,
+ .module = THIS_MODULE,
+ .ops = omci_genl_ops,
+ .n_ops = ARRAY_SIZE(omci_genl_ops),
+};
+
+static int omci_send(struct omci_device *odev, struct sk_buff *msg,
+ u32 portid, struct net *net)
+{
+ int ret;
+
+ ret = genlmsg_unicast(net, msg, portid);
+ if (ret == -ESRCH || ret == -ECONNREFUSED) {
+ struct net *owner_net = NULL;
+
+ mutex_lock(&odev->owner_lock);
+ if (odev->owner_portid == portid && odev->owner_net == net) {
+ owner_net = odev->owner_net;
+ odev->owner_net = NULL;
+ odev->owner_portid = 0;
+ }
+ mutex_unlock(&odev->owner_lock);
+ if (owner_net)
+ put_net(owner_net);
+ }
+
+ return ret;
+}
+
+static void omci_rx_work(struct work_struct *work)
+{
+ struct omci_device *odev;
+ struct sk_buff *skb;
+
+ odev = container_of(work, struct omci_device, rx_work);
+ while ((skb = skb_dequeue(&odev->rx_queue))) {
+ struct omci_skb_cb *cb = OMCI_SKB_CB(skb);
+ struct net *net = NULL;
+ struct sk_buff *msg;
+ u32 portid = 0;
+ u16 onu_id;
+ void *hdr;
+ int ret;
+
+ spin_lock_bh(&odev->state_lock);
+ onu_id = odev->onu_id;
+ if (cb->generation != odev->generation) {
+ spin_unlock_bh(&odev->state_lock);
+ atomic64_inc(&odev->rx_dropped);
+ dev_kfree_skb_any(skb);
+ continue;
+ }
+ spin_unlock_bh(&odev->state_lock);
+
+ omci_agent_receive(odev, skb);
+
+ mutex_lock(&odev->owner_lock);
+ if (odev->owner_portid && odev->owner_net) {
+ portid = odev->owner_portid;
+ net = get_net(odev->owner_net);
+ }
+ mutex_unlock(&odev->owner_lock);
+
+ if (!net) {
+ dev_kfree_skb_any(skb);
+ continue;
+ }
+
+ msg = genlmsg_new(NLMSG_DEFAULT_SIZE + skb->len, GFP_KERNEL);
+ if (!msg) {
+ atomic64_inc(&odev->rx_dropped);
+ put_net(net);
+ dev_kfree_skb_any(skb);
+ continue;
+ }
+
+ hdr = genlmsg_put(msg, 0, 0, &omci_genl_family, 0,
+ OMCI_CMD_RX);
+ if (!hdr ||
+ nla_put_u32(msg, OMCI_ATTR_DEV_ID, odev->id) ||
+ nla_put_u16(msg, OMCI_ATTR_ONU_ID, onu_id) ||
+ nla_put_u16(msg, OMCI_ATTR_GEM_PORT_ID, cb->gem_port_id) ||
+ nla_put_u32(msg, OMCI_ATTR_FLAGS, cb->flags) ||
+ nla_put_u64_64bit(msg, OMCI_ATTR_SEQUENCE, cb->sequence,
+ OMCI_ATTR_PAD) ||
+ nla_put(msg, OMCI_ATTR_PDU, skb->len, skb->data)) {
+ nlmsg_free(msg);
+ atomic64_inc(&odev->rx_dropped);
+ put_net(net);
+ dev_kfree_skb_any(skb);
+ continue;
+ }
+
+ genlmsg_end(msg, hdr);
+ ret = omci_send(odev, msg, portid, net);
+ if (ret)
+ atomic64_inc(&odev->rx_dropped);
+ put_net(net);
+ dev_kfree_skb_any(skb);
+ }
+}
+
+void omci_device_notify(struct omci_device *odev, u8 event)
+{
+ struct net *net = NULL;
+ struct sk_buff *msg;
+ u32 portid = 0;
+ void *hdr;
+
+ mutex_lock(&odev->owner_lock);
+ if (odev->owner_portid && odev->owner_net) {
+ portid = odev->owner_portid;
+ net = get_net(odev->owner_net);
+ }
+ mutex_unlock(&odev->owner_lock);
+ if (!net)
+ return;
+
+ msg = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ goto out_put_net;
+
+ hdr = genlmsg_put(msg, 0, 0, &omci_genl_family, 0,
+ OMCI_CMD_EVENT);
+ if (!hdr || nla_put_u8(msg, OMCI_ATTR_EVENT, event) ||
+ omci_put_status(msg, odev)) {
+ nlmsg_free(msg);
+ goto out_put_net;
+ }
+
+ genlmsg_end(msg, hdr);
+ omci_send(odev, msg, portid, net);
+
+out_put_net:
+ put_net(net);
+}
+
+static int omci_netlink_notify(struct notifier_block *nb, unsigned long state,
+ void *data)
+{
+ struct netlink_notify *notify = data;
+ struct omci_device *odev;
+
+ if (state != NETLINK_URELEASE || notify->protocol != NETLINK_GENERIC)
+ return NOTIFY_DONE;
+
+ mutex_lock(&omci_devices_lock);
+ list_for_each_entry(odev, &omci_devices, list) {
+ struct net *owner_net = NULL;
+
+ mutex_lock(&odev->owner_lock);
+ if (odev->owner_portid == notify->portid &&
+ odev->owner_net == notify->net) {
+ owner_net = odev->owner_net;
+ odev->owner_net = NULL;
+ odev->owner_portid = 0;
+ }
+ mutex_unlock(&odev->owner_lock);
+ if (owner_net)
+ put_net(owner_net);
+ }
+ mutex_unlock(&omci_devices_lock);
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block omci_netlink_nb = {
+ .notifier_call = omci_netlink_notify,
+};
+
+struct omci_device *
+omci_device_register(struct xpon_device *xpon, u32 capabilities,
+ const struct omci_device_ops *ops, void *priv)
+{
+ struct omci_device *odev;
+
+ if (!xpon || !ops || !ops->xmit)
+ return ERR_PTR(-EINVAL);
+ if (xpon->omci)
+ return ERR_PTR(-EBUSY);
+
+ odev = kzalloc(sizeof(*odev), GFP_KERNEL);
+ if (!odev)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&odev->list);
+ mutex_init(&odev->lifecycle_lock);
+ mutex_init(&odev->owner_lock);
+ spin_lock_init(&odev->state_lock);
+ skb_queue_head_init(&odev->rx_queue);
+ INIT_WORK(&odev->rx_work, omci_rx_work);
+ odev->xpon = xpon;
+ odev->parent = xpon->class_dev;
+ odev->ifindex = xpon->netdev->ifindex;
+ odev->capabilities = capabilities | OMCI_CAP_BASELINE_AGENT;
+ odev->ops = ops;
+ odev->priv = priv;
+ odev->onu_id = 0xffff;
+ odev->gem_port_id = 0xffff;
+ odev->id = atomic_inc_return(&omci_next_id) - 1;
+
+ {
+ int ret = omci_agent_init(odev);
+
+ if (ret) {
+ kfree(odev);
+ return ERR_PTR(ret);
+ }
+
+ xpon->omci = odev;
+ ret = omci_sysfs_register(odev);
+ if (ret) {
+ xpon->omci = NULL;
+ omci_agent_cleanup(odev);
+ kfree(odev);
+ return ERR_PTR(ret);
+ }
+ }
+
+ mutex_lock(&omci_devices_lock);
+ list_add_tail(&odev->list, &omci_devices);
+ mutex_unlock(&omci_devices_lock);
+
+ dev_info(odev->parent, "registered OMCI agent device %u\n", odev->id);
+ return odev;
+}
+EXPORT_SYMBOL_GPL(omci_device_register);
+
+int omci_device_start(struct omci_device *odev)
+{
+ int ret = 0;
+
+ if (!odev)
+ return -EINVAL;
+
+ mutex_lock(&odev->lifecycle_lock);
+ if (odev->started)
+ goto out;
+
+ /*
+ * Publish the lifecycle state before entering the provider so state and
+ * receive callbacks generated synchronously by start() are accepted.
+ */
+ WRITE_ONCE(odev->started, true);
+ if (odev->ops->start) {
+ ret = odev->ops->start(odev);
+ if (ret)
+ WRITE_ONCE(odev->started, false);
+ }
+out:
+ mutex_unlock(&odev->lifecycle_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(omci_device_start);
+
+void omci_device_stop(struct omci_device *odev)
+{
+ if (!odev)
+ return;
+
+ mutex_lock(&odev->lifecycle_lock);
+ if (!odev->started)
+ goto out;
+
+ /* Let the provider drain the active OMCC before rejecting new traffic. */
+ if (odev->ops->stop)
+ odev->ops->stop(odev);
+ WRITE_ONCE(odev->started, false);
+out:
+ mutex_unlock(&odev->lifecycle_lock);
+}
+EXPORT_SYMBOL_GPL(omci_device_stop);
+
+void omci_device_unregister(struct omci_device *odev)
+{
+ struct net *owner_net;
+
+ if (!odev)
+ return;
+
+ mutex_lock(&omci_devices_lock);
+ list_del_init(&odev->list);
+ mutex_unlock(&omci_devices_lock);
+ omci_device_stop(odev);
+
+ cancel_work_sync(&odev->rx_work);
+ skb_queue_purge(&odev->rx_queue);
+
+ mutex_lock(&odev->owner_lock);
+ owner_net = odev->owner_net;
+ odev->owner_net = NULL;
+ odev->owner_portid = 0;
+ mutex_unlock(&odev->owner_lock);
+ if (owner_net)
+ put_net(owner_net);
+ omci_sysfs_unregister(odev);
+ if (odev->xpon && odev->xpon->omci == odev)
+ odev->xpon->omci = NULL;
+ omci_agent_cleanup(odev);
+ kfree(odev);
+}
+EXPORT_SYMBOL_GPL(omci_device_unregister);
+
+void *omci_device_priv(const struct omci_device *odev)
+{
+ return odev->priv;
+}
+EXPORT_SYMBOL_GPL(omci_device_priv);
+
+u32 omci_device_id(const struct omci_device *odev)
+{
+ return odev->id;
+}
+EXPORT_SYMBOL_GPL(omci_device_id);
+
+void omci_device_set_identity_info(struct omci_device *odev,
+ const struct omci_identity *identity)
+{
+ if (!odev || !identity)
+ return;
+ if (identity->valid & OMCI_IDENTITY_F_SERIAL_NUMBER)
+ omci_agent_config_set_source(odev, OMCI_CONFIG_SERIAL_NUMBER,
+ identity->serial_number,
+ sizeof(identity->serial_number),
+ identity->serial_source);
+ if (identity->valid & OMCI_IDENTITY_F_VENDOR_ID)
+ omci_agent_config_set_source(odev, OMCI_CONFIG_VENDOR_ID,
+ identity->vendor_id,
+ sizeof(identity->vendor_id),
+ identity->vendor_source);
+ if (identity->valid & OMCI_IDENTITY_F_PASSWORD)
+ omci_agent_config_set_source(odev, OMCI_CONFIG_PASSWORD,
+ identity->password,
+ sizeof(identity->password),
+ identity->password_source);
+ if (identity->valid & OMCI_IDENTITY_F_VERSION)
+ omci_agent_config_set_source(odev, OMCI_CONFIG_VERSION,
+ identity->version,
+ sizeof(identity->version),
+ identity->version_source);
+ if (identity->valid & OMCI_IDENTITY_F_EQUIPMENT_ID)
+ omci_agent_config_set_source(odev, OMCI_CONFIG_EQUIPMENT_ID,
+ identity->equipment_id,
+ sizeof(identity->equipment_id),
+ identity->equipment_source);
+}
+EXPORT_SYMBOL_GPL(omci_device_set_identity_info);
+
+void omci_device_set_identity(struct omci_device *odev,
+ const u8 serial_number[8],
+ const u8 password[10])
+{
+ struct omci_identity identity = {};
+
+ if (serial_number) {
+ memcpy(identity.serial_number, serial_number,
+ sizeof(identity.serial_number));
+ memcpy(identity.vendor_id, serial_number,
+ sizeof(identity.vendor_id));
+ identity.valid |= OMCI_IDENTITY_F_SERIAL_NUMBER |
+ OMCI_IDENTITY_F_VENDOR_ID;
+ identity.serial_source = OMCI_CONFIG_SOURCE_DRIVER;
+ identity.vendor_source = OMCI_CONFIG_SOURCE_DRIVER;
+ }
+ if (password) {
+ memcpy(identity.password, password, sizeof(identity.password));
+ identity.valid |= OMCI_IDENTITY_F_PASSWORD;
+ identity.password_source = OMCI_CONFIG_SOURCE_DRIVER;
+ }
+ omci_device_set_identity_info(odev, &identity);
+}
+EXPORT_SYMBOL_GPL(omci_device_set_identity);
+
+int omci_device_set_dying_gasp_enabled(struct omci_device *odev,
+ bool enabled, u8 source)
+{
+ u8 value = enabled;
+
+ if (!odev || source > OMCI_CONFIG_SOURCE_SYSFS)
+ return -EINVAL;
+
+ return omci_agent_config_set_source(odev,
+ OMCI_CONFIG_AGENT_DYING_GASP,
+ &value, sizeof(value), source);
+}
+EXPORT_SYMBOL_GPL(omci_device_set_dying_gasp_enabled);
+
+int omci_device_send_dying_gasp(struct omci_device *odev)
+{
+ if (!odev)
+ return -EINVAL;
+
+ return omci_agent_send_dying_gasp(odev);
+}
+EXPORT_SYMBOL_GPL(omci_device_send_dying_gasp);
+
+void omci_device_set_onu_id(struct omci_device *odev, u16 onu_id)
+{
+ spin_lock_bh(&odev->state_lock);
+ odev->onu_id = onu_id;
+ spin_unlock_bh(&odev->state_lock);
+}
+EXPORT_SYMBOL_GPL(omci_device_set_onu_id);
+
+void omci_device_set_channel(struct omci_device *odev, u16 gem_port_id,
+ bool valid)
+{
+ u8 event = valid ? OMCI_EVENT_CHANNEL_UP : OMCI_EVENT_CHANNEL_DOWN;
+ bool changed;
+
+ spin_lock_bh(&odev->state_lock);
+ changed = odev->channel_up != valid ||
+ (valid && odev->gem_port_id != gem_port_id);
+ if (changed)
+ odev->generation++;
+ odev->channel_up = valid;
+ odev->gem_port_id = valid ? gem_port_id : 0xffff;
+ spin_unlock_bh(&odev->state_lock);
+
+ if (!valid)
+ skb_queue_purge(&odev->rx_queue);
+ if (changed) {
+ omci_agent_channel_changed(odev, valid);
+ omci_device_notify(odev, event);
+ }
+}
+EXPORT_SYMBOL_GPL(omci_device_set_channel);
+
+void omci_device_set_state(struct omci_device *odev, u8 state)
+{
+ bool changed;
+
+ spin_lock_bh(&odev->state_lock);
+ changed = odev->state != state;
+ odev->state = state;
+ spin_unlock_bh(&odev->state_lock);
+
+ if (changed)
+ omci_device_notify(odev, OMCI_EVENT_STATE_CHANGE);
+}
+EXPORT_SYMBOL_GPL(omci_device_set_state);
+
+void omci_device_reset_session(struct omci_device *odev)
+{
+ if (!odev)
+ return;
+
+ /*
+ * Finish requests already accepted on the old OMCC before changing
+ * the generation. A Deactivate_ONU-ID PLOAM can race with an OMCI
+ * request carried by the same downstream frame.
+ */
+ if (current_work() != &odev->rx_work)
+ flush_work(&odev->rx_work);
+
+ spin_lock_bh(&odev->state_lock);
+ odev->generation++;
+ odev->onu_id = 0xffff;
+ odev->gem_port_id = 0xffff;
+ odev->channel_up = false;
+ spin_unlock_bh(&odev->state_lock);
+
+ if (current_work() != &odev->rx_work)
+ cancel_work_sync(&odev->rx_work);
+ skb_queue_purge(&odev->rx_queue);
+ omci_agent_channel_changed(odev, false);
+ omci_device_notify(odev, OMCI_EVENT_CHANNEL_DOWN);
+}
+EXPORT_SYMBOL_GPL(omci_device_reset_session);
+
+void omci_device_receive(struct omci_device *odev, struct sk_buff *skb,
+ u16 gem_port_id, u32 flags)
+{
+ struct omci_skb_cb *cb;
+ bool channel_up;
+ u16 configured_gem;
+ u32 generation;
+
+ if (!skb)
+ return;
+ if (!odev) {
+ dev_kfree_skb_any(skb);
+ return;
+ }
+ if (!READ_ONCE(odev->started)) {
+ atomic64_inc(&odev->rx_dropped);
+ dev_kfree_skb_any(skb);
+ return;
+ }
+
+ spin_lock_bh(&odev->state_lock);
+ channel_up = odev->channel_up;
+ configured_gem = odev->gem_port_id;
+ generation = odev->generation;
+ spin_unlock_bh(&odev->state_lock);
+
+ if (!channel_up || gem_port_id != configured_gem || !skb->len ||
+ skb->len > OMCI_MAX_PDU_LEN) {
+ atomic64_inc(&odev->rx_dropped);
+ dev_kfree_skb_any(skb);
+ return;
+ }
+
+ if (skb_queue_len(&odev->rx_queue) >= OMCI_RX_QUEUE_LEN) {
+ atomic64_inc(&odev->rx_dropped);
+ dev_kfree_skb_any(skb);
+ return;
+ }
+
+ cb = OMCI_SKB_CB(skb);
+ memset(cb, 0, sizeof(*cb));
+ cb->sequence = atomic64_inc_return(&odev->sequence);
+ cb->flags = flags;
+ cb->generation = generation;
+ cb->gem_port_id = gem_port_id;
+ atomic64_inc(&odev->rx_packets);
+ atomic64_add(skb->len, &odev->rx_bytes);
+ skb_queue_tail(&odev->rx_queue, skb);
+ schedule_work(&odev->rx_work);
+}
+EXPORT_SYMBOL_GPL(omci_device_receive);
+
+static int __init omci_init(void)
+{
+ int ret;
+
+ ret = omci_sysfs_init();
+ if (ret)
+ return ret;
+
+ ret = genl_register_family(&omci_genl_family);
+ if (ret)
+ goto err_sysfs;
+
+ ret = netlink_register_notifier(&omci_netlink_nb);
+ if (ret)
+ goto err_genl;
+
+ return 0;
+
+err_genl:
+ genl_unregister_family(&omci_genl_family);
+err_sysfs:
+ omci_sysfs_exit();
+ return ret;
+}
+module_init(omci_init);
+
+static void __exit omci_exit(void)
+{
+ netlink_unregister_notifier(&omci_netlink_nb);
+ genl_unregister_family(&omci_genl_family);
+ omci_sysfs_exit();
+}
+module_exit(omci_exit);
+
+MODULE_DESCRIPTION("Generic in-kernel OMCI agent");
+MODULE_LICENSE("GPL");
diff --git a/net/xpon/omci/identity.c b/net/xpon/omci/identity.c
new file mode 100644
index 000000000000..d71cc8caf078
--- /dev/null
+++ b/net/xpon/omci/identity.c
@@ -0,0 +1,466 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * OMCI identity loading and normalization
+ */
+
+#include <linux/ctype.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/unaligned.h>
+
+#include "internal.h"
+
+#define OMCI_IDENTITY_MAX_INPUT_LEN 64
+
+static const u8 default_vendor_id[4] = { 'G', 'P', 'O', 'N' };
+
+static bool omci_source_can_replace(u8 current_source, u8 source)
+{
+ return source >= current_source;
+}
+
+static size_t omci_trim_input(const u8 **data, size_t len)
+{
+ while (len && isspace(**data)) {
+ (*data)++;
+ len--;
+ }
+ while (len && (data[0][len - 1] == '\0' ||
+ data[0][len - 1] == 0xff ||
+ isspace(data[0][len - 1])))
+ len--;
+
+ return len;
+}
+
+static int omci_hex_compact(const u8 *data, size_t len, u8 *output,
+ size_t output_len)
+{
+ size_t digits = 0;
+ size_t i;
+ int high = -1;
+
+ if (len >= 4 && !strncasecmp((const char *)data, "hex:", 4)) {
+ data += 4;
+ len -= 4;
+ } else if (len >= 2 && data[0] == '0' &&
+ (data[1] == 'x' || data[1] == 'X')) {
+ data += 2;
+ len -= 2;
+ }
+
+ memset(output, 0, output_len);
+ for (i = 0; i < len; i++) {
+ int value;
+
+ if (data[i] == ':' || data[i] == '-' || data[i] == '.' ||
+ data[i] == '_' || isspace(data[i]))
+ continue;
+ value = hex_to_bin(data[i]);
+ if (value < 0)
+ return -EINVAL;
+ if (high < 0) {
+ high = value;
+ } else {
+ if (digits / 2 >= output_len)
+ return -E2BIG;
+ output[digits / 2] = (high << 4) | value;
+ high = -1;
+ }
+ digits++;
+ }
+
+ if (high >= 0 || digits != output_len * 2)
+ return -EINVAL;
+
+ return 0;
+}
+
+static bool omci_all_hex(const u8 *data, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ if (hex_to_bin(data[i]) < 0)
+ return false;
+ return true;
+}
+
+static int omci_normalize_serial(const u8 *data, size_t len, u8 serial[8])
+{
+ const u8 *trimmed = data;
+ u8 suffix[4];
+ int ret;
+
+
+ switch (len) {
+ case 8:
+ memcpy(serial, data, 8);
+ return 0;
+ case 16:
+ if (omci_all_hex(trimmed, 16))
+ return omci_hex_compact(trimmed, 16, serial, 8);
+ break;
+ }
+ len = omci_trim_input(&trimmed, len);
+ if (len == 12 && omci_all_hex(trimmed + 4, 8)) {
+ ret = omci_hex_compact(trimmed + 4, 8, suffix, sizeof(suffix));
+ if (ret)
+ return ret;
+ memcpy(serial, trimmed, 4);
+ memcpy(serial + 4, suffix, sizeof(suffix));
+ return 0;
+ }
+
+ return omci_hex_compact(trimmed, len, serial, 8);
+}
+
+static int omci_normalize_vendor(const u8 *data, size_t len, u8 vendor[4])
+{
+ const u8 *trimmed = data;
+
+ if (len == 4) {
+ memcpy(vendor, data, 4);
+ return 0;
+ }
+ len = omci_trim_input(&trimmed, len);
+ return omci_hex_compact(trimmed, len, vendor, 4);
+}
+
+static int omci_normalize_password(const u8 *data, size_t len,
+ u8 password[10])
+{
+ const u8 *trimmed = data;
+
+ if (len == 10) {
+ memcpy(password, data, 10);
+ return 0;
+ }
+ len = omci_trim_input(&trimmed, len);
+ if (!len)
+ return -EINVAL;
+ if (len == 20 || (len > 4 && !strncasecmp((const char *)trimmed, "hex:", 4)))
+ return omci_hex_compact(trimmed, len, password, 10);
+ if (len > 10)
+ return -E2BIG;
+
+ memset(password, 0, 10);
+ memcpy(password, trimmed, len);
+ return 0;
+}
+
+static int omci_normalize_string(const u8 *data, size_t len, u8 *output,
+ size_t output_len)
+{
+ const u8 *trimmed = data;
+
+ len = omci_trim_input(&trimmed, len);
+ if (!len || len > output_len)
+ return -EINVAL;
+ memset(output, 0, output_len);
+ memcpy(output, trimmed, len);
+ return 0;
+}
+
+static int omci_identity_set_serial(struct omci_identity *identity,
+ const u8 *data, size_t len, u8 source)
+{
+ u8 serial[8];
+ int ret;
+
+ ret = omci_normalize_serial(data, len, serial);
+ if (ret)
+ return ret;
+ if (omci_source_can_replace(identity->serial_source, source)) {
+ memcpy(identity->serial_number, serial, sizeof(serial));
+ identity->serial_source = source;
+ identity->valid |= OMCI_IDENTITY_F_SERIAL_NUMBER;
+ }
+ if (omci_source_can_replace(identity->vendor_source, source)) {
+ memcpy(identity->vendor_id, serial, sizeof(identity->vendor_id));
+ identity->vendor_source = source;
+ identity->valid |= OMCI_IDENTITY_F_VENDOR_ID;
+ }
+ return 0;
+}
+
+static int omci_identity_set_vendor(struct omci_identity *identity,
+ const u8 *data, size_t len, u8 source)
+{
+ u8 vendor[4];
+ int ret;
+
+ ret = omci_normalize_vendor(data, len, vendor);
+ if (ret)
+ return ret;
+ if (!omci_source_can_replace(identity->vendor_source, source))
+ return 0;
+
+ memcpy(identity->vendor_id, vendor, sizeof(vendor));
+ identity->vendor_source = source;
+ identity->valid |= OMCI_IDENTITY_F_VENDOR_ID;
+ if (identity->valid & OMCI_IDENTITY_F_SERIAL_NUMBER)
+ memcpy(identity->serial_number, vendor, sizeof(vendor));
+ return 0;
+}
+
+static int omci_identity_set_password(struct omci_identity *identity,
+ const u8 *data, size_t len, u8 source)
+{
+ u8 password[10];
+ int ret;
+
+ ret = omci_normalize_password(data, len, password);
+ if (ret)
+ return ret;
+ if (!omci_source_can_replace(identity->password_source, source))
+ return 0;
+
+ memcpy(identity->password, password, sizeof(password));
+ identity->password_source = source;
+ identity->valid |= OMCI_IDENTITY_F_PASSWORD;
+ return 0;
+}
+
+static int __maybe_unused omci_identity_set_version(struct
omci_identity *identity,
+ const u8 *data,
+ size_t len, u8 source)
+{
+ u8 version[OMCI_OLT_VERSION_LEN];
+ int ret;
+
+ ret = omci_normalize_string(data, len, version, sizeof(version));
+ if (ret)
+ return ret;
+ if (!omci_source_can_replace(identity->version_source, source))
+ return 0;
+
+ memcpy(identity->version, version, sizeof(version));
+ identity->version_source = source;
+ identity->valid |= OMCI_IDENTITY_F_VERSION;
+ return 0;
+}
+
+static int __maybe_unused omci_identity_set_equipment(struct
omci_identity *identity,
+ const u8 *data,
+ size_t len, u8 source)
+{
+ u8 equipment[OMCI_OLT_EQUIPMENT_ID_LEN];
+ int ret;
+
+ ret = omci_normalize_string(data, len, equipment, sizeof(equipment));
+ if (ret)
+ return ret;
+ if (!omci_source_can_replace(identity->equipment_source, source))
+ return 0;
+
+ memcpy(identity->equipment_id, equipment, sizeof(equipment));
+ identity->equipment_source = source;
+ identity->valid |= OMCI_IDENTITY_F_EQUIPMENT_ID;
+ return 0;
+}
+
+int omci_identity_normalize_config(u16 key, const void *value, size_t len,
+ u8 *output, size_t *output_len)
+{
+ size_t required;
+ int ret;
+
+ if (!value || !output || !output_len)
+ return -EINVAL;
+ switch (key) {
+ case OMCI_CONFIG_SERIAL_NUMBER:
+ required = 8;
+ if (*output_len < required)
+ return -ENOSPC;
+ ret = omci_normalize_serial(value, len, output);
+ break;
+ case OMCI_CONFIG_VENDOR_ID:
+ required = 4;
+ if (*output_len < required)
+ return -ENOSPC;
+ ret = omci_normalize_vendor(value, len, output);
+ break;
+ case OMCI_CONFIG_PASSWORD:
+ required = 10;
+ if (*output_len < required)
+ return -ENOSPC;
+ ret = omci_normalize_password(value, len, output);
+ break;
+ case OMCI_CONFIG_VERSION:
+ case OMCI_CONFIG_HARDWARE_VERSION:
+ case OMCI_CONFIG_SOFTWARE_VERSION_0:
+ case OMCI_CONFIG_SOFTWARE_VERSION_1:
+ required = OMCI_HARDWARE_VERSION_LEN;
+ if (*output_len < required)
+ return -ENOSPC;
+ ret = omci_normalize_string(value, len, output, required);
+ break;
+ case OMCI_CONFIG_EQUIPMENT_ID:
+ required = OMCI_OLT_EQUIPMENT_ID_LEN;
+ if (*output_len < required)
+ return -ENOSPC;
+ ret = omci_normalize_string(value, len, output, required);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ if (ret)
+ return ret;
+ *output_len = required;
+ return 0;
+}
+
+typedef int (*omci_identity_setter_t)(struct omci_identity *identity,
+ const u8 *data, size_t len, u8 source);
+
+static int omci_identity_read_property(struct device *dev,
+ struct omci_identity *identity,
+ const char *name,
+ omci_identity_setter_t setter)
+{
+ u8 data[OMCI_IDENTITY_MAX_INPUT_LEN];
+ const char *string;
+ int count;
+ int ret;
+
+ ret = device_property_read_string(dev, name, &string);
+ if (!ret)
+ return setter(identity, string, strlen(string),
+ OMCI_CONFIG_SOURCE_DEVICE_TREE);
+
+ count = device_property_count_u8(dev, name);
+ if (count < 0)
+ return count == -EINVAL || count == -ENODATA ? 0 : count;
+ if (!count || count > sizeof(data))
+ return -EINVAL;
+ ret = device_property_read_u8_array(dev, name, data, count);
+ if (ret)
+ return ret;
+ return setter(identity, data, count, OMCI_CONFIG_SOURCE_DEVICE_TREE);
+}
+
+static int omci_identity_read_cell(struct device *dev,
+ struct omci_identity *identity,
+ const char *name,
+ omci_identity_setter_t setter)
+{
+ struct nvmem_cell *cell;
+ void *data;
+ size_t len;
+ int ret;
+
+ cell = nvmem_cell_get(dev, name);
+ if (IS_ERR(cell)) {
+ ret = PTR_ERR(cell);
+ if (ret == -ENOENT || ret == -ENODEV || ret == -EOPNOTSUPP)
+ return 0;
+ return ret;
+ }
+ data = nvmem_cell_read(cell, &len);
+ nvmem_cell_put(cell);
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+ ret = setter(identity, data, len, OMCI_CONFIG_SOURCE_NVMEM);
+ kfree(data);
+ return ret;
+}
+
+static int omci_identity_load_properties(struct device *dev,
+ struct omci_identity *identity)
+{
+ static const struct {
+ const char *name;
+ omci_identity_setter_t setter;
+ } properties[] = {
+ { "omci-vendor-id", omci_identity_set_vendor },
+ };
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(properties); i++) {
+ ret = omci_identity_read_property(dev, identity,
+ properties[i].name,
+ properties[i].setter);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "invalid %s identity property\n",
+ properties[i].name);
+ }
+ return 0;
+}
+
+static int omci_identity_load_nvmem(struct device *dev,
+ struct omci_identity *identity)
+{
+ static const struct {
+ const char *name;
+ omci_identity_setter_t setter;
+ } cells[] = {
+ { "gpon-serial-number", omci_identity_set_serial },
+ { "gpon-password", omci_identity_set_password },
+ };
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(cells); i++) {
+ ret = omci_identity_read_cell(dev, identity, cells[i].name,
+ cells[i].setter);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read %s NVMEM cell\n",
+ cells[i].name);
+ }
+ return 0;
+}
+
+int omci_identity_load(struct device *dev, struct omci_identity *identity)
+{
+ int ret;
+
+ if (!dev || !identity)
+ return -EINVAL;
+ memset(identity, 0, sizeof(*identity));
+
+ ret = omci_identity_load_properties(dev, identity);
+ if (ret)
+ return ret;
+
+ return omci_identity_load_nvmem(dev, identity);
+}
+EXPORT_SYMBOL_GPL(omci_identity_load);
+
+void gpon_random_serial_number(const u8 vendor[4], struct
omci_identity *identity)
+{
+ u32 serial;
+
+ /*
+ * GPON serial numbers contain a four-byte vendor identifier followed by
+ * a four-byte vendor-specific value. Keep a configured vendor identifier
+ * when available and randomize only the vendor-specific part.
+ */
+ if (!(identity->valid & OMCI_IDENTITY_F_VENDOR_ID)) {
+ memcpy(identity->vendor_id,
+ (vendor == (u8[4]){0,0,0,0}) ? default_vendor_id : vendor,
+ sizeof(identity->vendor_id));
+ identity->valid |= OMCI_IDENTITY_F_VENDOR_ID;
+ identity->vendor_source = OMCI_CONFIG_SOURCE_DEFAULT;
+ }
+
+ do {
+ serial = get_random_u32();
+ } while (!serial || serial == U32_MAX);
+
+ memcpy(identity->serial_number, identity->vendor_id,
+ sizeof(identity->vendor_id));
+ put_unaligned_be32(serial, identity->serial_number + 4);
+ identity->valid |= OMCI_IDENTITY_F_SERIAL_NUMBER;
+ identity->serial_source = OMCI_CONFIG_SOURCE_DRIVER;
+}
+EXPORT_SYMBOL_GPL(gpon_random_serial_number);
diff --git a/net/xpon/omci/internal.h b/net/xpon/omci/internal.h
new file mode 100644
index 000000000000..9370fe365850
--- /dev/null
+++ b/net/xpon/omci/internal.h
@@ -0,0 +1,203 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _NET_OMCI_INTERNAL_H
+#define _NET_OMCI_INTERNAL_H
+
+#include <linux/atomic.h>
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+#include <linux/xarray.h>
+#include <net/net_namespace.h>
+#include <net/xpon/omci.h>
+
+#include "me.h"
+
+#define OMCI_RX_QUEUE_LEN 256
+#define OMCI_BASELINE_DEV_ID 0x0a
+#define OMCI_EXTENDED_DEV_ID 0x0b
+#define OMCI_BASELINE_LEN 48
+#define OMCI_BASELINE_LEN_NO_MIC 44
+#define OMCI_EXTENDED_HEADER_LEN 10
+#define OMCI_MIC_LEN 4
+
+struct omci_skb_cb {
+ u64 sequence;
+ u32 flags;
+ u32 generation;
+ u16 gem_port_id;
+};
+
+#define OMCI_SKB_CB(_skb) ((struct omci_skb_cb *)&((_skb)->cb[0]))
+
+struct omci_mib_object {
+ u16 class_id;
+ u16 entity_id;
+ u16 attr_mask;
+ u8 origin;
+ u8 owner_profile;
+ u8 data_len;
+ bool pending_delete;
+ u8 data[OMCI_MAX_ATTR_DATA];
+ struct omci_olt_g olt_g;
+ struct omci_vlan_tagging_filter vlan_filter;
+ struct omci_extended_vlan extended_vlan;
+};
+
+struct omci_service_state {
+ struct omci_service_config config;
+};
+
+struct omci_agent_config {
+ u8 serial_number[8];
+ u8 vendor_id[4];
+ u8 version[OMCI_HARDWARE_VERSION_LEN];
+ u8 software_version[2][OMCI_SOFTWARE_VERSION_LEN];
+ u8 equipment_id[20];
+ u8 password[10];
+ u8 serial_source;
+ u8 vendor_source;
+ u8 version_source;
+ u8 software_version_source[2];
+ u8 omcc_version;
+ u8 omcc_version_source;
+ u8 equipment_source;
+ u8 password_source;
+ u8 traffic_mgmt_option;
+ u8 onu_type;
+ u8 onu_type_source;
+ u8 uni_count;
+ u8 olt_profile;
+ u8 olt_profile_force;
+ u8 olt_profile_source;
+ u8 olt_profile_force_source;
+ u8 dying_gasp_source;
+};
+
+struct omci_agent {
+ /* Serializes configuration, MIB and transaction state. */
+ struct mutex lock;
+ struct xarray mib;
+ struct xarray services;
+ struct omci_agent_config config;
+ u32 upload_index;
+ u16 mib_sync;
+ bool enabled;
+ bool permissive;
+ bool fake_omci;
+ bool dying_gasp;
+ bool operational;
+ u8 alarm_sequence;
+ u8 profile_effective;
+ u32 profile_quirks;
+
+ struct sk_buff *last_request;
+ struct sk_buff *last_response;
+ u32 last_request_hash;
+ bool last_response_fake;
+ u8 *table_snapshot;
+ size_t table_snapshot_len;
+ unsigned long table_snapshot_jiffies;
+ u16 table_snapshot_class_id;
+ u16 table_snapshot_entity_id;
+ u16 table_snapshot_mask;
+
+ atomic64_t responses;
+ atomic64_t duplicates;
+ atomic64_t unsupported;
+ atomic64_t fake_responses;
+};
+
+struct omci_device {
+ struct list_head list;
+ struct xpon_device *xpon;
+ struct device *parent;
+ const struct omci_device_ops *ops;
+ void *priv;
+ u32 id;
+ u32 ifindex;
+ u32 capabilities;
+ /* Serializes provider transport ownership. */
+ struct mutex lifecycle_lock;
+ bool started;
+
+ /* Protect the optional userspace observer. */
+ struct mutex owner_lock;
+ struct net *owner_net;
+ u32 owner_portid;
+
+ /* Protect channel state accessed from the hardware RX path. */
+ spinlock_t state_lock;
+ u16 onu_id;
+ u16 gem_port_id;
+ u32 generation;
+ u8 state;
+ bool channel_up;
+
+ struct sk_buff_head rx_queue;
+ struct work_struct rx_work;
+ atomic64_t sequence;
+ atomic64_t rx_packets;
+ atomic64_t rx_bytes;
+ atomic64_t rx_dropped;
+ atomic64_t tx_packets;
+ atomic64_t tx_bytes;
+ atomic64_t tx_errors;
+
+ struct omci_agent agent;
+};
+
+int omci_validate_tx(struct omci_device *odev, struct sk_buff *skb);
+int omci_device_xmit(struct omci_device *odev, const void *data, size_t len);
+void omci_device_notify(struct omci_device *odev, u8 event);
+
+int omci_agent_init(struct omci_device *odev);
+void omci_agent_cleanup(struct omci_device *odev);
+void omci_agent_receive(struct omci_device *odev, const struct sk_buff *skb);
+void omci_agent_channel_changed(struct omci_device *odev, bool valid);
+int omci_agent_send_dying_gasp(struct omci_device *odev);
+int omci_agent_put_status(struct sk_buff *msg, struct omci_device *odev);
+int omci_agent_config_get(struct omci_device *odev, u16 key,
+ void *value, size_t *len);
+int omci_agent_config_set(struct omci_device *odev, u16 key,
+ const void *value, size_t len);
+int omci_agent_config_set_userspace(struct omci_device *odev, u16 key,
+ const void *value, size_t len, u8 source);
+int omci_agent_config_set_source(struct omci_device *odev, u16 key,
+ const void *value, size_t len, u8 source);
+int omci_agent_config_source_get(struct omci_device *odev, u16 key,
u8 *source);
+int omci_identity_normalize_config(u16 key, const void *value, size_t len,
+ u8 *output, size_t *output_len);
+int omci_agent_mib_get(struct omci_device *odev, u16 class_id, u16 entity_id,
+ struct omci_mib_object *object);
+int omci_agent_mib_set(struct omci_device *odev,
+ const struct omci_mib_object *object);
+int omci_agent_mib_delete(struct omci_device *odev, u16 class_id,
+ u16 entity_id);
+void omci_agent_mib_reset(struct omci_device *odev, bool all);
+int omci_agent_mib_next(struct omci_device *odev, u32 index,
+ struct omci_mib_object *object, u32 *next_index,
+ const char **name);
+int omci_agent_olt_g_get(struct omci_device *odev, struct omci_olt_g *olt);
+
+bool omci_profile_valid(u8 profile);
+bool omci_profile_forceable(u8 profile);
+u8 omci_profile_detect(const struct omci_olt_g *olt);
+u32 omci_profile_quirks(u8 profile);
+void omci_profile_sanitize_olt_g(struct omci_olt_g *olt, u32 quirks);
+u16 omci_profile_normalize_uni_entity(u8 profile, u16 entity_id);
+void omci_profile_normalize_vlan_rule(u8 profile,
+ struct omci_extended_vlan_rule *rule);
+int omci_profile_resolve_multicast_ani(u8 profile,
+ u16 bridge_port_entity_id,
+ u16 *ani_entity_id);
+
+int omci_sysfs_init(void);
+void omci_sysfs_exit(void);
+int omci_sysfs_register(struct omci_device *odev);
+void omci_sysfs_unregister(struct omci_device *odev);
+
+#endif /* _NET_OMCI_INTERNAL_H */
diff --git a/net/xpon/omci/me.c b/net/xpon/omci/me.c
new file mode 100644
index 000000000000..35c307e52daf
--- /dev/null
+++ b/net/xpon/omci/me.c
@@ -0,0 +1,1210 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * OMCI managed entity catalogue and attribute descriptors
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <net/xpon/omci.h>
+
+#include "internal.h"
+#include "me.h"
+
+struct omci_class_name {
+ u16 class_id;
+ const char *name;
+};
+
+static const struct omci_class_name omci_classes[] = {
+ { 1, "ONT" },
+ { 2, "ONU data" },
+ { 3, "PON IF line cardholder" },
+ { 4, "PON IF line card" },
+ { 5, "Cardholder" },
+ { 6, "Circuit pack" },
+ { 7, "Software image" },
+ { 8, "UNI" },
+ { 9, "TC adapter" },
+ { 10, "Physical path termination point ATM UNI" },
+ { 11, "Physical path termination point Ethernet UNI" },
+ { 12, "Physical path termination point CES UNI" },
+ { 13, "Logical N x 64 kbit/s sub-port connection termination point (CTP)" },
+ { 14, "Interworking VCC termination point" },
+ { 15, "AAL 1 profile" },
+ { 16, "AAL 5 profile" },
+ { 17, "AAL 1 protocol monitoring history data" },
+ { 18, "AAL 5 performance monitoring history data" },
+ { 19, "AAL 2 profile" },
+ { 20, "Intentionally left blank" },
+ { 21, "CES service profile" },
+ { 22, "Reserved" },
+ { 23, "CES physical interface performance monitoring history data" },
+ { 24, "Ethernet performance monitoring history data" },
+ { 25, "VP network CTP" },
+ { 26, "ATM VP cross-connection" },
+ { 27, "Priority queue" },
+ { 28, "DBR/CBR traffic descriptor" },
+ { 29, "UBR traffic descriptor" },
+ { 30, "SBR1/VBR1 traffic descriptor" },
+ { 31, "SBR2/VBR2 traffic descriptor" },
+ { 32, "SBR3/VBR3 traffic descriptor" },
+ { 33, "ABR traffic descriptor" },
+ { 34, "GFR traffic descriptor" },
+ { 35, "ABT/DT/IT traffic descriptor" },
+ { 36, "UPC disagreement monitoring history data" },
+ { 37, "Intentionally left blank" },
+ { 38, "ANI" },
+ { 39, "PON TC adapter" },
+ { 40, "PON physical path termination point" },
+ { 41, "TC adapter protocol monitoring history data" },
+ { 42, "Threshold data" },
+ { 43, "Operator specific" },
+ { 44, "Vendor specific" },
+ { 45, "MAC bridge service profile" },
+ { 46, "MAC bridge configuration data" },
+ { 47, "MAC bridge port configuration data" },
+ { 48, "MAC bridge port designation data" },
+ { 49, "MAC bridge port filter table data" },
+ { 50, "MAC bridge port bridge table data" },
+ { 51, "MAC bridge performance monitoring history data" },
+ { 52, "MAC bridge port performance monitoring history data" },
+ { 53, "Physical path termination point POTS UNI" },
+ { 54, "Voice CTP" },
+ { 55, "Voice PM history data" },
+ { 56, "AAL2 PVC profile" },
+ { 57, "AAL2 CPS protocol monitoring history data" },
+ { 58, "Voice service profile" },
+ { 59, "LES service profile" },
+ { 60, "AAL2 SSCS parameter profile 1" },
+ { 61, "AAL2 SSCS parameter profile 2" },
+ { 62, "VP performance monitoring history data" },
+ { 63, "Traffic scheduler" },
+ { 64, "T-CONT buffer" },
+ { 65, "UBR+ traffic descriptor" },
+ { 66, "AAL2 SSCS protocol monitoring history data" },
+ { 67, "IP port configuration data" },
+ { 68, "IP router service profile" },
+ { 69, "IP router configuration data" },
+ { 70, "IP router performance monitoring history data 1" },
+ { 71, "IP router performance monitoring history data 2" },
+ { 72, "ICMP performance monitoring history data 1" },
+ { 73, "ICMP performance monitoring history data 2" },
+ { 74, "IP route table" },
+ { 75, "IP static routes" },
+ { 76, "ARP service profile" },
+ { 77, "ARP configuration data" },
+ { 78, "VLAN tagging operation configuration data" },
+ { 79, "MAC bridge port filter pre-assign table" },
+ { 80, "Physical path termination point ISDN UNI" },
+ { 81, "Reserved" },
+ { 82, "Physical path termination point video UNI" },
+ { 83, "Physical path termination point LCT UNI" },
+ { 84, "VLAN tagging filter data" },
+ { 85, "ONU" },
+ { 86, "ATM VC cross-connection" },
+ { 87, "VC network CTP" },
+ { 88, "VC PM history data" },
+ { 89, "Ethernet performance monitoring history data 2" },
+ { 90, "Physical path termination point video ANI" },
+ { 91, "Physical path termination point IEEE 802.11 UNI" },
+ { 92, "IEEE 802.11 station management data 1" },
+ { 93, "IEEE 802.11 station management data 2" },
+ { 94, "IEEE 802.11 general purpose object" },
+ { 95, "IEEE 802.11 MAC and PHY operation and antenna data" },
+ { 96, "IEEE 802.11 performance monitoring history data" },
+ { 97, "IEEE 802.11 PHY FHSS DSSS IR tables" },
+ { 98, "Physical path termination point xDSL UNI part 1" },
+ { 99, "Physical path termination point xDSL UNI part 2" },
+ { 100, "xDSL line inventory and status data part 1" },
+ { 101, "xDSL line inventory and status data part 2" },
+ { 102, "xDSL channel downstream status data" },
+ { 103, "xDSL channel upstream status data" },
+ { 104, "xDSL line configuration profile part 1" },
+ { 105, "xDSL line configuration profile part 2" },
+ { 106, "xDSL line configuration profile part 3" },
+ { 107, "xDSL channel configuration profile" },
+ { 108, "xDSL subcarrier masking downstream profile" },
+ { 109, "xDSL subcarrier masking upstream profile" },
+ { 110, "xDSL PSD mask profile" },
+ { 111, "xDSL downstream radio frequency interference (RFI) bands profile" },
+ { 112, "xDSL xTU-C performance monitoring history data" },
+ { 113, "xDSL xTU-R performance monitoring history data" },
+ { 114, "xDSL xTU-C channel performance monitoring history data" },
+ { 115, "xDSL xTU-R channel performance monitoring history data" },
+ { 116, "TC adaptor performance monitoring history data xDSL" },
+ { 117, "Physical path termination point VDSL UNI (ITU-T G.993.1 VDSL1)" },
+ { 118, "VDSL VTU-O physical data" },
+ { 119, "VDSL VTU-R physical data" },
+ { 120, "VDSL channel data" },
+ { 121, "VDSL line configuration profile" },
+ { 122, "VDSL channel configuration profile" },
+ { 123, "VDSL band plan configuration profile" },
+ { 124, "VDSL VTU-O physical interface monitoring history data" },
+ { 125, "VDSL VTU-R physical interface monitoring history data" },
+ { 126, "VDSL VTU-O channel performance monitoring history data" },
+ { 127, "VDSL VTU-R channel performance monitoring history data" },
+ { 128, "Video return path service profile" },
+ { 129, "Video return path performance monitoring history data" },
+ { 130, "IEEE 802.1p mapper service profile" },
+ { 131, "OLT-G" },
+ { 132, "Multicast interworking VCC termination point" },
+ { 133, "ONU power shedding" },
+ { 134, "IP host config data" },
+ { 135, "IP host performance monitoring history data" },
+ { 136, "TCP/UDP config data" },
+ { 137, "Network address" },
+ { 138, "VoIP config data" },
+ { 139, "VoIP voice CTP" },
+ { 140, "Call control performance monitoring history data" },
+ { 141, "VoIP line status" },
+ { 142, "VoIP media profile" },
+ { 143, "RTP profile data" },
+ { 144, "RTP performance monitoring history data" },
+ { 145, "Network dial plan table" },
+ { 146, "VoIP application service profile" },
+ { 147, "VoIP feature access codes" },
+ { 148, "Authentication security method" },
+ { 149, "SIP config portal" },
+ { 150, "SIP agent config data" },
+ { 151, "SIP agent performance monitoring history data" },
+ { 152, "SIP call initiation performance monitoring history data" },
+ { 153, "SIP user data" },
+ { 154, "MGC config portal" },
+ { 155, "MGC config data" },
+ { 156, "MGC performance monitoring history data" },
+ { 157, "Large string" },
+ { 158, "ONU remote debug" },
+ { 159, "Equipment protection profile" },
+ { 160, "Equipment extension package" },
+ { 161, "Port-mapping package (legacy B-PON)" },
+ { 162, "Physical path termination point MoCA UNI" },
+ { 163, "MoCA Ethernet performance monitoring history data" },
+ { 164, "MoCA interface performance monitoring history data" },
+ { 165, "VDSL2 line configuration extensions" },
+ { 166, "xDSL line inventory and status data part 3" },
+ { 167, "xDSL line inventory and status data part 4" },
+ { 168, "VDSL2 line inventory and status data part 1" },
+ { 169, "VDSL2 line inventory and status data part 2" },
+ { 170, "VDSL2 line inventory and status data part 3" },
+ { 171, "Extended VLAN tagging operation configuration data" },
+ { 256, "ONU-G" },
+ { 257, "ONU2-G" },
+ { 258, "ONU-G (deprecated)" },
+ { 259, "ONU2-G (deprecated)" },
+ { 260, "PON IF line card-G" },
+ { 261, "PON TC adapter-G" },
+ { 262, "T-CONT" },
+ { 263, "ANI-G" },
+ { 264, "UNI-G" },
+ { 265, "ATM interworking VCC termination point" },
+ { 266, "GEM interworking termination point" },
+ { 267, "GEM port performance monitoring history data (obsolete)" },
+ { 268, "GEM port network CTP" },
+ { 269, "VP network CTP" },
+ { 270, "VC network CTP-G" },
+ { 271, "GAL TDM profile (deprecated)" },
+ { 272, "GAL Ethernet profile" },
+ { 273, "Threshold data 1" },
+ { 274, "Threshold data 2" },
+ { 275, "GAL TDM performance monitoring history data (deprecated)" },
+ { 276, "GAL Ethernet performance monitoring history data" },
+ { 277, "Priority queue" },
+ { 278, "Traffic scheduler" },
+ { 279, "Protection data" },
+ { 280, "Traffic descriptor" },
+ { 281, "Multicast GEM interworking termination point" },
+ { 282, "Pseudowire termination point" },
+ { 283, "RTP pseudowire parameters" },
+ { 284, "Pseudowire maintenance profile" },
+ { 285, "Pseudowire performance monitoring history data" },
+ { 286, "Ethernet flow termination point" },
+ { 287, "OMCI" },
+ { 288, "Managed entity" },
+ { 289, "Attribute" },
+ { 290, "Dot1X port extension package" },
+ { 291, "Dot1X configuration profile" },
+ { 292, "Dot1X performance monitoring history data" },
+ { 293, "Radius performance monitoring history data" },
+ { 294, "TU CTP" },
+ { 295, "TU performance monitoring history data" },
+ { 296, "Ethernet performance monitoring history data 3" },
+ { 297, "Port-mapping package" },
+ { 298, "Dot1 rate limiter" },
+ { 299, "Dot1ag maintenance domain" },
+ { 300, "Dot1ag maintenance association" },
+ { 301, "Dot1ag default MD level" },
+ { 302, "Dot1ag MEP" },
+ { 303, "Dot1ag MEP status" },
+ { 304, "Dot1ag MEP CCM database" },
+ { 305, "Dot1ag CFM stack" },
+ { 306, "Dot1ag chassis - management info" },
+ { 307, "Octet string" },
+ { 308, "General purpose buffer" },
+ { 309, "Multicast operations profile" },
+ { 310, "Multicast subscriber config info" },
+ { 311, "Multicast subscriber monitor" },
+ { 312, "FEC performance monitoring history data" },
+ { 313, "RE ANI-G" },
+ { 314, "Physical path termination point RE UNI" },
+ { 315, "RE upstream amplifier" },
+ { 316, "RE downstream amplifier" },
+ { 317, "RE config portal" },
+ { 318, "File transfer controller" },
+ { 319, "CES physical interface performance monitoring history data 2" },
+ { 320, "CES physical interface performance monitoring history data 3" },
+ { 321, "Ethernet frame performance monitoring history data downstream" },
+ { 322, "Ethernet frame performance monitoring history data upstream" },
+ { 323, "VDSL2 line configuration extensions 2" },
+ { 324, "xDSL impulse noise monitor performance monitoring history data" },
+ { 325, "xDSL line inventory and status data part 5" },
+ { 326, "xDSL line inventory and status data part 6" },
+ { 327, "xDSL line inventory and status data part 7" },
+ { 328, "RE common amplifier parameters" },
+ { 329, "Virtual Ethernet interface point" },
+ { 330, "Generic status portal" },
+ { 331, "ONU-E" },
+ { 332, "Enhanced security control" },
+ { 333, "MPLS pseudowire termination point" },
+ { 334, "Ethernet frame extended PM" },
+ { 335, "Simple network management protocol (SNMP) configuration data" },
+ { 336, "ONU dynamic power management control" },
+ { 337, "PW ATM configuration data" },
+ { 338, "PW ATM performance monitoring history data" },
+ { 339, "PW Ethernet configuration data" },
+ { 340, "BBF TR-069 management server" },
+ { 341, "GEM port network CTP performance monitoring history data" },
+ { 342, "TCP/UDP performance monitoring history data" },
+ { 343, "Energy consumption performance monitoring history data" },
+ { 344, "XG-PON TC performance monitoring history data" },
+ { 345, "XG-PON downstream management performance monitoring history data" },
+ { 346, "XG-PON upstream management performance monitoring history data" },
+ { 347, "IPv6 host config data" },
+ { 348, "MAC bridge port ICMPv6 process pre-assign table" },
+ { 349, "Power over Ethernet (PoE) control" },
+ { 400, "Ethernet pseudowire parameters" },
+ { 401, "Physical path termination point RS232/RS485 UNI" },
+ { 402, "RS232/RS485 port operation configuration data" },
+ { 403, "RS232/RS485 performance monitoring history data" },
+ { 404, "L2 multicast GEM interworking termination point" },
+ { 405, "ANI-E" },
+ { 406, "EPON downstream performance monitoring configuration" },
+ { 407, "SIP agent config data 2" },
+ { 408, "xDSL xTU-C performance monitoring history data part 2" },
+ { 409, "PTM performance monitoring history data xDSL" },
+ { 410, "VDSL2 line configuration extensions 3" },
+ { 411, "Vectoring line configuration extensions" },
+ { 412, "xDSL channel configuration profile part 2" },
+ { 413, "xTU data gathering configuration" },
+ { 414, "xDSL line inventory and status data part 8" },
+ { 415, "VDSL2 line inventory and status data part 4" },
+ { 416, "Vectoring line inventory and status data" },
+ { 417, "Data gathering line test, diagnostic and status" },
+ { 419, "EFM bonding group" },
+ { 420, "EFM bonding link" },
+ { 421, "EFM bonding group performance monitoring history data" },
+ { 422, "EFM bonding group performance monitoring history data part 2" },
+ { 423, "EFM bonding link performance monitoring history data" },
+ { 424, "EFM bonding port performance monitoring history data" },
+ { 425, "EFM bonding port performance monitoring history data part 2" },
+ { 426, "Ethernet frame extended PM 64 bit" },
+ { 427, "Physical path termination point xDSL UNI part 3" },
+ { 428, "FAST line configuration profile part 1" },
+ { 429, "FAST line configuration profile part 2" },
+ { 430, "FAST line configuration profile part 3" },
+ { 431, "FAST line configuration profile part 4" },
+ { 432, "FAST channel configuration profile" },
+ { 433, "FAST data path configuration profile" },
+ { 434, "FAST vectoring line configuration extensions" },
+ { 435, "FAST line inventory and status data" },
+ { 436, "FAST line inventory and status data part 2" },
+ { 437, "FAST xTU-C performance monitoring history data" },
+ { 438, "FAST xTU-R performance monitoring history data" },
+ { 439, "OpenFlow config data" },
+ { 440, "Time Status Message" },
+ { 441, "ONU3-G" },
+ { 442, "TWDM System Profile managed entity" },
+ { 443, "TWDM channel managed entity" },
+ { 444, "TWDM channel PHY/LODS performance monitoring history data" },
+ { 445, "TWDM channel XGEM performance monitoring history data" },
+ { 446, "TWDM channel PLOAM performance monitoring history data part 1" },
+ { 447, "TWDM channel PLOAM performance monitoring history data part 2" },
+ { 448, "TWDM channel PLOAM performance monitoring history data part 3" },
+ { 449, "TWDM channel tuning performance monitoring history data part 1" },
+ { 450, "TWDM channel tuning performance monitoring history data part 2" },
+ { 451, "TWDM channel tuning performance monitoring history data part 3" },
+ { 452, "TWDM channel OMCI performance monitoring history data" },
+};
+
+#define OMCI_ATTR(_bit, _offset, _len, _access) \
+ { .mask = BIT(_bit), .offset = (_offset), .len = (_len), \
+ .access = (_access) }
+
+#define OMCI_RW_SC (OMCI_ATTR_ACCESS_READ | OMCI_ATTR_ACCESS_WRITE | \
+ OMCI_ATTR_ACCESS_SET_CREATE)
+#define OMCI_RW (OMCI_ATTR_ACCESS_READ | OMCI_ATTR_ACCESS_WRITE)
+#define OMCI_R OMCI_ATTR_ACCESS_READ
+
+static const struct omci_attr_desc omci_software_image_attrs[] = {
+ OMCI_ATTR(15, 0, OMCI_SOFTWARE_VERSION_LEN, OMCI_R),
+ OMCI_ATTR(14, 14, 1, OMCI_R),
+ OMCI_ATTR(13, 15, 1, OMCI_R),
+ OMCI_ATTR(12, 16, 1, OMCI_R),
+};
+
+static const struct omci_attr_desc omci_cardholder_attrs[] = {
+ OMCI_ATTR(15, 0, 1, OMCI_R),
+ OMCI_ATTR(14, 1, 1, OMCI_RW),
+ OMCI_ATTR(13, 2, 1, OMCI_RW),
+ OMCI_ATTR(12, 3, 20, OMCI_RW),
+ OMCI_ATTR(11, 23, 20, OMCI_RW),
+ OMCI_ATTR(10, 43, 2, OMCI_R),
+};
+
+static const struct omci_attr_desc omci_circuit_pack_attrs[] = {
+ OMCI_ATTR(15, 0, 1, OMCI_R),
+ OMCI_ATTR(14, 1, 1, OMCI_R),
+ OMCI_ATTR(13, 2, 8, OMCI_R),
+ OMCI_ATTR(12, 10, 14, OMCI_R),
+ OMCI_ATTR(11, 24, 4, OMCI_R),
+ OMCI_ATTR(10, 28, 1, OMCI_RW),
+ OMCI_ATTR(9, 29, 1, OMCI_R),
+ OMCI_ATTR(8, 30, 1, OMCI_RW),
+ OMCI_ATTR(7, 31, 20, OMCI_R),
+ OMCI_ATTR(6, 51, 1, OMCI_RW),
+ OMCI_ATTR(5, 52, 1, OMCI_R),
+ OMCI_ATTR(4, 53, 1, OMCI_R),
+ OMCI_ATTR(3, 54, 1, OMCI_R),
+ OMCI_ATTR(2, 55, 4, OMCI_RW),
+};
+
+static const struct omci_attr_desc omci_pptp_ethernet_uni_attrs[] = {
+ OMCI_ATTR(15, 0, 1, OMCI_R),
+ OMCI_ATTR(14, 1, 1, OMCI_R),
+ OMCI_ATTR(13, 2, 1, OMCI_RW),
+ OMCI_ATTR(12, 3, 1, OMCI_RW),
+ OMCI_ATTR(11, 4, 1, OMCI_RW),
+ OMCI_ATTR(10, 5, 1, OMCI_R),
+ OMCI_ATTR(9, 6, 1, OMCI_R),
+ OMCI_ATTR(8, 7, 2, OMCI_RW),
+ OMCI_ATTR(7, 9, 1, OMCI_RW),
+ OMCI_ATTR(6, 10, 2, OMCI_RW),
+ OMCI_ATTR(5, 12, 1, OMCI_RW),
+ OMCI_ATTR(4, 13, 1, OMCI_RW),
+ OMCI_ATTR(3, 14, 1, OMCI_RW),
+ OMCI_ATTR(2, 15, 1, OMCI_RW),
+ OMCI_ATTR(1, 16, 1, OMCI_RW),
+};
+
+static const struct omci_attr_desc omci_bridge_port_attrs[] = {
+ OMCI_ATTR(15, 0, 2, OMCI_RW_SC),
+ OMCI_ATTR(14, 2, 1, OMCI_RW_SC),
+ OMCI_ATTR(13, 3, 1, OMCI_RW_SC),
+ OMCI_ATTR(12, 4, 2, OMCI_RW_SC),
+ OMCI_ATTR(11, 6, 2, OMCI_RW_SC),
+ OMCI_ATTR(10, 8, 2, OMCI_RW_SC),
+ OMCI_ATTR(9, 10, 1, OMCI_RW_SC),
+ OMCI_ATTR(8, 11, 1, OMCI_RW_SC),
+ OMCI_ATTR(7, 12, 1, OMCI_RW_SC),
+ OMCI_ATTR(6, 13, 6, OMCI_R),
+ OMCI_ATTR(5, 19, 2, OMCI_RW),
+ OMCI_ATTR(4, 21, 2, OMCI_RW),
+ OMCI_ATTR(3, 23, 1, OMCI_RW_SC),
+};
+
+static const struct omci_attr_desc omci_vlan_filter_attrs[] = {
+ OMCI_ATTR(15, 0, 24, OMCI_RW_SC),
+ OMCI_ATTR(14, 24, 1, OMCI_RW_SC),
+ OMCI_ATTR(13, 25, 1, OMCI_RW_SC),
+};
+
+static const struct omci_attr_desc omci_8021p_mapper_attrs[] = {
+ OMCI_ATTR(15, 0, 2, OMCI_RW_SC),
+ OMCI_ATTR(14, 2, 2, OMCI_RW_SC),
+ OMCI_ATTR(13, 4, 2, OMCI_RW_SC),
+ OMCI_ATTR(12, 6, 2, OMCI_RW_SC),
+ OMCI_ATTR(11, 8, 2, OMCI_RW_SC),
+ OMCI_ATTR(10, 10, 2, OMCI_RW_SC),
+ OMCI_ATTR(9, 12, 2, OMCI_RW_SC),
+ OMCI_ATTR(8, 14, 2, OMCI_RW_SC),
+ OMCI_ATTR(7, 16, 2, OMCI_RW_SC),
+ OMCI_ATTR(6, 18, 1, OMCI_RW_SC),
+ OMCI_ATTR(5, 19, 24, OMCI_RW),
+ OMCI_ATTR(4, 43, 1, OMCI_RW_SC),
+ OMCI_ATTR(3, 44, 1, OMCI_RW_SC),
+};
+
+static const struct omci_attr_desc omci_extended_vlan_attrs[] = {
+ OMCI_ATTR(15, 0, 1, OMCI_RW_SC),
+ OMCI_ATTR(14, 1, 2, OMCI_R),
+ OMCI_ATTR(13, 3, 2, OMCI_RW),
+ OMCI_ATTR(12, 5, 2, OMCI_RW),
+ OMCI_ATTR(11, 7, 1, OMCI_RW),
+ OMCI_ATTR(10, 8, 16, OMCI_RW),
+ OMCI_ATTR(9, 24, 2, OMCI_RW_SC),
+ OMCI_ATTR(8, 26, 24, OMCI_RW),
+};
+
+static const struct omci_attr_desc omci_tcont_attrs[] = {
+ OMCI_ATTR(15, 0, 2, OMCI_RW),
+ OMCI_ATTR(14, 2, 1, OMCI_R),
+ OMCI_ATTR(13, 3, 1, OMCI_RW),
+};
+
+static const struct omci_attr_desc omci_uni_g_attrs[] = {
+ OMCI_ATTR(15, 0, 2, OMCI_RW),
+ OMCI_ATTR(14, 2, 1, OMCI_RW),
+ OMCI_ATTR(13, 3, 1, OMCI_R),
+ OMCI_ATTR(12, 4, 2, OMCI_RW),
+};
+
+static const struct omci_attr_desc omci_gem_iwtp_attrs[] = {
+ OMCI_ATTR(15, 0, 2, OMCI_RW_SC),
+ OMCI_ATTR(14, 2, 1, OMCI_RW_SC),
+ OMCI_ATTR(13, 3, 2, OMCI_RW_SC),
+ OMCI_ATTR(12, 5, 2, OMCI_RW_SC),
+ OMCI_ATTR(11, 7, 1, OMCI_R),
+ OMCI_ATTR(10, 8, 1, OMCI_R),
+ OMCI_ATTR(9, 9, 2, OMCI_RW_SC),
+ OMCI_ATTR(8, 11, 1, OMCI_RW),
+};
+
+static const struct omci_attr_desc omci_gem_port_ctp_attrs[] = {
+ OMCI_ATTR(15, 0, 2, OMCI_RW_SC),
+ OMCI_ATTR(14, 2, 2, OMCI_RW_SC),
+ OMCI_ATTR(13, 4, 1, OMCI_RW_SC),
+ OMCI_ATTR(12, 5, 2, OMCI_RW_SC),
+ OMCI_ATTR(11, 7, 2, OMCI_RW_SC),
+ OMCI_ATTR(10, 9, 1, OMCI_R),
+ OMCI_ATTR(9, 10, 2, OMCI_RW_SC),
+ OMCI_ATTR(8, 12, 1, OMCI_R),
+ OMCI_ATTR(7, 13, 2, OMCI_RW_SC),
+ OMCI_ATTR(6, 15, 2, OMCI_RW),
+};
+
+static const struct omci_attr_desc omci_veip_attrs[] = {
+ OMCI_ATTR(15, 0, 1, OMCI_RW),
+ OMCI_ATTR(14, 1, 1, OMCI_R),
+ OMCI_ATTR(13, 2, 25, OMCI_RW),
+ OMCI_ATTR(12, 27, 2, OMCI_RW),
+ OMCI_ATTR(11, 29, 2, OMCI_R),
+};
+
+static const struct omci_attr_desc omci_huawei_350_attrs[] = {
+ OMCI_ATTR(15, 0, 1, OMCI_RW),
+ OMCI_ATTR(14, 1, 1, OMCI_R),
+ OMCI_ATTR(13, 2, 1, OMCI_RW),
+ OMCI_ATTR(12, 3, 1, OMCI_RW),
+ OMCI_ATTR(11, 4, 2, OMCI_R),
+ OMCI_ATTR(10, 6, 4, OMCI_RW),
+ OMCI_ATTR(9, 10, 16, OMCI_R),
+ OMCI_ATTR(8, 26, 4, OMCI_R),
+ OMCI_ATTR(7, 30, 6, OMCI_RW),
+};
+
+static const struct omci_attr_desc omci_huawei_352_attrs[] = {
+ OMCI_ATTR(15, 0, 2, OMCI_RW_SC),
+ OMCI_ATTR(14, 2, 2, OMCI_RW_SC),
+ OMCI_ATTR(13, 4, 2, OMCI_RW_SC),
+ OMCI_ATTR(12, 6, 1, OMCI_RW_SC),
+ OMCI_ATTR(11, 7, 4, OMCI_RW_SC),
+ OMCI_ATTR(10, 11, 4, OMCI_RW_SC),
+};
+
+static const struct omci_attr_desc omci_huawei_353_attrs[] = {
+ OMCI_ATTR(15, 0, 14, OMCI_R),
+ OMCI_ATTR(14, 14, 1, OMCI_R),
+ OMCI_ATTR(13, 15, 1, OMCI_R),
+ OMCI_ATTR(12, 16, 1, OMCI_R),
+ OMCI_ATTR(11, 17, 4, OMCI_RW),
+};
+
+static const struct omci_attr_desc omci_huawei_multicast_attrs[] = {
+ OMCI_ATTR(15, 0, 1, OMCI_RW),
+ OMCI_ATTR(14, 1, 2, OMCI_RW),
+ OMCI_ATTR(13, 3, 2, OMCI_RW),
+ OMCI_ATTR(12, 5, 2, OMCI_RW),
+ OMCI_ATTR(11, 7, 4, OMCI_RW),
+};
+
+static const struct omci_attr_desc omci_nokia_65295_attrs[] = {
+ OMCI_ATTR(15, 0, 1, OMCI_R),
+ OMCI_ATTR(14, 1, 2, OMCI_R),
+ OMCI_ATTR(13, 3, 1, OMCI_R),
+ OMCI_ATTR(12, 4, 2, OMCI_R),
+ OMCI_ATTR(11, 6, 1, OMCI_R),
+ OMCI_ATTR(10, 7, 2, OMCI_R),
+ OMCI_ATTR(9, 9, 1, OMCI_R),
+ OMCI_ATTR(8, 10, 2, OMCI_R),
+ OMCI_ATTR(7, 12, 1, OMCI_R),
+ OMCI_ATTR(6, 13, 2, OMCI_R),
+};
+
+static const struct omci_attr_desc omci_nokia_65296_attrs[] = {
+ OMCI_ATTR(15, 0, 1, OMCI_RW),
+ OMCI_ATTR(14, 1, 1, OMCI_RW),
+ OMCI_ATTR(13, 2, 4, OMCI_R),
+ OMCI_ATTR(12, 6, 1, OMCI_RW),
+ OMCI_ATTR(11, 7, 1, OMCI_RW),
+ OMCI_ATTR(10, 8, 1, OMCI_RW),
+ OMCI_ATTR(9, 9, 4, OMCI_R),
+ OMCI_ATTR(8, 13, 1, OMCI_R),
+ OMCI_ATTR(7, 14, 1, OMCI_RW),
+ OMCI_ATTR(6, 15, 12, OMCI_R),
+ OMCI_ATTR(5, 27, 4, OMCI_R),
+ OMCI_ATTR(4, 31, 25, OMCI_R),
+};
+
+static const struct omci_attr_desc omci_nokia_65297_attrs[] = {
+ OMCI_ATTR(15, 0, 1, OMCI_RW),
+ OMCI_ATTR(14, 1, 1, OMCI_RW),
+ OMCI_ATTR(13, 2, 2, OMCI_RW),
+ OMCI_ATTR(12, 4, 1, OMCI_RW),
+ OMCI_ATTR(11, 5, 1, OMCI_RW),
+ OMCI_ATTR(10, 6, 1, OMCI_RW),
+ OMCI_ATTR(9, 7, 1, OMCI_RW),
+};
+
+static const struct omci_attr_desc omci_nokia_65305_attrs[] = {
+ OMCI_ATTR(15, 0, 1, OMCI_RW),
+ OMCI_ATTR(14, 1, 6, OMCI_RW),
+};
+
+static const struct omci_attr_desc omci_nokia_65306_attrs[] = {
+ OMCI_ATTR(15, 0, 19, OMCI_RW),
+};
+
+static const struct omci_attr_desc omci_omci_attrs[] = {
+ OMCI_ATTR(15, 0, 4, OMCI_R),
+ OMCI_ATTR(14, 4, 4, OMCI_R),
+};
+
+static const struct omci_attr_desc omci_managed_entity_attrs[] = {
+ OMCI_ATTR(15, 0, 25, OMCI_R),
+ OMCI_ATTR(14, 25, 4, OMCI_R),
+ OMCI_ATTR(13, 29, 1, OMCI_R),
+ OMCI_ATTR(12, 30, 4, OMCI_R),
+ OMCI_ATTR(11, 34, 4, OMCI_R),
+ OMCI_ATTR(10, 38, 4, OMCI_R),
+ OMCI_ATTR(9, 42, 4, OMCI_R),
+ OMCI_ATTR(8, 46, 1, OMCI_R),
+};
+
+static const struct omci_attr_desc omci_attribute_attrs[] = {
+ OMCI_ATTR(15, 0, 25, OMCI_R),
+ OMCI_ATTR(14, 25, 2, OMCI_R),
+ OMCI_ATTR(13, 27, 1, OMCI_R),
+ OMCI_ATTR(12, 28, 1, OMCI_R),
+ OMCI_ATTR(11, 29, 4, OMCI_R),
+ OMCI_ATTR(10, 33, 4, OMCI_R),
+ OMCI_ATTR(9, 37, 4, OMCI_R),
+ OMCI_ATTR(8, 41, 4, OMCI_R),
+ OMCI_ATTR(7, 45, 1, OMCI_R),
+};
+
+#define STANDARD_ACTIONS (OMCI_ME_ACTION_GET | OMCI_ME_ACTION_SET)
+#define OLT_CREATED_ACTIONS (OMCI_ME_ACTION_CREATE | OMCI_ME_ACTION_DELETE | \
+ OMCI_ME_ACTION_GET | OMCI_ME_ACTION_SET)
+#define STANDARD_DESC(_class, _name, _actions, _flags, _mask, _upload, \
+ _len, _category, _support, _attrs) \
+ { .class_id = (_class), .name = (_name), \
+ .profile_mask = OMCI_PROFILE_STANDARD_MASK, .actions = (_actions), \
+ .flags = (_flags), .valid_attr_mask = (_mask), \
+ .mib_upload_mask = (_upload), .data_len = (_len), \
+ .category = (_category), .support = (_support), \
+ .attrs = (_attrs), .num_attrs = ARRAY_SIZE(_attrs) }
+#define VENDOR_DESC(_class, _name, _profile, _actions, _flags, _mask, \
+ _upload, _len, _category, _support, _attrs) \
+ { .class_id = (_class), .name = (_name), \
+ .profile_mask = OMCI_PROFILE_BIT(_profile), .actions = (_actions), \
+ .flags = (_flags) | OMCI_ME_F_VENDOR, .valid_attr_mask = (_mask), \
+ .mib_upload_mask = (_upload), .data_len = (_len), \
+ .category = (_category), .support = (_support), \
+ .attrs = (_attrs), .num_attrs = ARRAY_SIZE(_attrs) }
+
+static const struct omci_me_desc omci_me_descs[] = {
+ STANDARD_DESC(OMCI_CLASS_SOFTWARE_IMAGE, "Software image",
+ OMCI_ME_ACTION_GET, OMCI_ME_F_ONU_CREATED,
+ GENMASK(15, 12), GENMASK(15, 12), 17,
+ OMCI_CLASS_CATEGORY_EQUIPMENT, OMCI_CLASS_SUPPORT_NATIVE,
+ omci_software_image_attrs),
+ STANDARD_DESC(OMCI_CLASS_CARDHOLDER, "Cardholder", STANDARD_ACTIONS,
+ OMCI_ME_F_ONU_CREATED, GENMASK(15, 10), GENMASK(15, 10),
+ 45, OMCI_CLASS_CATEGORY_EQUIPMENT,
+ OMCI_CLASS_SUPPORT_NATIVE, omci_cardholder_attrs),
+ STANDARD_DESC(OMCI_CLASS_CIRCUIT_PACK, "Circuit pack", STANDARD_ACTIONS,
+ OMCI_ME_F_ONU_CREATED, GENMASK(15, 2), GENMASK(15, 2),
+ 59, OMCI_CLASS_CATEGORY_EQUIPMENT,
+ OMCI_CLASS_SUPPORT_NATIVE, omci_circuit_pack_attrs),
+ STANDARD_DESC(OMCI_CLASS_PPTP_ETHERNET_UNI,
+ "Physical path termination point Ethernet UNI",
+ STANDARD_ACTIONS, OMCI_ME_F_ONU_CREATED | OMCI_ME_F_DATAPATH,
+ GENMASK(15, 1), GENMASK(15, 1), 17,
+ OMCI_CLASS_CATEGORY_UNI, OMCI_CLASS_SUPPORT_PROVISIONED,
+ omci_pptp_ethernet_uni_attrs),
+ STANDARD_DESC(OMCI_CLASS_MAC_BRIDGE_PORT_CONFIG_DATA,
+ "MAC bridge port configuration data", OLT_CREATED_ACTIONS,
+ OMCI_ME_F_DATAPATH, GENMASK(15, 3), GENMASK(15, 3), 24,
+ OMCI_CLASS_CATEGORY_LAYER2, OMCI_CLASS_SUPPORT_PROVISIONED,
+ omci_bridge_port_attrs),
+ STANDARD_DESC(OMCI_CLASS_VLAN_TAGGING_FILTER_DATA,
+ "VLAN tagging filter data", OLT_CREATED_ACTIONS,
+ OMCI_ME_F_DATAPATH, GENMASK(15, 13), GENMASK(15, 13), 26,
+ OMCI_CLASS_CATEGORY_LAYER2, OMCI_CLASS_SUPPORT_PROVISIONED,
+ omci_vlan_filter_attrs),
+ STANDARD_DESC(OMCI_CLASS_8021P_MAPPER,
+ "802.1p mapper service profile", OLT_CREATED_ACTIONS,
+ OMCI_ME_F_DATAPATH, GENMASK(15, 3), GENMASK(15, 3), 45,
+ OMCI_CLASS_CATEGORY_LAYER2, OMCI_CLASS_SUPPORT_PROVISIONED,
+ omci_8021p_mapper_attrs),
+ STANDARD_DESC(OMCI_CLASS_EXTENDED_VLAN,
+ "Extended VLAN tagging operation configuration data",
+ OLT_CREATED_ACTIONS | OMCI_ME_ACTION_SET_TABLE,
+ OMCI_ME_F_DATAPATH, GENMASK(15, 8), GENMASK(15, 8), 50,
+ OMCI_CLASS_CATEGORY_LAYER2, OMCI_CLASS_SUPPORT_PROVISIONED,
+ omci_extended_vlan_attrs),
+ STANDARD_DESC(OMCI_CLASS_TCONT, "T-CONT", STANDARD_ACTIONS,
+ OMCI_ME_F_ONU_CREATED | OMCI_ME_F_DATAPATH,
+ GENMASK(15, 13), GENMASK(15, 13), 4,
+ OMCI_CLASS_CATEGORY_ANI, OMCI_CLASS_SUPPORT_NATIVE,
+ omci_tcont_attrs),
+ STANDARD_DESC(OMCI_CLASS_UNI_G, "UNI-G", STANDARD_ACTIONS,
+ OMCI_ME_F_ONU_CREATED, GENMASK(15, 12), GENMASK(15, 12),
+ 6, OMCI_CLASS_CATEGORY_UNI, OMCI_CLASS_SUPPORT_NATIVE,
+ omci_uni_g_attrs),
+ STANDARD_DESC(OMCI_CLASS_GEM_IWTP,
+ "GEM interworking termination point", OLT_CREATED_ACTIONS,
+ OMCI_ME_F_DATAPATH, GENMASK(15, 8), GENMASK(15, 8), 12,
+ OMCI_CLASS_CATEGORY_ANI, OMCI_CLASS_SUPPORT_PROVISIONED,
+ omci_gem_iwtp_attrs),
+ STANDARD_DESC(OMCI_CLASS_GEM_PORT_CTP, "GEM port network CTP",
+ OLT_CREATED_ACTIONS, OMCI_ME_F_DATAPATH,
+ GENMASK(15, 6), GENMASK(15, 6), 17,
+ OMCI_CLASS_CATEGORY_ANI, OMCI_CLASS_SUPPORT_PROVISIONED,
+ omci_gem_port_ctp_attrs),
+ STANDARD_DESC(OMCI_CLASS_OMCI, "OMCI",
+ OMCI_ME_ACTION_GET | OMCI_ME_ACTION_GET_NEXT,
+ OMCI_ME_F_ONU_CREATED | OMCI_ME_F_NO_MIB_UPLOAD,
+ GENMASK(15, 14), 0, 8,
+ OMCI_CLASS_CATEGORY_MANAGEMENT, OMCI_CLASS_SUPPORT_NATIVE,
+ omci_omci_attrs),
+ STANDARD_DESC(OMCI_CLASS_MANAGED_ENTITY, "Managed entity",
+ OMCI_ME_ACTION_GET | OMCI_ME_ACTION_GET_NEXT,
+ OMCI_ME_F_ONU_CREATED | OMCI_ME_F_NO_MIB_UPLOAD,
+ GENMASK(15, 8), 0, 47,
+ OMCI_CLASS_CATEGORY_MANAGEMENT, OMCI_CLASS_SUPPORT_NATIVE,
+ omci_managed_entity_attrs),
+ STANDARD_DESC(OMCI_CLASS_ATTRIBUTE, "Attribute",
+ OMCI_ME_ACTION_GET | OMCI_ME_ACTION_GET_NEXT,
+ OMCI_ME_F_ONU_CREATED | OMCI_ME_F_NO_MIB_UPLOAD,
+ GENMASK(15, 7), 0, 46,
+ OMCI_CLASS_CATEGORY_MANAGEMENT, OMCI_CLASS_SUPPORT_NATIVE,
+ omci_attribute_attrs),
+ STANDARD_DESC(OMCI_CLASS_VEIP, "Virtual Ethernet interface point",
+ STANDARD_ACTIONS, OMCI_ME_F_ONU_CREATED | OMCI_ME_F_DATAPATH,
+ GENMASK(15, 11), GENMASK(15, 11), 31,
+ OMCI_CLASS_CATEGORY_UNI, OMCI_CLASS_SUPPORT_PROVISIONED,
+ omci_veip_attrs),
+
+ VENDOR_DESC(OMCI_CLASS_HUAWEI_FLOW_MAPPING,
+ "Huawei flow mapping configuration", OMCI_OLT_PROFILE_HUAWEI,
+ STANDARD_ACTIONS, OMCI_ME_F_ONU_CREATED,
+ GENMASK(15, 7), GENMASK(15, 7), 36,
+ OMCI_CLASS_CATEGORY_VENDOR, OMCI_CLASS_SUPPORT_NATIVE,
+ omci_huawei_350_attrs),
+ VENDOR_DESC(OMCI_CLASS_HUAWEI_VLAN_MAPPING,
+ "Huawei VLAN mapping configuration", OMCI_OLT_PROFILE_HUAWEI,
+ OLT_CREATED_ACTIONS, OMCI_ME_F_NO_MIB_UPLOAD | OMCI_ME_F_DATAPATH,
+ GENMASK(15, 10), 0, 15, OMCI_CLASS_CATEGORY_VENDOR,
+ OMCI_CLASS_SUPPORT_SHADOW, omci_huawei_352_attrs),
+ VENDOR_DESC(OMCI_CLASS_HUAWEI_SW_IMAGE_EXT,
+ "Huawei software image extension", OMCI_OLT_PROFILE_HUAWEI,
+ STANDARD_ACTIONS, OMCI_ME_F_ONU_CREATED,
+ GENMASK(15, 11), GENMASK(15, 11), 21,
+ OMCI_CLASS_CATEGORY_VENDOR, OMCI_CLASS_SUPPORT_NATIVE,
+ omci_huawei_353_attrs),
+#define HUAWEI_MULTICAST_DESC(_class, _name) \
+ VENDOR_DESC((_class), (_name), OMCI_OLT_PROFILE_HUAWEI, STANDARD_ACTIONS, \
+ OMCI_ME_F_ONU_CREATED | OMCI_ME_F_DATAPATH, GENMASK(15, 11), \
+ GENMASK(15, 11), 11, \
+ OMCI_CLASS_CATEGORY_MULTICAST, OMCI_CLASS_SUPPORT_SHADOW, \
+ omci_huawei_multicast_attrs)
+ HUAWEI_MULTICAST_DESC(OMCI_CLASS_HUAWEI_MULTICAST_367,
+ "Huawei multicast configuration 367"),
+ HUAWEI_MULTICAST_DESC(OMCI_CLASS_HUAWEI_MULTICAST_370,
+ "Huawei multicast configuration 370"),
+ HUAWEI_MULTICAST_DESC(OMCI_CLASS_HUAWEI_MULTICAST_373,
+ "Huawei multicast configuration 373"),
+ HUAWEI_MULTICAST_DESC(OMCI_CLASS_HUAWEI_MULTICAST_65408,
+ "Huawei multicast configuration 65408"),
+ HUAWEI_MULTICAST_DESC(OMCI_CLASS_HUAWEI_MULTICAST_65414,
+ "Huawei multicast configuration 65414"),
+ HUAWEI_MULTICAST_DESC(OMCI_CLASS_HUAWEI_MULTICAST_65425,
+ "Huawei multicast configuration 65425"),
+#undef HUAWEI_MULTICAST_DESC
+
+ VENDOR_DESC(OMCI_CLASS_NOKIA_OPTICAL_SUPERVISION,
+ "Nokia ONT optical supervision status",
+ OMCI_OLT_PROFILE_NOKIA_ALCL, OMCI_ME_ACTION_GET,
+ OMCI_ME_F_ONU_CREATED, GENMASK(15, 6), GENMASK(15, 6), 15,
+ OMCI_CLASS_CATEGORY_VENDOR, OMCI_CLASS_SUPPORT_NATIVE,
+ omci_nokia_65295_attrs),
+ VENDOR_DESC(OMCI_CLASS_NOKIA_ONT_GENERIC_V2,
+ "Nokia ONT generic V2", OMCI_OLT_PROFILE_NOKIA_ALCL,
+ STANDARD_ACTIONS, OMCI_ME_F_ONU_CREATED,
+ GENMASK(15, 4), GENMASK(15, 4), 56,
+ OMCI_CLASS_CATEGORY_VENDOR, OMCI_CLASS_SUPPORT_NATIVE,
+ omci_nokia_65296_attrs),
+ VENDOR_DESC(OMCI_CLASS_NOKIA_UNI_SUPPLEMENTAL_V2,
+ "Nokia UNI supplemental 1 V2", OMCI_OLT_PROFILE_NOKIA_ALCL,
+ STANDARD_ACTIONS, OMCI_ME_F_ONU_CREATED,
+ GENMASK(15, 9), GENMASK(15, 9), 8,
+ OMCI_CLASS_CATEGORY_VENDOR, OMCI_CLASS_SUPPORT_SHADOW,
+ omci_nokia_65297_attrs),
+ VENDOR_DESC(OMCI_CLASS_NOKIA_VLAN_DS_SUPPLEMENTAL,
+ "Nokia VLAN downstream supplemental",
+ OMCI_OLT_PROFILE_NOKIA_ALCL, OLT_CREATED_ACTIONS,
+ OMCI_ME_F_DATAPATH, GENMASK(15, 14), GENMASK(15, 14), 7,
+ OMCI_CLASS_CATEGORY_VENDOR, OMCI_CLASS_SUPPORT_SHADOW,
+ omci_nokia_65305_attrs),
+ VENDOR_DESC(OMCI_CLASS_NOKIA_VLAN_US_POLICER,
+ "Nokia upstream VLAN policer", OMCI_OLT_PROFILE_NOKIA_ALCL,
+ OLT_CREATED_ACTIONS, OMCI_ME_F_DATAPATH,
+ BIT(15), BIT(15), 19, OMCI_CLASS_CATEGORY_VENDOR,
+ OMCI_CLASS_SUPPORT_SHADOW, omci_nokia_65306_attrs),
+};
+
+static bool omci_class_name_contains(const char *name, const char *needle)
+{
+ return strnstr(name, needle, strlen(name));
+}
+
+static u8 omci_class_category(u16 class_id, const char *name)
+{
+ if ((class_id >= 240 && class_id <= 255) ||
+ (class_id >= 350 && class_id <= 399) || class_id >= 65280)
+ return OMCI_CLASS_CATEGORY_VENDOR;
+ if ((class_id >= 172 && class_id <= 239) ||
+ (class_id >= 453 && class_id <= 65279) ||
+ omci_class_name_contains(name, "Reserved") ||
+ omci_class_name_contains(name, "Intentionally left blank"))
+ return OMCI_CLASS_CATEGORY_RESERVED;
+ if (omci_class_name_contains(name, "performance monitoring") ||
+ omci_class_name_contains(name, "PM history") ||
+ omci_class_name_contains(name, "monitoring history"))
+ return OMCI_CLASS_CATEGORY_PERFORMANCE;
+ if (omci_class_name_contains(name, "xDSL") ||
+ omci_class_name_contains(name, "VDSL") ||
+ omci_class_name_contains(name, "FAST") ||
+ omci_class_name_contains(name, "EFM bonding"))
+ return OMCI_CLASS_CATEGORY_XDSL;
+ if (omci_class_name_contains(name, "VoIP") ||
+ omci_class_name_contains(name, "SIP") ||
+ omci_class_name_contains(name, "MGC") ||
+ omci_class_name_contains(name, "RTP") ||
+ omci_class_name_contains(name, "Voice") ||
+ omci_class_name_contains(name, "POTS"))
+ return OMCI_CLASS_CATEGORY_VOICE;
+ if (omci_class_name_contains(name, "Multicast"))
+ return OMCI_CLASS_CATEGORY_MULTICAST;
+ if (omci_class_name_contains(name, "security") ||
+ omci_class_name_contains(name, "Authentication") ||
+ omci_class_name_contains(name, "Dot1X") ||
+ omci_class_name_contains(name, "Radius"))
+ return OMCI_CLASS_CATEGORY_SECURITY;
+ if (omci_class_name_contains(name, "IP ") ||
+ omci_class_name_contains(name, "TCP/UDP") ||
+ omci_class_name_contains(name, "ARP") ||
+ omci_class_name_contains(name, "ICMP") ||
+ omci_class_name_contains(name, "TR-069") ||
+ omci_class_name_contains(name, "SNMP") ||
+ omci_class_name_contains(name, "Network address"))
+ return OMCI_CLASS_CATEGORY_LAYER3;
+ if (omci_class_name_contains(name, "VLAN") ||
+ omci_class_name_contains(name, "MAC bridge") ||
+ omci_class_name_contains(name, "Ethernet") ||
+ omci_class_name_contains(name, "802.1p") ||
+ omci_class_name_contains(name, "Dot1ag") ||
+ omci_class_name_contains(name, "OpenFlow"))
+ return OMCI_CLASS_CATEGORY_LAYER2;
+ if (omci_class_name_contains(name, "UNI"))
+ return OMCI_CLASS_CATEGORY_UNI;
+ if (omci_class_name_contains(name, "ANI") ||
+ omci_class_name_contains(name, "GEM") ||
+ omci_class_name_contains(name, "T-CONT") ||
+ omci_class_name_contains(name, "PON") ||
+ omci_class_name_contains(name, "Traffic scheduler") ||
+ omci_class_name_contains(name, "Priority queue"))
+ return OMCI_CLASS_CATEGORY_ANI;
+ if (class_id <= 7 || class_id == 133 || class_id == 159 ||
+ class_id == 160 || class_id == 256 || class_id == 257 ||
+ class_id == 297 || class_id == 331 || class_id == 336 ||
+ class_id == 441)
+ return OMCI_CLASS_CATEGORY_EQUIPMENT;
+ if (class_id == 137 || class_id == 157 || class_id == 158 ||
+ class_id == 287 || class_id == 288 || class_id == 289 ||
+ class_id == 307 || class_id == 308 || class_id == 318 ||
+ class_id == 330 || class_id == 440)
+ return OMCI_CLASS_CATEGORY_MANAGEMENT;
+ if (class_id < 256)
+ return OMCI_CLASS_CATEGORY_LEGACY;
+
+ return OMCI_CLASS_CATEGORY_OTHER;
+}
+
+static u32 omci_class_flags(u16 class_id, const char *name)
+{
+ u32 flags = OMCI_CLASS_F_STANDARD;
+ u8 category = omci_class_category(class_id, name);
+
+ if (omci_class_name_contains(name, "deprecated") ||
+ omci_class_name_contains(name, "obsolete"))
+ flags |= OMCI_CLASS_F_DEPRECATED;
+ if (category == OMCI_CLASS_CATEGORY_RESERVED)
+ flags |= OMCI_CLASS_F_RESERVED;
+ if (category == OMCI_CLASS_CATEGORY_VENDOR)
+ flags |= OMCI_CLASS_F_VENDOR_SPECIFIC;
+ if (category == OMCI_CLASS_CATEGORY_PERFORMANCE)
+ flags |= OMCI_CLASS_F_PERFORMANCE;
+ if (omci_class_name_contains(name, "table") || class_id == 84 ||
+ class_id == 145 || class_id == 171)
+ flags |= OMCI_CLASS_F_TABLE;
+
+ return flags;
+}
+
+const struct omci_me_desc *omci_me_lookup_profile(u8 profile, u16 class_id)
+{
+ unsigned int i;
+
+ if (profile == OMCI_OLT_PROFILE_UNSPEC ||
+ profile == OMCI_OLT_PROFILE_AUTO)
+ profile = OMCI_OLT_PROFILE_GENERIC;
+
+ for (i = 0; i < ARRAY_SIZE(omci_me_descs); i++) {
+ const struct omci_me_desc *desc = &omci_me_descs[i];
+
+ if (desc->class_id == class_id &&
+ (desc->profile_mask & OMCI_PROFILE_BIT(profile)))
+ return desc;
+ }
+
+ return NULL;
+}
+
+const struct omci_me_desc *omci_me_lookup(const struct omci_agent *agent,
+ u16 class_id)
+{
+ return omci_me_lookup_profile(agent->profile_effective, class_id);
+}
+
+bool omci_me_active(const struct omci_agent *agent, u16 class_id)
+{
+ const struct omci_me_desc *desc;
+
+ desc = omci_me_lookup(agent, class_id);
+ if (desc)
+ return true;
+
+ /* Standard catalogue entries remain available as shadow objects. */
+ return class_id < 240 || (class_id >= 256 && class_id < 350) ||
+ (class_id >= 400 && class_id <= 452);
+}
+
+bool omci_me_action_allowed(const struct omci_agent *agent, u16 class_id,
+ u8 message_type)
+{
+ const struct omci_me_desc *desc = omci_me_lookup(agent, class_id);
+
+ if (!desc)
+ return omci_me_active(agent, class_id);
+ if (message_type >= 32)
+ return false;
+
+ return !!(desc->actions & BIT(message_type));
+}
+
+static const struct omci_attr_desc *
+omci_me_find_attr(const struct omci_me_desc *desc, u16 mask)
+{
+ unsigned int i;
+
+ for (i = 0; i < desc->num_attrs; i++)
+ if (desc->attrs[i].mask == mask)
+ return &desc->attrs[i];
+
+ return NULL;
+}
+
+int omci_me_decode_create(const struct omci_me_desc *desc,
+ struct omci_mib_object *object,
+ const u8 *data, size_t len)
+{
+ unsigned int i;
+ u16 mask = 0;
+
+ if (!desc)
+ return 0;
+ for (i = 0; i < desc->num_attrs; i++) {
+ const struct omci_attr_desc *attr = &desc->attrs[i];
+
+ if (!(attr->access & OMCI_ATTR_ACCESS_SET_CREATE))
+ continue;
+ if (attr->offset + attr->len > sizeof(object->data) ||
+ len < attr->len)
+ return -EINVAL;
+ memcpy(object->data + attr->offset, data, attr->len);
+ data += attr->len;
+ len -= attr->len;
+ mask |= attr->mask;
+ }
+ object->attr_mask |= mask;
+
+ return 0;
+}
+
+int omci_me_decode_set(const struct omci_me_desc *desc,
+ struct omci_mib_object *object, u16 mask,
+ const u8 *data, size_t len)
+{
+ unsigned int bit;
+
+ if (!desc)
+ return 0;
+ if (mask & ~desc->valid_attr_mask)
+ return -EINVAL;
+
+ for (bit = 0; bit < 16; bit++) {
+ const struct omci_attr_desc *attr;
+ u16 attr_mask = BIT(15 - bit);
+
+ if (!(mask & attr_mask))
+ continue;
+ attr = omci_me_find_attr(desc, attr_mask);
+ if (!attr || !(attr->access & OMCI_ATTR_ACCESS_WRITE))
+ return -EACCES;
+ if (attr->offset + attr->len > sizeof(object->data) ||
+ len < attr->len)
+ return -EINVAL;
+ memcpy(object->data + attr->offset, data, attr->len);
+ data += attr->len;
+ len -= attr->len;
+ }
+ object->attr_mask |= mask;
+
+ return 0;
+}
+
+int omci_me_encode_attributes(const struct omci_me_desc *desc,
+ const struct omci_mib_object *object,
+ u16 mask, u8 *data, size_t len,
+ u16 *encoded_mask, size_t *encoded_len)
+{
+ size_t used = 0;
+ unsigned int bit;
+ u16 encoded = 0;
+
+ if (!desc)
+ return -ENOENT;
+ mask &= desc->valid_attr_mask;
+ for (bit = 0; bit < 16; bit++) {
+ const struct omci_attr_desc *attr;
+ u16 attr_mask = BIT(15 - bit);
+
+ if (!(mask & attr_mask))
+ continue;
+ attr = omci_me_find_attr(desc, attr_mask);
+ if (!attr || !(attr->access & OMCI_ATTR_ACCESS_READ))
+ continue;
+ if (attr->offset + attr->len > sizeof(object->data))
+ return -EINVAL;
+ if (len - used < attr->len)
+ break;
+ memcpy(data + used, object->data + attr->offset, attr->len);
+ used += attr->len;
+ encoded |= attr_mask;
+ }
+ if (encoded_mask)
+ *encoded_mask = encoded;
+ if (encoded_len)
+ *encoded_len = used;
+
+ return encoded ? 0 : -ENOSPC;
+}
+
+unsigned int omci_me_attr_count(const struct omci_agent *agent)
+{
+ unsigned int count = 0;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(omci_me_descs); i++) {
+ const struct omci_me_desc *desc = &omci_me_descs[i];
+
+ if (omci_me_lookup(agent, desc->class_id) != desc)
+ continue;
+ count += desc->num_attrs;
+ }
+
+ return count;
+}
+
+int omci_me_attr_id(const struct omci_agent *agent, u16 class_id,
+ unsigned int attr_index, u16 *attr_id)
+{
+ unsigned int next_id = 1;
+ unsigned int i;
+
+ if (!agent || !attr_id)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(omci_me_descs); i++) {
+ const struct omci_me_desc *desc = &omci_me_descs[i];
+
+ if (omci_me_lookup(agent, desc->class_id) != desc)
+ continue;
+ if (desc->class_id == class_id) {
+ if (attr_index >= desc->num_attrs)
+ return -ENOENT;
+ if (next_id + attr_index > U16_MAX)
+ return -EOVERFLOW;
+ *attr_id = next_id + attr_index;
+ return 0;
+ }
+ if (next_id + desc->num_attrs > U16_MAX)
+ return -EOVERFLOW;
+ next_id += desc->num_attrs;
+ }
+
+ return -ENOENT;
+}
+
+int omci_me_attr_get(const struct omci_agent *agent, u16 attr_id,
+ u16 *class_id, unsigned int *attr_index,
+ const struct omci_me_desc **me_desc,
+ const struct omci_attr_desc **me_attr)
+{
+ unsigned int next_id = 1;
+ unsigned int i;
+
+ if (!agent || !attr_id)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(omci_me_descs); i++) {
+ const struct omci_me_desc *desc = &omci_me_descs[i];
+ unsigned int index;
+
+ if (omci_me_lookup(agent, desc->class_id) != desc)
+ continue;
+ if (attr_id < next_id || attr_id >= next_id + desc->num_attrs) {
+ next_id += desc->num_attrs;
+ continue;
+ }
+
+ index = attr_id - next_id;
+ if (class_id)
+ *class_id = desc->class_id;
+ if (attr_index)
+ *attr_index = index;
+ if (me_desc)
+ *me_desc = desc;
+ if (me_attr)
+ *me_attr = &desc->attrs[index];
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+const char *omci_me_class_name(u16 class_id)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(omci_me_descs); i++)
+ if (omci_me_descs[i].class_id == class_id)
+ return omci_me_descs[i].name;
+ for (i = 0; i < ARRAY_SIZE(omci_classes); i++)
+ if (omci_classes[i].class_id == class_id)
+ return omci_classes[i].name;
+
+ if (class_id >= 172 && class_id <= 239)
+ return "Reserved for future managed entities";
+ if (class_id >= 240 && class_id <= 255)
+ return "Reserved for vendor-specific managed entities";
+ if (class_id >= 350 && class_id <= 399)
+ return "Vendor-specific managed entity";
+ if (class_id >= 453 && class_id <= 65279)
+ return "Reserved for future standardization";
+ if (class_id >= 65280)
+ return "Vendor-specific managed entity";
+
+ return "Unassigned managed entity";
+}
+
+int omci_me_class_get(struct omci_device *odev, u16 class_id,
+ struct omci_me_class *class)
+{
+ const struct omci_me_desc *desc;
+ const char *name;
+
+ if (!odev || !class)
+ return -EINVAL;
+ if (!omci_me_active(&odev->agent, class_id))
+ return -ENOENT;
+
+ desc = omci_me_lookup(&odev->agent, class_id);
+ name = omci_me_class_name(class_id);
+ class->class_id = class_id;
+ class->category = desc ? desc->category :
+ omci_class_category(class_id, name);
+ class->support = desc ? desc->support : OMCI_CLASS_SUPPORT_SHADOW;
+ class->flags = omci_class_flags(class_id, name);
+ if (desc) {
+ if (desc->flags & OMCI_ME_F_VENDOR)
+ class->flags |= OMCI_CLASS_F_VENDOR_SPECIFIC;
+ if (desc->flags & OMCI_ME_F_DATAPATH)
+ class->flags |= OMCI_CLASS_F_DATAPATH;
+ }
+ class->name = name;
+
+ return 0;
+}
+
+int omci_me_class_next(struct omci_device *odev, u32 index,
+ struct omci_me_class *class, u32 *next_index)
+{
+ u32 position = 0;
+ unsigned int i;
+
+ if (!odev || !class || !next_index)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(omci_classes); i++) {
+ if (!omci_me_active(&odev->agent, omci_classes[i].class_id))
+ continue;
+ if (position++ < index)
+ continue;
+ omci_me_class_get(odev, omci_classes[i].class_id, class);
+ *next_index = index + 1;
+ return 0;
+ }
+ for (i = 0; i < ARRAY_SIZE(omci_me_descs); i++) {
+ u16 class_id = omci_me_descs[i].class_id;
+ unsigned int j;
+ bool catalogued = false;
+
+ for (j = 0; j < ARRAY_SIZE(omci_classes); j++)
+ if (omci_classes[j].class_id == class_id) {
+ catalogued = true;
+ break;
+ }
+ if (catalogued || !omci_me_lookup(&odev->agent, class_id))
+ continue;
+ if (position++ < index)
+ continue;
+ omci_me_class_get(odev, class_id, class);
+ *next_index = index + 1;
+ return 0;
+ }
+
+ return -ENOENT;
+}
diff --git a/net/xpon/omci/me.h b/net/xpon/omci/me.h
new file mode 100644
index 000000000000..7953efcb9eef
--- /dev/null
+++ b/net/xpon/omci/me.h
@@ -0,0 +1,161 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _NET_OMCI_ME_H
+#define _NET_OMCI_ME_H
+
+#include <linux/bits.h>
+#include <linux/types.h>
+
+struct omci_agent;
+struct omci_device;
+struct omci_me_class;
+struct omci_mib_object;
+
+#define OMCI_CLASS_ONU_DATA 2
+#define OMCI_CLASS_CARDHOLDER 5
+#define OMCI_CLASS_CIRCUIT_PACK 6
+#define OMCI_CLASS_SOFTWARE_IMAGE 7
+#define OMCI_CLASS_PPTP_ETHERNET_UNI 11
+#define OMCI_CLASS_MAC_BRIDGE_SERVICE_PROFILE 45
+#define OMCI_CLASS_MAC_BRIDGE_CONFIG_DATA 46
+#define OMCI_CLASS_MAC_BRIDGE_PORT_CONFIG_DATA 47
+#define OMCI_CLASS_VLAN_TAGGING_FILTER_DATA 84
+#define OMCI_CLASS_8021P_MAPPER 130
+
+/*
+ * 802.1p mapper instances seeded for the Nokia/ALCL profile. One per GEM
+ * interworking termination point: mapper 1 fronts GEM 256 (management and
+ * VoIP), mapper 2 fronts GEM 257 (high speed internet).
+ */
+#define OMCI_NOKIA_MAPPER_MGMT 1
+#define OMCI_NOKIA_MAPPER_HSI 2
+#define OMCI_CLASS_OLT_G 131
+#define OMCI_CLASS_NETWORK_ADDRESS 137
+#define OMCI_CLASS_AUTH_METHOD 148
+#define OMCI_CLASS_EXTENDED_VLAN 171
+#define OMCI_CLASS_ONU_G 256
+#define OMCI_CLASS_ONU2_G 257
+
+/*
+ * ONU2-G security capability and security mode, G.988 9.1.3: 1 selects
+ * AES-128, the only cipher GPON defines.
+ */
+#define OMCI_ONU2G_SECURITY_AES128 1
+
+/*
+ * ONU2-G connectivity capability, G.988 Table 9.1.3-1. One bit per forwarding
+ * model: N:1 bridging, 1:M mapping, 1:P filtering, N:M bridge-mapping,
+ * 1:MP map-filtering, N:P bridge-filtering and N:MP bridge-map-filtering.
+ * The agent builds its datapath out of MAC bridges, GEM interworking TPs and
+ * a VEIP, so advertise the full set the way a stock ONU does and let the OLT
+ * pick.
+ */
+#define OMCI_ONU2G_CONNECTIVITY_CAPABILITY 0x007f
+#define OMCI_CLASS_TCONT 262
+#define OMCI_CLASS_ANI_G 263
+#define OMCI_CLASS_UNI_G 264
+#define OMCI_CLASS_GEM_IWTP 266
+#define OMCI_CLASS_GEM_PORT_CTP 268
+#define OMCI_CLASS_GAL_ETHERNET_PROFILE 272
+#define OMCI_CLASS_PRIORITY_QUEUE 277
+#define OMCI_CLASS_TRAFFIC_SCHEDULER 278
+#define OMCI_CLASS_OMCI 287
+#define OMCI_CLASS_MANAGED_ENTITY 288
+#define OMCI_CLASS_ATTRIBUTE 289
+#define OMCI_CLASS_VEIP 329
+
+#define OMCI_CLASS_HUAWEI_FLOW_MAPPING 350
+#define OMCI_CLASS_HUAWEI_VLAN_MAPPING 352
+#define OMCI_CLASS_HUAWEI_SW_IMAGE_EXT 353
+#define OMCI_CLASS_HUAWEI_MULTICAST_367 367
+#define OMCI_CLASS_HUAWEI_MULTICAST_370 370
+#define OMCI_CLASS_HUAWEI_MULTICAST_373 373
+#define OMCI_CLASS_HUAWEI_MULTICAST_65408 65408
+#define OMCI_CLASS_HUAWEI_MULTICAST_65414 65414
+#define OMCI_CLASS_HUAWEI_MULTICAST_65425 65425
+
+#define OMCI_CLASS_NOKIA_OPTICAL_SUPERVISION 65295
+#define OMCI_CLASS_NOKIA_ONT_GENERIC_V2 65296
+#define OMCI_CLASS_NOKIA_UNI_SUPPLEMENTAL_V2 65297
+#define OMCI_CLASS_NOKIA_GEM_PM_PART2 65300
+#define OMCI_CLASS_NOKIA_VLAN_DS_SUPPLEMENTAL 65305
+#define OMCI_CLASS_NOKIA_VLAN_US_POLICER 65306
+
+#define OMCI_ME_ACTION_CREATE BIT(4)
+#define OMCI_ME_ACTION_DELETE BIT(6)
+#define OMCI_ME_ACTION_SET BIT(8)
+#define OMCI_ME_ACTION_GET BIT(9)
+#define OMCI_ME_ACTION_GET_NEXT BIT(26)
+#define OMCI_ME_ACTION_MIB_UPLOAD BIT(13)
+#define OMCI_ME_ACTION_MIB_UPLOAD_NEXT BIT(14)
+#define OMCI_ME_ACTION_MIB_RESET BIT(15)
+#define OMCI_ME_ACTION_SET_TABLE BIT(29)
+
+#define OMCI_ME_F_NO_MIB_UPLOAD BIT(0)
+#define OMCI_ME_F_ONU_CREATED BIT(1)
+#define OMCI_ME_F_DATAPATH BIT(2)
+#define OMCI_ME_F_SAFE_FAKE BIT(3)
+#define OMCI_ME_F_VENDOR BIT(4)
+
+#define OMCI_PROFILE_BIT(_profile) BIT(_profile)
+#define OMCI_PROFILE_STANDARD_MASK GENMASK(7, 1)
+
+enum omci_attr_access {
+ OMCI_ATTR_ACCESS_READ = BIT(0),
+ OMCI_ATTR_ACCESS_WRITE = BIT(1),
+ OMCI_ATTR_ACCESS_SET_CREATE = BIT(2),
+};
+
+struct omci_attr_desc {
+ u16 mask;
+ u8 offset;
+ u8 len;
+ u8 access;
+};
+
+struct omci_me_desc {
+ u16 class_id;
+ const char *name;
+ u32 profile_mask;
+ u32 actions;
+ u32 flags;
+ u16 valid_attr_mask;
+ u16 mib_upload_mask;
+ u8 data_len;
+ u8 category;
+ u8 support;
+ const struct omci_attr_desc *attrs;
+ unsigned int num_attrs;
+};
+
+const struct omci_me_desc *omci_me_lookup(const struct omci_agent *agent,
+ u16 class_id);
+const struct omci_me_desc *omci_me_lookup_profile(u8 profile, u16 class_id);
+bool omci_me_active(const struct omci_agent *agent, u16 class_id);
+bool omci_me_action_allowed(const struct omci_agent *agent, u16 class_id,
+ u8 message_type);
+
+int omci_me_decode_create(const struct omci_me_desc *desc,
+ struct omci_mib_object *object,
+ const u8 *data, size_t len);
+int omci_me_decode_set(const struct omci_me_desc *desc,
+ struct omci_mib_object *object, u16 mask,
+ const u8 *data, size_t len);
+int omci_me_encode_attributes(const struct omci_me_desc *desc,
+ const struct omci_mib_object *object,
+ u16 mask, u8 *data, size_t len,
+ u16 *encoded_mask, size_t *encoded_len);
+
+const char *omci_me_class_name(u16 class_id);
+int omci_me_class_get(struct omci_device *odev, u16 class_id,
+ struct omci_me_class *class);
+int omci_me_class_next(struct omci_device *odev, u32 index,
+ struct omci_me_class *class, u32 *next_index);
+int omci_me_attr_id(const struct omci_agent *agent, u16 class_id,
+ unsigned int attr_index, u16 *attr_id);
+int omci_me_attr_get(const struct omci_agent *agent, u16 attr_id,
+ u16 *class_id, unsigned int *attr_index,
+ const struct omci_me_desc **desc,
+ const struct omci_attr_desc **attr);
+unsigned int omci_me_attr_count(const struct omci_agent *agent);
+
+#endif /* _NET_OMCI_ME_H */
diff --git a/net/xpon/omci/profiles.c b/net/xpon/omci/profiles.c
new file mode 100644
index 000000000000..9a3dc8c49b17
--- /dev/null
+++ b/net/xpon/omci/profiles.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * OMCI OLT interoperability profiles
+ *
+ * Profiles describe policy deviations required by selected OLT families.
+ * Automatic detection is the default and uses OLT-G identification attributes
+ * only as a hint. Until OLT-G is available, the effective profile is generic.
+ */
+
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+
+#include "internal.h"
+
+struct omci_profile_desc {
+ u8 profile;
+ u32 quirks;
+ const char *name;
+};
+
+static const struct omci_profile_desc omci_profiles[] = {
+ {
+ .profile = OMCI_OLT_PROFILE_GENERIC,
+ .name = "generic",
+ }, {
+ .profile = OMCI_OLT_PROFILE_AUTO,
+ .name = "auto",
+ }, {
+ .profile = OMCI_OLT_PROFILE_NOKIA_ALCL,
+ .quirks = OMCI_OLT_QUIRK_SANITIZE_VERSION |
+ OMCI_OLT_QUIRK_VENDOR_SPECIFIC_MES,
+ .name = "nokia-alcl",
+ }, {
+ .profile = OMCI_OLT_PROFILE_DASAN,
+ .quirks = OMCI_OLT_QUIRK_ALLOW_SET_CREATE |
+ OMCI_OLT_QUIRK_FAKE_UNSUPPORTED_SUCCESS |
+ OMCI_OLT_QUIRK_IGNORE_UNSUPPORTED_UNI |
+ OMCI_OLT_QUIRK_DASAN_MULTICAST_ANI,
+ .name = "dasan",
+ }, {
+ .profile = OMCI_OLT_PROFILE_HUAWEI,
+ .quirks = OMCI_OLT_QUIRK_VENDOR_SPECIFIC_MES,
+ .name = "huawei",
+ }, {
+ .profile = OMCI_OLT_PROFILE_FIBERHOME,
+ .quirks = OMCI_OLT_QUIRK_ALLOW_SET_CREATE |
+ OMCI_OLT_QUIRK_FULL_UNI_ENTITY_ID,
+ .name = "fiberhome",
+ }, {
+ .profile = OMCI_OLT_PROFILE_ZTE,
+ .quirks = OMCI_OLT_QUIRK_ALLOW_SET_CREATE |
+ OMCI_OLT_QUIRK_ZTE_VLAN_TAG_MODE,
+ .name = "zte",
+ },
+};
+
+static const struct omci_profile_desc *omci_profile_find(u8 profile)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(omci_profiles); i++)
+ if (omci_profiles[i].profile == profile)
+ return &omci_profiles[i];
+
+ return NULL;
+}
+
+bool omci_profile_valid(u8 profile)
+{
+ return !!omci_profile_find(profile);
+}
+
+bool omci_profile_forceable(u8 profile)
+{
+ return profile == OMCI_OLT_PROFILE_UNSPEC ||
+ (profile != OMCI_OLT_PROFILE_AUTO && omci_profile_valid(profile));
+}
+
+u32 omci_profile_quirks(u8 profile)
+{
+ const struct omci_profile_desc *desc = omci_profile_find(profile);
+
+ return desc ? desc->quirks : 0;
+}
+
+const char *omci_olt_profile_name(u8 profile)
+{
+ const struct omci_profile_desc *desc = omci_profile_find(profile);
+
+ return desc ? desc->name : "unspecified";
+}
+EXPORT_SYMBOL_GPL(omci_olt_profile_name);
+
+static bool omci_profile_vendor_is(const struct omci_olt_g *olt,
+ const char vendor[OMCI_OLT_VENDOR_ID_LEN])
+{
+ return olt && olt->vendor_id_valid &&
+ !strncasecmp(olt->vendor_id, vendor, OMCI_OLT_VENDOR_ID_LEN);
+}
+
+static bool omci_profile_field_contains(const char *field, size_t len,
+ const char *token)
+{
+ size_t token_len = strlen(token);
+ size_t i;
+
+ if (!token_len || token_len > len)
+ return false;
+
+ for (i = 0; i + token_len <= len && field[i]; i++)
+ if (!strncasecmp(field + i, token, token_len))
+ return true;
+
+ return false;
+}
+
+u8 omci_profile_detect(const struct omci_olt_g *olt)
+{
+ if (!olt || !olt->valid)
+ return OMCI_OLT_PROFILE_GENERIC;
+
+ if (omci_profile_vendor_is(olt, "ALCL") ||
+ omci_profile_vendor_is(olt, "NOKI"))
+ return OMCI_OLT_PROFILE_NOKIA_ALCL;
+ if (omci_profile_vendor_is(olt, "DSNW") ||
+ omci_profile_vendor_is(olt, "DASA"))
+ return OMCI_OLT_PROFILE_DASAN;
+ if (omci_profile_vendor_is(olt, "HWTC") ||
+ omci_profile_vendor_is(olt, "HWTI"))
+ return OMCI_OLT_PROFILE_HUAWEI;
+ if (omci_profile_vendor_is(olt, "FHTT") ||
+ omci_profile_vendor_is(olt, "FHTC"))
+ return OMCI_OLT_PROFILE_FIBERHOME;
+ if (omci_profile_vendor_is(olt, "ZTEG") ||
+ omci_profile_vendor_is(olt, "ZTEC"))
+ return OMCI_OLT_PROFILE_ZTE;
+
+ if (olt->equipment_id_valid) {
+ if (omci_profile_field_contains(olt->equipment_id,
+ OMCI_OLT_EQUIPMENT_ID_LEN,
+ "NOKIA") ||
+ omci_profile_field_contains(olt->equipment_id,
+ OMCI_OLT_EQUIPMENT_ID_LEN,
+ "ALCATEL"))
+ return OMCI_OLT_PROFILE_NOKIA_ALCL;
+ if (omci_profile_field_contains(olt->equipment_id,
+ OMCI_OLT_EQUIPMENT_ID_LEN,
+ "DASAN"))
+ return OMCI_OLT_PROFILE_DASAN;
+ if (omci_profile_field_contains(olt->equipment_id,
+ OMCI_OLT_EQUIPMENT_ID_LEN,
+ "HUAWEI"))
+ return OMCI_OLT_PROFILE_HUAWEI;
+ if (omci_profile_field_contains(olt->equipment_id,
+ OMCI_OLT_EQUIPMENT_ID_LEN,
+ "FIBERHOME"))
+ return OMCI_OLT_PROFILE_FIBERHOME;
+ if (omci_profile_field_contains(olt->equipment_id,
+ OMCI_OLT_EQUIPMENT_ID_LEN,
+ "ZTE"))
+ return OMCI_OLT_PROFILE_ZTE;
+ }
+
+ return OMCI_OLT_PROFILE_GENERIC;
+}
+
+void omci_profile_sanitize_olt_g(struct omci_olt_g *olt, u32 quirks)
+{
+ unsigned int i;
+
+ if (!olt || !(quirks & OMCI_OLT_QUIRK_SANITIZE_VERSION) ||
+ !olt->version_valid)
+ return;
+
+ for (i = 0; i < OMCI_OLT_VERSION_LEN && olt->version[i]; i++)
+ if (olt->version[i] < ' ' || olt->version[i] > '~')
+ olt->version[i] = ' ';
+}
+
+u16 omci_profile_normalize_uni_entity(u8 profile, u16 entity_id)
+{
+ if (omci_profile_quirks(profile) & OMCI_OLT_QUIRK_FULL_UNI_ENTITY_ID)
+ return entity_id;
+
+ return entity_id & 0xff;
+}
+
+void omci_profile_normalize_vlan_rule(u8 profile,
+ struct omci_extended_vlan_rule *rule)
+{
+ if (!rule || !(omci_profile_quirks(profile) &
+ OMCI_OLT_QUIRK_ZTE_VLAN_TAG_MODE))
+ return;
+
+ /*
+ * The vendor SDK forces a TPID and DEI when a ZTE rule produces an
+ * untagged service. Preserve the wire rule, but normalize the treatment
+ * consumed by the hardware-independent service resolver.
+ */
+ if (!rule->tags_to_remove && rule->treat_outer_vid >= 4096 &&
+ rule->treat_inner_vid >= 4096) {
+ rule->treat_inner_tpid_dei = 1;
+ rule->treat_inner_pbit = min_t(u8, rule->treat_inner_pbit, 8);
+ }
+}
+
+int omci_profile_resolve_multicast_ani(u8 profile,
+ u16 bridge_port_entity_id,
+ u16 *ani_entity_id)
+{
+ if (!ani_entity_id)
+ return -EINVAL;
+ if (!(omci_profile_quirks(profile) &
+ OMCI_OLT_QUIRK_DASAN_MULTICAST_ANI))
+ return -EOPNOTSUPP;
+
+ /*
+ * The legacy SDK associates a multicast GEM with the ANI allocated to
+ * its WAN MAC bridge port. Keep that stable semantic identifier in the
+ * normalized service; the hardware backend owns index allocation.
+ */
+ *ani_entity_id = bridge_port_entity_id;
+ return 0;
+}
diff --git a/net/xpon/omci/sysfs.c b/net/xpon/omci/sysfs.c
new file mode 100644
index 000000000000..577e0b53af28
--- /dev/null
+++ b/net/xpon/omci/sysfs.c
@@ -0,0 +1,697 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * OMCI sysfs interface below the owning xPON device.
+ *
+ * Generic Netlink remains the structured management API. The sysfs group
+ * exports the small, stable subset useful to shell scripts and early
userspace.
+ */
+
+#include <linux/ctype.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "../internal.h"
+#include "internal.h"
+
+static struct omci_device *omci_dev_from_dev(struct device *dev)
+{
+ struct xpon_device *xpon = dev_get_drvdata(dev);
+
+ return xpon ? xpon->omci : NULL;
+}
+
+static int omci_sysfs_config_set(struct omci_device *odev, u16 key,
+ const void *value, size_t len)
+{
+ u8 event = OMCI_EVENT_CONFIG_CHANGE;
+ int ret;
+
+ ret = omci_agent_config_set_userspace(odev, key, value, len,
+ OMCI_CONFIG_SOURCE_SYSFS);
+ if (ret)
+ return ret;
+
+ if (key == OMCI_CONFIG_OLT_PROFILE ||
+ key == OMCI_CONFIG_OLT_PROFILE_FORCE)
+ event = OMCI_EVENT_PROFILE_CHANGE;
+ omci_device_notify(odev, event);
+ return 0;
+}
+
+static int omci_sysfs_config_get_u8(struct omci_device *odev, u16 key,
+ u8 *value)
+{
+ size_t len = sizeof(*value);
+
+ return omci_agent_config_get(odev, key, value, &len);
+}
+
+static ssize_t dev_id_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+
+ return sysfs_emit(buf, "%u\n", odev->id);
+}
+static DEVICE_ATTR_RO(dev_id);
+
+static ssize_t ifindex_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+
+ return sysfs_emit(buf, "%u\n", odev->ifindex);
+}
+static DEVICE_ATTR_RO(ifindex);
+
+static void omci_sysfs_channel_snapshot(struct omci_device *odev, u16 *onu_id,
+ u16 *gem_port_id, u8 *state,
+ bool *channel_up)
+{
+ spin_lock_bh(&odev->state_lock);
+ if (onu_id)
+ *onu_id = odev->onu_id;
+ if (gem_port_id)
+ *gem_port_id = odev->gem_port_id;
+ if (state)
+ *state = odev->state;
+ if (channel_up)
+ *channel_up = odev->channel_up;
+ spin_unlock_bh(&odev->state_lock);
+}
+
+static ssize_t onu_id_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ u16 value;
+
+ omci_sysfs_channel_snapshot(odev, &value, NULL, NULL, NULL);
+ return sysfs_emit(buf, "%u\n", value);
+}
+static DEVICE_ATTR_RO(onu_id);
+
+static ssize_t gem_port_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ u16 value;
+
+ omci_sysfs_channel_snapshot(odev, NULL, &value, NULL, NULL);
+ return sysfs_emit(buf, "%u\n", value);
+}
+static DEVICE_ATTR_RO(gem_port_id);
+
+static ssize_t state_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ u8 value;
+
+ omci_sysfs_channel_snapshot(odev, NULL, NULL, &value, NULL);
+ return sysfs_emit(buf, "%u\n", value);
+}
+static DEVICE_ATTR_RO(state);
+
+static ssize_t channel_up_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ bool value;
+
+ omci_sysfs_channel_snapshot(odev, NULL, NULL, NULL, &value);
+ return sysfs_emit(buf, "%u\n", value);
+}
+static DEVICE_ATTR_RO(channel_up);
+
+static ssize_t capabilities_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+
+ return sysfs_emit(buf, "0x%08x\n", odev->capabilities);
+}
+static DEVICE_ATTR_RO(capabilities);
+
+#define OMCI_ATOMIC64_ATTR_RO(_name, _member) \
+static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ struct omci_device *odev = omci_dev_from_dev(dev); \
+ return sysfs_emit(buf, "%lld\n", \
+ (long long)atomic64_read(&odev->_member)); \
+} \
+static DEVICE_ATTR_RO(_name)
+
+OMCI_ATOMIC64_ATTR_RO(rx_packets, rx_packets);
+OMCI_ATOMIC64_ATTR_RO(rx_bytes, rx_bytes);
+OMCI_ATOMIC64_ATTR_RO(rx_dropped, rx_dropped);
+OMCI_ATOMIC64_ATTR_RO(tx_packets, tx_packets);
+OMCI_ATOMIC64_ATTR_RO(tx_bytes, tx_bytes);
+OMCI_ATOMIC64_ATTR_RO(tx_errors, tx_errors);
+
+#define OMCI_AGENT_ATOMIC64_ATTR_RO(_name, _member) \
+static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ struct omci_device *odev = omci_dev_from_dev(dev); \
+ return sysfs_emit(buf, "%lld\n", \
+ (long long)atomic64_read(&odev->agent._member)); \
+} \
+static DEVICE_ATTR_RO(_name)
+
+OMCI_AGENT_ATOMIC64_ATTR_RO(agent_responses, responses);
+OMCI_AGENT_ATOMIC64_ATTR_RO(agent_duplicates, duplicates);
+OMCI_AGENT_ATOMIC64_ATTR_RO(agent_unsupported, unsupported);
+OMCI_AGENT_ATOMIC64_ATTR_RO(agent_fake_responses, fake_responses);
+
+static ssize_t mib_sync_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ u16 value;
+
+ mutex_lock(&odev->agent.lock);
+ value = odev->agent.mib_sync;
+ mutex_unlock(&odev->agent.lock);
+ return sysfs_emit(buf, "%u\n", value);
+}
+static DEVICE_ATTR_RO(mib_sync);
+
+static ssize_t agent_operational_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ bool value;
+
+ mutex_lock(&odev->agent.lock);
+ value = odev->agent.operational;
+ mutex_unlock(&odev->agent.lock);
+ return sysfs_emit(buf, "%u\n", value);
+}
+static DEVICE_ATTR_RO(agent_operational);
+
+#define OMCI_CONFIG_U8_ATTR_RW(_name, _key) \
+static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ struct omci_device *odev = omci_dev_from_dev(dev); \
+ u8 value; \
+ int ret = omci_sysfs_config_get_u8(odev, _key, &value); \
+ if (ret) \
+ return ret; \
+ return sysfs_emit(buf, "%u\n", value); \
+} \
+static ssize_t _name##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ struct omci_device *odev = omci_dev_from_dev(dev); \
+ u8 value; \
+ int ret = kstrtou8(buf, 0, &value); \
+ if (ret) \
+ return ret; \
+ ret = omci_sysfs_config_set(odev, _key, &value, sizeof(value)); \
+ return ret ? ret : count; \
+} \
+static DEVICE_ATTR_RW(_name)
+
+#define OMCI_CONFIG_BOOL_ATTR_RW(_name, _key) \
+static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ struct omci_device *odev = omci_dev_from_dev(dev); \
+ u8 value; \
+ int ret = omci_sysfs_config_get_u8(odev, _key, &value); \
+ if (ret) \
+ return ret; \
+ return sysfs_emit(buf, "%u\n", !!value); \
+} \
+static ssize_t _name##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ struct omci_device *odev = omci_dev_from_dev(dev); \
+ bool enabled; \
+ u8 value; \
+ int ret = kstrtobool(buf, &enabled); \
+ if (ret) \
+ return ret; \
+ value = enabled; \
+ ret = omci_sysfs_config_set(odev, _key, &value, sizeof(value)); \
+ return ret ? ret : count; \
+} \
+static DEVICE_ATTR_RW(_name)
+
+OMCI_CONFIG_U8_ATTR_RW(traffic_management_option,
OMCI_CONFIG_TRAFFIC_MGMT_OPTION);
+OMCI_CONFIG_U8_ATTR_RW(uni_count, OMCI_CONFIG_UNI_COUNT);
+OMCI_CONFIG_U8_ATTR_RW(omcc_version, OMCI_CONFIG_OMCC_VERSION);
+OMCI_CONFIG_BOOL_ATTR_RW(agent_enabled, OMCI_CONFIG_AGENT_ENABLED);
+OMCI_CONFIG_BOOL_ATTR_RW(agent_permissive, OMCI_CONFIG_AGENT_PERMISSIVE);
+OMCI_CONFIG_BOOL_ATTR_RW(agent_fake_omci, OMCI_CONFIG_AGENT_FAKE_OMCI);
+OMCI_CONFIG_BOOL_ATTR_RW(agent_dying_gasp, OMCI_CONFIG_AGENT_DYING_GASP);
+
+static ssize_t omci_sysfs_string_show(struct omci_device *odev, u16 key,
+ char *buf)
+{
+ u8 value[OMCI_MAX_CONFIG_VALUE];
+ size_t len = sizeof(value);
+ int ret;
+
+ ret = omci_agent_config_get(odev, key, value, &len);
+ if (ret)
+ return ret;
+ return sysfs_emit(buf, "%.*s\n", (int)len, value);
+}
+
+static ssize_t omci_sysfs_string_store(struct omci_device *odev, u16 key,
+ const char *buf, size_t count)
+{
+ size_t input_count = count;
+ int ret;
+
+ while (count && (buf[count - 1] == '\n' || buf[count - 1] == '\r'))
+ count--;
+ if (!count)
+ return -EINVAL;
+
+ ret = omci_sysfs_config_set(odev, key, buf, count);
+ return ret ? ret : input_count;
+}
+
+#define OMCI_CONFIG_STRING_ATTR_RW(_name, _key) \
+static ssize_t _name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ return omci_sysfs_string_show(omci_dev_from_dev(dev), _key, buf); \
+} \
+static ssize_t _name##_store(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ return omci_sysfs_string_store(omci_dev_from_dev(dev), _key, buf, count); \
+} \
+static DEVICE_ATTR_RW(_name)
+
+OMCI_CONFIG_STRING_ATTR_RW(equipment_id, OMCI_CONFIG_EQUIPMENT_ID);
+OMCI_CONFIG_STRING_ATTR_RW(hardware_version, OMCI_CONFIG_HARDWARE_VERSION);
+OMCI_CONFIG_STRING_ATTR_RW(software_version_0, OMCI_CONFIG_SOFTWARE_VERSION_0);
+OMCI_CONFIG_STRING_ATTR_RW(software_version_1, OMCI_CONFIG_SOFTWARE_VERSION_1);
+
+static ssize_t vendor_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ u8 value[4];
+ size_t len = sizeof(value);
+ unsigned int i;
+ int ret;
+
+ ret = omci_agent_config_get(odev, OMCI_CONFIG_VENDOR_ID, value, &len);
+ if (ret)
+ return ret;
+ for (i = 0; i < sizeof(value); i++)
+ if (!isprint(value[i]))
+ return sysfs_emit(buf, "%*phN\n", (int)sizeof(value), value);
+ return sysfs_emit(buf, "%.*s\n", (int)sizeof(value), value);
+}
+
+static ssize_t vendor_id_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return omci_sysfs_string_store(omci_dev_from_dev(dev),
+ OMCI_CONFIG_VENDOR_ID, buf, count);
+}
+static DEVICE_ATTR_RW(vendor_id);
+
+static ssize_t serial_number_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ u8 value[8];
+ size_t len = sizeof(value);
+ unsigned int i;
+ int ret;
+
+ ret = omci_agent_config_get(odev, OMCI_CONFIG_SERIAL_NUMBER,
+ value, &len);
+ if (ret)
+ return ret;
+ for (i = 0; i < 4; i++)
+ if (!isprint(value[i]))
+ return sysfs_emit(buf, "%*phN\n", (int)sizeof(value), value);
+
+ return sysfs_emit(buf, "%c%c%c%c%02X%02X%02X%02X\n",
+ value[0], value[1], value[2], value[3],
+ value[4], value[5], value[6], value[7]);
+}
+
+static ssize_t serial_number_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return omci_sysfs_string_store(omci_dev_from_dev(dev),
+ OMCI_CONFIG_SERIAL_NUMBER, buf, count);
+}
+static DEVICE_ATTR_RW(serial_number);
+
+static ssize_t password_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return omci_sysfs_string_store(omci_dev_from_dev(dev), OMCI_CONFIG_PASSWORD,
+ buf, count);
+}
+static DEVICE_ATTR_WO(password);
+
+struct omci_onu_type_name {
+ u8 type;
+ const char *name;
+ const char *description;
+};
+
+static const struct omci_onu_type_name omci_onu_types[] = {
+ { OMCI_ONU_TYPE_OTHER, "Other", "Other" },
+ { OMCI_ONU_TYPE_SFU, "SFU", "Single Family Unit" },
+ { OMCI_ONU_TYPE_HGU, "HGU", "Home Gateway Unit" },
+ { OMCI_ONU_TYPE_MDU, "MDU", "Multi-Dwelling Unit" },
+ { OMCI_ONU_TYPE_SBU, "SBU", "Single Business Unit" },
+ { OMCI_ONU_TYPE_MTU, "MTU", "Multi-Tenant Unit" },
+ { OMCI_ONU_TYPE_CBU, "CBU", "Cellular Backhaul Unit" },
+};
+
+static const char *omci_sysfs_onu_type_name(u8 type)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(omci_onu_types); i++)
+ if (omci_onu_types[i].type == type)
+ return omci_onu_types[i].name;
+ return "Other";
+}
+
+static int omci_sysfs_parse_onu_type(const char *buf, size_t count, u8 *type)
+{
+ char input[32];
+ char *name;
+ unsigned int i;
+ int ret;
+
+ if (!count || count >= sizeof(input))
+ return -EINVAL;
+ memcpy(input, buf, count);
+ input[count] = '\0';
+ name = strim(input);
+
+ ret = kstrtou8(name, 0, type);
+ if (!ret)
+ return *type <= OMCI_ONU_TYPE_CBU ? 0 : -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(omci_onu_types); i++)
+ if (!strcasecmp(name, omci_onu_types[i].name) ||
+ !strcasecmp(name, omci_onu_types[i].description)) {
+ *type = omci_onu_types[i].type;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static ssize_t onu_type_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ u8 type;
+ int ret;
+
+ ret = omci_sysfs_config_get_u8(odev, OMCI_CONFIG_ONU_TYPE, &type);
+ if (ret)
+ return ret;
+ return sysfs_emit(buf, "%s\n", omci_sysfs_onu_type_name(type));
+}
+
+static ssize_t onu_type_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ u8 type;
+ int ret;
+
+ ret = omci_sysfs_parse_onu_type(buf, count, &type);
+ if (ret)
+ return ret;
+ ret = omci_sysfs_config_set(odev, OMCI_CONFIG_ONU_TYPE,
+ &type, sizeof(type));
+ return ret ? ret : count;
+}
+static DEVICE_ATTR_RW(onu_type);
+
+static ssize_t onu_type_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ u8 type;
+ int ret;
+
+ ret = omci_sysfs_config_get_u8(odev, OMCI_CONFIG_ONU_TYPE, &type);
+ if (ret)
+ return ret;
+ return sysfs_emit(buf, "%u\n", type);
+}
+static DEVICE_ATTR_RO(onu_type_id);
+
+static int omci_sysfs_parse_profile(const char *buf, size_t count,
+ bool force, u8 *profile)
+{
+ char input[32];
+ char *name;
+ unsigned int i;
+ int ret;
+
+ if (!count || count >= sizeof(input))
+ return -EINVAL;
+ memcpy(input, buf, count);
+ input[count] = '\0';
+ name = strim(input);
+
+ ret = kstrtou8(name, 0, profile);
+ if (!ret)
+ return force ? (omci_profile_forceable(*profile) ? 0 : -EINVAL) :
+ (omci_profile_valid(*profile) ? 0 : -EINVAL);
+
+ if (force && !strcasecmp(name, "unspecified")) {
+ *profile = OMCI_OLT_PROFILE_UNSPEC;
+ return 0;
+ }
+
+ for (i = OMCI_OLT_PROFILE_GENERIC; i <= OMCI_OLT_PROFILE_ZTE; i++) {
+ if (!strcasecmp(name, omci_olt_profile_name(i))) {
+ if (force && !omci_profile_forceable(i))
+ return -EINVAL;
+ *profile = i;
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static ssize_t olt_profile_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ u8 profile;
+ int ret;
+
+ ret = omci_sysfs_config_get_u8(odev, OMCI_CONFIG_OLT_PROFILE, &profile);
+ if (ret)
+ return ret;
+ return sysfs_emit(buf, "%u %s\n", profile,
+ omci_olt_profile_name(profile));
+}
+
+static ssize_t olt_profile_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ u8 profile;
+ int ret;
+
+ ret = omci_sysfs_parse_profile(buf, count, false, &profile);
+ if (ret)
+ return ret;
+ ret = omci_sysfs_config_set(odev, OMCI_CONFIG_OLT_PROFILE,
+ &profile, sizeof(profile));
+ return ret ? ret : count;
+}
+static DEVICE_ATTR_RW(olt_profile);
+
+static ssize_t olt_profile_force_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ u8 profile;
+ int ret;
+
+ ret = omci_sysfs_config_get_u8(odev, OMCI_CONFIG_OLT_PROFILE_FORCE,
+ &profile);
+ if (ret)
+ return ret;
+ return sysfs_emit(buf, "%u %s\n", profile,
+ omci_olt_profile_name(profile));
+}
+
+static ssize_t olt_profile_force_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ u8 profile;
+ int ret;
+
+ ret = omci_sysfs_parse_profile(buf, count, true, &profile);
+ if (ret)
+ return ret;
+ ret = omci_sysfs_config_set(odev, OMCI_CONFIG_OLT_PROFILE_FORCE,
+ &profile, sizeof(profile));
+ return ret ? ret : count;
+}
+static DEVICE_ATTR_RW(olt_profile_force);
+
+static ssize_t olt_profile_effective_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ u8 profile;
+
+ mutex_lock(&odev->agent.lock);
+ profile = odev->agent.profile_effective;
+ mutex_unlock(&odev->agent.lock);
+ return sysfs_emit(buf, "%u %s\n", profile,
+ omci_olt_profile_name(profile));
+}
+static DEVICE_ATTR_RO(olt_profile_effective);
+
+static ssize_t olt_profile_quirks_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ u32 quirks;
+
+ mutex_lock(&odev->agent.lock);
+ quirks = odev->agent.profile_quirks;
+ mutex_unlock(&odev->agent.lock);
+ return sysfs_emit(buf, "0x%08x\n", quirks);
+}
+static DEVICE_ATTR_RO(olt_profile_quirks);
+
+static ssize_t olt_vendor_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ struct omci_olt_g olt = {};
+
+ if (omci_agent_olt_g_get(odev, &olt) || !olt.vendor_id_valid)
+ return sysfs_emit(buf, "unknown\n");
+ return sysfs_emit(buf, "%s\n", olt.vendor_id);
+}
+static DEVICE_ATTR_RO(olt_vendor_id);
+
+static ssize_t olt_equipment_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ struct omci_olt_g olt = {};
+
+ if (omci_agent_olt_g_get(odev, &olt) || !olt.equipment_id_valid)
+ return sysfs_emit(buf, "unknown\n");
+ return sysfs_emit(buf, "%s\n", olt.equipment_id);
+}
+static DEVICE_ATTR_RO(olt_equipment_id);
+
+static ssize_t olt_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct omci_device *odev = omci_dev_from_dev(dev);
+ struct omci_olt_g olt = {};
+
+ if (omci_agent_olt_g_get(odev, &olt) || !olt.version_valid)
+ return sysfs_emit(buf, "unknown\n");
+ return sysfs_emit(buf, "%s\n", olt.version);
+}
+static DEVICE_ATTR_RO(olt_version);
+
+static struct attribute *omci_attrs[] = {
+ &dev_attr_dev_id.attr,
+ &dev_attr_ifindex.attr,
+ &dev_attr_onu_id.attr,
+ &dev_attr_gem_port_id.attr,
+ &dev_attr_state.attr,
+ &dev_attr_channel_up.attr,
+ &dev_attr_capabilities.attr,
+ &dev_attr_rx_packets.attr,
+ &dev_attr_rx_bytes.attr,
+ &dev_attr_rx_dropped.attr,
+ &dev_attr_tx_packets.attr,
+ &dev_attr_tx_bytes.attr,
+ &dev_attr_tx_errors.attr,
+ &dev_attr_agent_responses.attr,
+ &dev_attr_agent_duplicates.attr,
+ &dev_attr_agent_unsupported.attr,
+ &dev_attr_agent_fake_responses.attr,
+ &dev_attr_mib_sync.attr,
+ &dev_attr_agent_operational.attr,
+ &dev_attr_agent_enabled.attr,
+ &dev_attr_agent_permissive.attr,
+ &dev_attr_agent_fake_omci.attr,
+ &dev_attr_agent_dying_gasp.attr,
+ &dev_attr_serial_number.attr,
+ &dev_attr_vendor_id.attr,
+ &dev_attr_password.attr,
+ &dev_attr_equipment_id.attr,
+ &dev_attr_hardware_version.attr,
+ &dev_attr_software_version_0.attr,
+ &dev_attr_software_version_1.attr,
+ &dev_attr_traffic_management_option.attr,
+ &dev_attr_onu_type.attr,
+ &dev_attr_onu_type_id.attr,
+ &dev_attr_uni_count.attr,
+ &dev_attr_omcc_version.attr,
+ &dev_attr_olt_profile.attr,
+ &dev_attr_olt_profile_force.attr,
+ &dev_attr_olt_profile_effective.attr,
+ &dev_attr_olt_profile_quirks.attr,
+ &dev_attr_olt_vendor_id.attr,
+ &dev_attr_olt_equipment_id.attr,
+ &dev_attr_olt_version.attr,
+ NULL,
+};
+static const struct attribute_group omci_group = {
+ .name = "omci",
+ .attrs = omci_attrs,
+};
+
+int omci_sysfs_init(void)
+{
+ return 0;
+}
+
+void omci_sysfs_exit(void)
+{
+}
+
+int omci_sysfs_register(struct omci_device *odev)
+{
+ return sysfs_create_group(&odev->xpon->class_dev->kobj, &omci_group);
+}
+
+void omci_sysfs_unregister(struct omci_device *odev)
+{
+ if (odev->xpon && odev->xpon->class_dev)
+ sysfs_remove_group(&odev->xpon->class_dev->kobj, &omci_group);
+}
diff --git a/net/xpon/omci/wire.c b/net/xpon/omci/wire.c
new file mode 100644
index 000000000000..d60b68587cfa
--- /dev/null
+++ b/net/xpon/omci/wire.c
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/unaligned.h>
+
+#include "internal.h"
+#include "wire.h"
+
+int omci_wire_decode(const void *data, size_t len,
+ struct omci_wire_request *request)
+{
+ const u8 *pdu = data;
+ u16 payload_len;
+ size_t pdu_len;
+
+ if (!data || !request || len < 8)
+ return -EINVAL;
+
+ memset(request, 0, sizeof(*request));
+ request->transaction_id = get_unaligned_be16(pdu);
+ request->message_type = pdu[2];
+ request->device_id = pdu[3];
+ request->class_id = get_unaligned_be16(pdu + 4);
+ request->entity_id = get_unaligned_be16(pdu + 6);
+
+ switch (request->device_id) {
+ case OMCI_BASELINE_DEV_ID:
+ if (len != OMCI_BASELINE_LEN_NO_MIC && len != OMCI_BASELINE_LEN)
+ return -EMSGSIZE;
+ request->payload = pdu + 8;
+ request->payload_len = 32;
+ return 0;
+ case OMCI_EXTENDED_DEV_ID:
+ if (len < OMCI_EXTENDED_HEADER_LEN)
+ return -EMSGSIZE;
+ payload_len = get_unaligned_be16(pdu + 8);
+ pdu_len = OMCI_EXTENDED_HEADER_LEN + payload_len;
+ if (pdu_len > OMCI_MAX_PDU_LEN || len < pdu_len)
+ return -EMSGSIZE;
+ if (len != pdu_len && len != pdu_len + OMCI_MIC_LEN)
+ return -EMSGSIZE;
+ request->payload = pdu + OMCI_EXTENDED_HEADER_LEN;
+ request->payload_len = payload_len;
+ return 0;
+ default:
+ return -EPROTO;
+ }
+}
+
+int omci_wire_encode_response(const struct omci_wire_request *request,
+ u8 action, const void *payload,
+ size_t payload_len, void *data,
+ size_t capacity, size_t *encoded_len)
+{
+ u8 *pdu = data;
+ size_t len;
+
+ if (!request || !data || !encoded_len || (!payload && payload_len))
+ return -EINVAL;
+
+ switch (request->device_id) {
+ case OMCI_BASELINE_DEV_ID:
+ if (payload_len > 32 || capacity < OMCI_BASELINE_LEN_NO_MIC)
+ return -EMSGSIZE;
+ len = OMCI_BASELINE_LEN_NO_MIC;
+ memset(pdu, 0, len);
+ put_unaligned_be16(request->transaction_id, pdu);
+ pdu[2] = (request->message_type & 0x80) | 0x20 | action;
+ pdu[3] = OMCI_BASELINE_DEV_ID;
+ put_unaligned_be16(request->class_id, pdu + 4);
+ put_unaligned_be16(request->entity_id, pdu + 6);
+ if (payload_len)
+ memcpy(pdu + 8, payload, payload_len);
+ put_unaligned_be32(40, pdu + 40);
+ break;
+ case OMCI_EXTENDED_DEV_ID:
+ if (payload_len > U16_MAX)
+ return -EMSGSIZE;
+ len = OMCI_EXTENDED_HEADER_LEN + payload_len;
+ if (len > OMCI_MAX_PDU_LEN || capacity < len)
+ return -EMSGSIZE;
+ put_unaligned_be16(request->transaction_id, pdu);
+ pdu[2] = (request->message_type & 0x80) | 0x20 | action;
+ pdu[3] = OMCI_EXTENDED_DEV_ID;
+ put_unaligned_be16(request->class_id, pdu + 4);
+ put_unaligned_be16(request->entity_id, pdu + 6);
+ put_unaligned_be16(payload_len, pdu + 8);
+ if (payload_len)
+ memcpy(pdu + OMCI_EXTENDED_HEADER_LEN, payload,
+ payload_len);
+ break;
+ default:
+ return -EPROTO;
+ }
+
+ *encoded_len = len;
+ return 0;
+}
diff --git a/net/xpon/omci/wire.h b/net/xpon/omci/wire.h
new file mode 100644
index 000000000000..9a6b748b243e
--- /dev/null
+++ b/net/xpon/omci/wire.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _NET_OMCI_WIRE_H
+#define _NET_OMCI_WIRE_H
+
+#include <linux/types.h>
+
+struct omci_wire_request {
+ u16 transaction_id;
+ u16 class_id;
+ u16 entity_id;
+ u16 payload_len;
+ u8 message_type;
+ u8 device_id;
+ const u8 *payload;
+};
+
+int omci_wire_decode(const void *data, size_t len,
+ struct omci_wire_request *request);
+int omci_wire_encode_response(const struct omci_wire_request *request,
+ u8 action, const void *payload,
+ size_t payload_len, void *data,
+ size_t capacity, size_t *encoded_len);
+
+#endif /* _NET_OMCI_WIRE_H */
diff --git a/net/xpon/sysfs.c b/net/xpon/sysfs.c
new file mode 100644
index 000000000000..32bebc663536
--- /dev/null
+++ b/net/xpon/sysfs.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/device.h>
+#include <linux/netdevice.h>
+#include <linux/string.h>
+#include <net/xpon.h>
+
+#include "internal.h"
+
+struct class *xpon_class;
+
+static const char *xpon_mode_name(enum xpon_mode mode)
+{
+ switch (mode) {
+ case XPON_MODE_GPON:
+ return "gpon";
+ case XPON_MODE_EPON:
+ return "epon";
+ case XPON_MODE_XGSPON:
+ return "xgspon";
+ default:
+ return "unknown";
+ }
+}
+
+static const char *xpon_registration_name(enum xpon_registration_state state)
+{
+ switch (state) {
+ case XPON_REGISTRATION_DOWN:
+ return "down";
+ case XPON_REGISTRATION_DISCOVERY:
+ return "discovery";
+ case XPON_REGISTRATION_REGISTERING:
+ return "registering";
+ case XPON_REGISTRATION_OPERATIONAL:
+ return "operational";
+ default:
+ return "unknown";
+ }
+}
+
+static ssize_t mode_available_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct xpon_device *xpon = dev_get_drvdata(dev);
+ ssize_t len = 0;
+ int mode;
+
+ for (mode = 0; mode < __XPON_MODE_MAX; mode++) {
+ if (!(xpon->modes & XPON_MODE_CAP(mode)))
+ continue;
+ len += sysfs_emit_at(buf, len, "%s%s", len ? " " : "",
+ xpon_mode_name(mode));
+ }
+
+ return sysfs_emit_at(buf, len, "\n") + len;
+}
+static DEVICE_ATTR_RO(mode_available);
+
+static ssize_t mode_current_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct xpon_device *xpon = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%s\n", xpon_mode_name(xpon->state.mode));
+}
+
+static ssize_t mode_current_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct xpon_device *xpon = dev_get_drvdata(dev);
+ enum xpon_mode mode;
+ int ret;
+
+ if (sysfs_streq(buf, "gpon"))
+ mode = XPON_MODE_GPON;
+ else if (sysfs_streq(buf, "epon"))
+ mode = XPON_MODE_EPON;
+ else if (sysfs_streq(buf, "xgspon"))
+ mode = XPON_MODE_XGSPON;
+ else
+ return -EINVAL;
+
+ ret = xpon_device_set_mode(xpon, mode);
+ return ret ? ret : count;
+}
+static DEVICE_ATTR_RW(mode_current);
+
+static ssize_t state_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct xpon_device *xpon = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%s\n",
+ xpon_registration_name(xpon->state.registration));
+}
+static DEVICE_ATTR_RO(state);
+
+static ssize_t carrier_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct xpon_device *xpon = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", xpon->state.carrier);
+}
+static DEVICE_ATTR_RO(carrier);
+
+static ssize_t signal_detect_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct xpon_device *xpon = dev_get_drvdata(dev);
+
+ if (!(xpon->state.valid & XPON_STATE_F_SIGNAL))
+ return sysfs_emit(buf, "unknown\n");
+ return sysfs_emit(buf, "%u\n", xpon->state.signal_detect);
+}
+static DEVICE_ATTR_RO(signal_detect);
+
+static ssize_t los_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct xpon_device *xpon = dev_get_drvdata(dev);
+
+ if (!(xpon->state.valid & XPON_STATE_F_LOS))
+ return sysfs_emit(buf, "unknown\n");
+ return sysfs_emit(buf, "%u\n", xpon->state.los);
+}
+static DEVICE_ATTR_RO(los);
+
+static struct attribute *xpon_attrs[] = {
+ &dev_attr_mode_available.attr,
+ &dev_attr_mode_current.attr,
+ &dev_attr_state.attr,
+ &dev_attr_carrier.attr,
+ &dev_attr_signal_detect.attr,
+ &dev_attr_los.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(xpon);
+
+static int xpon_netdev_event(struct notifier_block *nb, unsigned long event,
+ void *ptr)
+{
+ struct net_device *netdev = netdev_notifier_info_to_dev(ptr);
+ struct xpon_device *xpon;
+
+ if (event != NETDEV_CHANGENAME)
+ return NOTIFY_DONE;
+
+ xpon = xpon_device_find_by_ifindex(netdev->ifindex);
+ if (!xpon)
+ return NOTIFY_DONE;
+
+ device_rename(xpon->class_dev, netdev->name);
+ xpon_device_put(xpon);
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block xpon_netdev_nb = {
+ .notifier_call = xpon_netdev_event,
+};
+
+int xpon_sysfs_init(void)
+{
+ int ret;
+
+ xpon_class = class_create("xpon");
+ if (IS_ERR(xpon_class))
+ return PTR_ERR(xpon_class);
+
+ ret = register_netdevice_notifier(&xpon_netdev_nb);
+ if (ret) {
+ class_destroy(xpon_class);
+ xpon_class = NULL;
+ }
+ return ret;
+}
+
+void xpon_sysfs_exit(void)
+{
+ unregister_netdevice_notifier(&xpon_netdev_nb);
+ class_destroy(xpon_class);
+ xpon_class = NULL;
+}
+
+int xpon_sysfs_register(struct xpon_device *xpon)
+{
+ int ret;
+
+ xpon->class_dev = device_create_with_groups(xpon_class, xpon->parent,
+ MKDEV(0, 0), xpon,
+ xpon_groups, "%s",
+ xpon->netdev->name);
+ if (IS_ERR(xpon->class_dev)) {
+ ret = PTR_ERR(xpon->class_dev);
+ xpon->class_dev = NULL;
+ return ret;
+ }
+
+ if (xpon->optical) {
+ ret = sysfs_create_link(&xpon->class_dev->kobj,
+ &xpon->optical->kobj, "optical");
+ if (ret)
+ dev_warn(xpon->parent,
+ "failed to create optical xPON link: %d\n", ret);
+ }
+
+ return 0;
+}
+
+void xpon_sysfs_unregister(struct xpon_device *xpon)
+{
+ if (!xpon->class_dev)
+ return;
+ if (xpon->optical)
+ sysfs_remove_link(&xpon->class_dev->kobj, "optical");
+ device_unregister(xpon->class_dev);
+ xpon->class_dev = NULL;
+}
+
+void xpon_sysfs_notify(struct xpon_device *xpon, unsigned long changed)
+{
+ if (!xpon->class_dev)
+ return;
+ if (changed & XPON_EVENT_MODE)
+ sysfs_notify(&xpon->class_dev->kobj, NULL, "mode_current");
+ if (changed & XPON_EVENT_REGISTRATION)
+ sysfs_notify(&xpon->class_dev->kobj, NULL, "state");
+ if (changed & XPON_EVENT_CARRIER)
+ sysfs_notify(&xpon->class_dev->kobj, NULL, "carrier");
+ if (changed & XPON_EVENT_SIGNAL)
+ sysfs_notify(&xpon->class_dev->kobj, NULL, "signal_detect");
+ if (changed & XPON_EVENT_LOS)
+ sysfs_notify(&xpon->class_dev->kobj, NULL, "los");
+}
^ permalink raw reply related [flat|nested] 26+ messages in thread