diff --git a/backends/ebpf/CMakeLists.txt b/backends/ebpf/CMakeLists.txt index c2f79f2231a..e93bd678292 100644 --- a/backends/ebpf/CMakeLists.txt +++ b/backends/ebpf/CMakeLists.txt @@ -41,8 +41,10 @@ set (P4C_EBPF_SRCS psa/ebpfPipeline.cpp psa/ebpfPsaParser.cpp psa/ebpfPsaDeparser.cpp + psa/ebpfPsaControl.cpp psa/ebpfPsaTable.cpp - psa/backend.cpp) + psa/backend.cpp + psa/externs/ebpfPsaCounter.cpp) set (P4C_EBPF_HDRS codeGen.h @@ -70,6 +72,7 @@ set (P4C_EBPF_HDRS psa/ebpfPsaDeparser.h psa/ebpfPsaControl.h psa/ebpfPsaTable.h + psa/externs/ebpfPsaCounter.h ) add_cpplint_files(${CMAKE_CURRENT_SOURCE_DIR} "${P4C_EBPF_SRCS};${P4C_EBPF_HDRS}") diff --git a/backends/ebpf/psa/ebpfPsaControl.cpp b/backends/ebpf/psa/ebpfPsaControl.cpp new file mode 100644 index 00000000000..2179858dbc1 --- /dev/null +++ b/backends/ebpf/psa/ebpfPsaControl.cpp @@ -0,0 +1,40 @@ +/* +Copyright 2022-present Orange +Copyright 2022-present Open Networking Foundation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "ebpfPsaControl.h" + +namespace EBPF { + +ControlBodyTranslatorPSA::ControlBodyTranslatorPSA(const EBPFControlPSA* control) : + CodeGenInspector(control->program->refMap, control->program->typeMap), + ControlBodyTranslator(control) {} + +void ControlBodyTranslatorPSA::processMethod(const P4::ExternMethod* method) { + auto decl = method->object; + auto declType = method->originalExternType; + cstring name = EBPFObject::externalName(decl); + + if (declType->name.name == "Counter") { + auto counterMap = control->getCounter(name); + counterMap->to()->emitMethodInvocation(builder, method, this); + return; + } + + ControlBodyTranslator::processMethod(method); +} + +} diff --git a/backends/ebpf/psa/ebpfPsaControl.h b/backends/ebpf/psa/ebpfPsaControl.h index 4561f399640..0f6a13247ee 100644 --- a/backends/ebpf/psa/ebpfPsaControl.h +++ b/backends/ebpf/psa/ebpfPsaControl.h @@ -24,6 +24,24 @@ namespace EBPF { class EBPFControlPSA; +class ControlBodyTranslatorPSA : public ControlBodyTranslator { + public: + explicit ControlBodyTranslatorPSA(const EBPFControlPSA* control); + + void processMethod(const P4::ExternMethod* method) override; +}; + +class ActionTranslationVisitorPSA : public ActionTranslationVisitor, + public ControlBodyTranslatorPSA { + public: + ActionTranslationVisitorPSA(const EBPFProgram* program, cstring valueName); + + bool preorder(const IR::PathExpression* pe) override; + bool isActionParameter(const IR::Expression *expression) const; + + void processMethod(const P4::ExternMethod* method) override; +}; + class EBPFControlPSA : public EBPFControl { public: // Keeps track if ingress_timestamp or egress_timestamp is used within a control block. diff --git a/backends/ebpf/psa/ebpfPsaGen.cpp b/backends/ebpf/psa/ebpfPsaGen.cpp index 4ff7e708b75..b31982fe3d6 100644 --- a/backends/ebpf/psa/ebpfPsaGen.cpp +++ b/backends/ebpf/psa/ebpfPsaGen.cpp @@ -20,6 +20,7 @@ limitations under the License. #include "ebpfPsaTable.h" #include "ebpfPsaControl.h" #include "xdpHelpProgram.h" +#include "externs/ebpfPsaCounter.h" namespace EBPF { @@ -520,7 +521,7 @@ bool ConvertToEBPFControlPSA::preorder(const IR::ControlBlock *ctrl) { control->inputStandardMetadata = *it; ++it; control->outputStandardMetadata = *it; - auto codegen = new ControlBodyTranslator(control); + auto codegen = new ControlBodyTranslatorPSA(control); codegen->substitute(control->headers, parserHeaders); if (type != TC_EGRESS) { @@ -608,6 +609,24 @@ bool ConvertToEBPFControlPSA::preorder(const IR::Declaration_Variable* decl) { return true; } +bool ConvertToEBPFControlPSA::preorder(const IR::ExternBlock* instance) { + auto di = instance->node->to(); + if (di == nullptr) + return false; + cstring name = EBPFObject::externalName(di); + cstring typeName = instance->type->getName().name; + + if (typeName == "Counter") { + auto ctr = new EBPFCounterPSA(program, di, name, control->codeGen); + control->counters.emplace(name, ctr); + } else { + ::error(ErrorType::ERR_UNEXPECTED, "Unexpected block %s nested within control", + instance); + } + + return false; +} + // =====================EBPFDeparser============================= bool ConvertToEBPFDeparserPSA::preorder(const IR::ControlBlock *ctrl) { if (type == TC_INGRESS) { diff --git a/backends/ebpf/psa/ebpfPsaGen.h b/backends/ebpf/psa/ebpfPsaGen.h index cd529700850..b1c719dc4ee 100644 --- a/backends/ebpf/psa/ebpfPsaGen.h +++ b/backends/ebpf/psa/ebpfPsaGen.h @@ -169,6 +169,7 @@ class ConvertToEBPFControlPSA : public Inspector { bool preorder(const IR::Declaration_Variable*) override; bool preorder(const IR::Member *m) override; bool preorder(const IR::IfStatement *a) override; + bool preorder(const IR::ExternBlock* instance) override; EBPF::EBPFControlPSA *getEBPFControl() { return control; } }; diff --git a/backends/ebpf/psa/ebpfPsaTable.cpp b/backends/ebpf/psa/ebpfPsaTable.cpp index 8247f7ad221..1738cf764cf 100644 --- a/backends/ebpf/psa/ebpfPsaTable.cpp +++ b/backends/ebpf/psa/ebpfPsaTable.cpp @@ -22,6 +22,52 @@ limitations under the License. namespace EBPF { +// =====================ActionTranslationVisitorPSA============================= +ActionTranslationVisitorPSA::ActionTranslationVisitorPSA(const EBPFProgram* program, + cstring valueName) : + CodeGenInspector(program->refMap, program->typeMap), + ActionTranslationVisitor(valueName, program), + ControlBodyTranslatorPSA(program->to()->control) {} + +bool ActionTranslationVisitorPSA::preorder(const IR::PathExpression* pe) { + if (isActionParameter(pe)) { + return ActionTranslationVisitor::preorder(pe); + } + return ControlBodyTranslator::preorder(pe); +} + +bool ActionTranslationVisitorPSA::isActionParameter(const IR::Expression *expression) const { + if (auto path = expression->to()) + return ActionTranslationVisitor::isActionParameter(path); + else if (auto cast = expression->to()) + return isActionParameter(cast->expr); + else + return false; +} + +void ActionTranslationVisitorPSA::processMethod(const P4::ExternMethod* method) { + auto declType = method->originalExternType; + auto decl = method->object; + BUG_CHECK(decl->is(), + "Extern has not been declared: %1%", decl); + auto di = decl->to(); + auto instanceName = EBPFObject::externalName(di); + + if (declType->name.name == "Counter") { + auto ctr = control->to()->getCounter(instanceName); + // Counter count() always has one argument/index + if (ctr != nullptr) { + ctr->to()->emitMethodInvocation(builder, method, this); + } else { + ::error(ErrorType::ERR_NOT_FOUND, + "%1%: Counter named %2% not found", + method->expr, instanceName); + } + } else { + ControlBodyTranslatorPSA::processMethod(method); + } +} + // =====================EBPFTablePSA============================= EBPFTablePSA::EBPFTablePSA(const EBPFProgram* program, const IR::TableBlock* table, CodeGenInspector* codeGen) : @@ -42,6 +88,11 @@ EBPFTablePSA::EBPFTablePSA(const EBPFProgram* program, const IR::TableBlock* tab } } +ActionTranslationVisitor* EBPFTablePSA::createActionTranslationVisitor( + cstring valueName, const EBPFProgram* program) const { + return new ActionTranslationVisitorPSA(program->to(), valueName); +} + void EBPFTablePSA::emitValueStructStructure(CodeBuilder* builder) { // TODO: placeholder for handling psa_implementation EBPFTable::emitValueStructStructure(builder); diff --git a/backends/ebpf/psa/ebpfPsaTable.h b/backends/ebpf/psa/ebpfPsaTable.h index 5c17ae1be32..0d37a587c2a 100644 --- a/backends/ebpf/psa/ebpfPsaTable.h +++ b/backends/ebpf/psa/ebpfPsaTable.h @@ -19,7 +19,7 @@ limitations under the License. #include "frontends/p4/methodInstance.h" #include "backends/ebpf/ebpfTable.h" -#include "ebpfPsaControl.h" +#include "backends/ebpf/psa/externs/ebpfPsaCounter.h" namespace EBPF { @@ -33,6 +33,9 @@ class EBPFTablePSA : public EBPFTable { size_t size) const; protected: + ActionTranslationVisitor* createActionTranslationVisitor( + cstring valueName, const EBPFProgram* program) const override; + void emitTableValue(CodeBuilder* builder, const IR::MethodCallExpression* actionMce, cstring valueName); void emitDefaultActionInitializer(CodeBuilder *builder); diff --git a/backends/ebpf/psa/externs/ebpfPsaCounter.cpp b/backends/ebpf/psa/externs/ebpfPsaCounter.cpp new file mode 100644 index 00000000000..4987b936a08 --- /dev/null +++ b/backends/ebpf/psa/externs/ebpfPsaCounter.cpp @@ -0,0 +1,255 @@ +/* +Copyright 2022-present Orange +Copyright 2022-present Open Networking Foundation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "ebpfPsaCounter.h" +#include "backends/ebpf/ebpfType.h" + +namespace EBPF { + +EBPFCounterPSA::EBPFCounterPSA(const EBPFProgram* program, const IR::Declaration_Instance* di, + cstring name, CodeGenInspector* codeGen) : + EBPFCounterTable(program, name, codeGen, 1, false) { + CHECK_NULL(di); + if (di->arguments->size() != 2) { + ::error(ErrorType::ERR_MODEL, "Expected 2 arguments: %1%", di); + return; + } + + if (!di->type->is()) { + ::error(ErrorType::ERR_MODEL, "Missing specialization: %1%", di); + return; + } + auto ts = di->type->to(); + + if (ts->arguments->size() != 2) { + ::error(ErrorType::ERR_MODEL, "Expected a type specialized with two arguments: %1%", ts); + return; + } + + // check dataplane counter width + auto dpwtype = ts->arguments->at(0); + if (!dpwtype->is()) { + ::error(ErrorType::ERR_UNSUPPORTED, "Must be bit or int type: %1%", dpwtype); + return; + } + + dataplaneWidthType = EBPFTypeFactory::instance->create(dpwtype); + unsigned dataplaneWidth = dpwtype->width_bits(); + if (dataplaneWidth > 64) { + ::error(ErrorType::ERR_UNSUPPORTED, + "Counters dataplane width up to 64 bits are supported: %1%", dpwtype); + return; + } + if (dataplaneWidth < 8 || (dataplaneWidth & (dataplaneWidth - 1)) != 0) { + ::warning(ErrorType::WARN_UNSUPPORTED, "Counter dataplane width will be extended to " + "nearest type (8, 16, 32 or 64 bits): %1%", dpwtype); + } + + // check index type + auto istype = ts->arguments->at(1); + if (!dpwtype->is()) { + ::error(ErrorType::ERR_UNSUPPORTED, "Must be bit or int type: %1%", istype); + return; + } + unsigned indexWidth = istype->width_bits(); + if (indexWidth != 32) { + // ARRAY_MAP can have only 32 bits key, so assume this and warn user + ::warning(ErrorType::WARN_UNSUPPORTED, + "Only 32-bit type for index is supported, changed to 32 bit: %1%", istype); + indexWidth = 32; + } + + indexWidthType = EBPFTypeFactory::instance->create(istype); + + auto declaredSize = di->arguments->at(0)->expression->to(); + if (!declaredSize->fitsInt()) { + ::error(ErrorType::ERR_OVERLIMIT, "%1%: size too large", declaredSize); + return; + } + size = declaredSize->asUnsigned(); + + + // By default, use BPF array map for Counter + // TODO: add more advance logic to decide whether used map will be HASH_MAP or ARRAY_MAP + isHash = false; + + auto typeArg = di->arguments->at(di->arguments->size() - 1)->expression->to(); + type = toCounterType(typeArg->asInt()); +} + +EBPFCounterPSA::CounterType EBPFCounterPSA::toCounterType(const int type) { + // TODO: make use of something similar to EBPFModel to avoid hardcoded values + if (type == 0) + return CounterType::PACKETS; + else if (type == 1) + return CounterType::BYTES; + else if (type == 2) + return CounterType::PACKETS_AND_BYTES; + + BUG("Unknown counter type %1%", type); +} + +void EBPFCounterPSA::emitTypes(CodeBuilder* builder) { + emitKeyType(builder); + emitValueType(builder); +} + +void EBPFCounterPSA::emitKeyType(CodeBuilder* builder) { + builder->emitIndent(); + builder->append("typedef "); + indexWidthType->emit(builder); + builder->appendFormat(" %s", keyTypeName.c_str()); + builder->endOfStatement(true); +} + +void EBPFCounterPSA::emitValueType(CodeBuilder* builder) { + builder->emitIndent(); + builder->append("typedef "); + builder->append("struct "); + builder->blockStart(); + if (type == CounterType::BYTES || type == CounterType::PACKETS_AND_BYTES) { + builder->emitIndent(); + dataplaneWidthType->emit(builder); + builder->append(" bytes"); + builder->endOfStatement(true); + } + if (type == CounterType::PACKETS || type == CounterType::PACKETS_AND_BYTES) { + builder->emitIndent(); + dataplaneWidthType->emit(builder); + builder->append(" packets"); + builder->endOfStatement(true); + } + + builder->blockEnd(false); + builder->appendFormat(" %s", valueTypeName.c_str()); + builder->endOfStatement(true); +} + +void EBPFCounterPSA::emitMethodInvocation(CodeBuilder* builder, const P4::ExternMethod* method, + CodeGenInspector* codeGen) { + if (method->method->name.name != "count") { + ::error(ErrorType::ERR_UNSUPPORTED, "Unexpected method %1%", method->expr); + return; + } + BUG_CHECK(method->expr->arguments->size() == 1, + "Expected just 1 argument for %1%", method->expr); + + builder->blockStart(); + this->emitCount(builder, method->expr, codeGen); + builder->blockEnd(false); +} + +void EBPFCounterPSA::emitCount(CodeBuilder* builder, + const IR::MethodCallExpression *expression, + CodeGenInspector* codeGen) { + BUG_CHECK(codeGen != nullptr, "Index codeGen is nullptr!"); + cstring keyName = program->refMap->newName("key"); + cstring valueName = program->refMap->newName("value"); + cstring msgStr, varStr; + + builder->emitIndent(); + builder->appendFormat("%s *%s", valueTypeName.c_str(), valueName.c_str()); + builder->endOfStatement(true); + + builder->emitIndent(); + builder->appendFormat("%s %s = ", keyTypeName.c_str(), keyName.c_str()); + auto index = expression->arguments->at(0); + codeGen->visit(index); + builder->endOfStatement(true); + + msgStr = Util::printf_format("Counter: updating %s, id=%%u, packets=1, bytes=%%u", + instanceName.c_str()); + varStr = Util::printf_format("%s", program->lengthVar.c_str()); + builder->target->emitTraceMessage(builder, msgStr.c_str(), 2, keyName.c_str(), varStr.c_str()); + + builder->emitIndent(); + builder->target->emitTableLookup(builder, dataMapName, keyName, valueName); + builder->endOfStatement(true); + + emitCounterUpdate(builder, valueName, keyName); + + msgStr = Util::printf_format("Counter: %s updated", instanceName.c_str()); + builder->target->emitTraceMessage(builder, msgStr.c_str()); +} + +void EBPFCounterPSA::emitCounterUpdate(CodeBuilder* builder, const cstring target, + const cstring keyName) { + cstring varStr; + cstring initValueName = program->refMap->newName("init_val"); + + cstring targetWAccess = target + "->"; + + builder->emitIndent(); + builder->appendFormat("if (%s != NULL) ", target.c_str()); + builder->blockStart(); + + if (type == CounterType::BYTES || type == CounterType::PACKETS_AND_BYTES) { + builder->emitIndent(); + builder->appendFormat("__sync_fetch_and_add(&(%sbytes), %s)", + targetWAccess.c_str(), program->lengthVar); + builder->endOfStatement(true); + + varStr = Util::printf_format("%sbytes", targetWAccess.c_str()); + builder->target->emitTraceMessage(builder, "Counter: now bytes=%u", 1, varStr.c_str()); + } + if (type == CounterType::PACKETS || type == CounterType::PACKETS_AND_BYTES) { + builder->emitIndent(); + builder->appendFormat("__sync_fetch_and_add(&(%spackets), 1)", targetWAccess.c_str()); + builder->endOfStatement(true); + + varStr = Util::printf_format("%spackets", targetWAccess.c_str()); + builder->target->emitTraceMessage(builder, "Counter: now packets=%u", 1, varStr.c_str()); + } + + builder->blockEnd(false); + builder->append(" else "); + builder->blockStart(); + + if (isHash) { + builder->target->emitTraceMessage(builder, + "Counter: data not found, adding new instance"); + builder->emitIndent(); + builder->appendFormat("%s %s = ", valueTypeName.c_str(), target.c_str()); + emitCounterInitializer(builder); + builder->endOfStatement(true); + + builder->emitIndent(); + builder->target->emitTableUpdate(builder, dataMapName, keyName, initValueName); + builder->newline(); + } else { + builder->target->emitTraceMessage(builder, "Counter: instance not found"); + } + + builder->blockEnd(true); +} + +void EBPFCounterPSA::emitCounterInitializer(CodeBuilder* builder) { + builder->blockStart(); + if (type == CounterType::BYTES || type == CounterType::PACKETS_AND_BYTES) { + builder->emitIndent(); + builder->appendFormat(".bytes = %s,", program->lengthVar.c_str()); + builder->newline(); + } + if (type == CounterType::PACKETS || type == CounterType::PACKETS_AND_BYTES) { + builder->emitIndent(); + builder->appendFormat(".packets = 1,"); + builder->newline(); + } + builder->blockEnd(false); +} + +} diff --git a/backends/ebpf/psa/externs/ebpfPsaCounter.h b/backends/ebpf/psa/externs/ebpfPsaCounter.h new file mode 100644 index 00000000000..a5441cc9cee --- /dev/null +++ b/backends/ebpf/psa/externs/ebpfPsaCounter.h @@ -0,0 +1,58 @@ +/* +Copyright 2022-present Orange +Copyright 2022-present Open Networking Foundation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#ifndef BACKENDS_EBPF_PSA_EXTERNS_EBPFPSACOUNTER_H_ +#define BACKENDS_EBPF_PSA_EXTERNS_EBPFPSACOUNTER_H_ + +#include "backends/ebpf/ebpfTable.h" + +namespace EBPF { + +class ControlBodyTranslatorPSA; + +class EBPFCounterPSA : public EBPFCounterTable { + protected: + EBPFType* dataplaneWidthType; + EBPFType* indexWidthType; + + public: + enum CounterType { + PACKETS, + BYTES, + PACKETS_AND_BYTES + }; + CounterType type; + + EBPFCounterPSA(const EBPFProgram* program, const IR::Declaration_Instance* di, + cstring name, CodeGenInspector* codeGen); + + static CounterType toCounterType(int type); + + void emitTypes(CodeBuilder* builder) override; + virtual void emitKeyType(CodeBuilder* builder); + virtual void emitValueType(CodeBuilder* builder); + + void emitMethodInvocation(CodeBuilder* builder, const P4::ExternMethod* method, + CodeGenInspector* codeGen); + virtual void emitCount(CodeBuilder* builder, const IR::MethodCallExpression *expression, + CodeGenInspector* codeGen); + virtual void emitCounterUpdate(CodeBuilder* builder, cstring target, cstring keyName); + virtual void emitCounterInitializer(CodeBuilder* builder); +}; + +} // namespace EBPF + +#endif /* BACKENDS_EBPF_PSA_EXTERNS_EBPFPSACOUNTER_H_ */ diff --git a/backends/ebpf/runtime/kernel.mk b/backends/ebpf/runtime/kernel.mk index 8d215743784..36adeba175e 100644 --- a/backends/ebpf/runtime/kernel.mk +++ b/backends/ebpf/runtime/kernel.mk @@ -22,7 +22,7 @@ TARGET=kernel # Extra arguments for the compiler ARGS= # Extra arguments for the P4 compiler -P4ARGS= +P4ARGS=--Werror # If needed, bpf target files can be hardcoded here # This can be any file of type ".c", ".bc" or, ".o" @@ -65,7 +65,7 @@ $(BPFNAME).c: $(P4FILE) echo "*** ERROR: Cannot find p4c-ebpf"; \ exit 1;\ fi; - $(P4C) --Werror $(P4INCLUDE) --target $(TARGET) -o $@ $< $(P4ARGS) $(P4ARGS_TARGET); + $(P4C) $(P4INCLUDE) --target $(TARGET) -o $@ $< $(P4ARGS) $(P4ARGS_TARGET); # Compile the C code with the clang llvm compiler $(BPFNAME).bc: %.bc : %.c diff --git a/backends/ebpf/target.cpp b/backends/ebpf/target.cpp index 2f301273351..ac10dd31595 100644 --- a/backends/ebpf/target.cpp +++ b/backends/ebpf/target.cpp @@ -79,17 +79,17 @@ void KernelSamplesTarget::emitTableDecl(Util::SourceCodeBuilder* builder, if (keyType != "u32" && (tableKind == TablePerCPUArray || tableKind == TableArray)) { // it's more safe to overwrite user-provided key type, // as array map must have u32 key type. - keyType = "u32"; ::warning(ErrorType::WARN_INVALID, "Invalid key type (%1%) for table kind %2%, replacing with u32", keyType, kind); - } else if (tableKind == TableProgArray && (keyType != "u32" || valueType != "u32")) { keyType = "u32"; - valueType = "u32"; + } else if (tableKind == TableProgArray && (keyType != "u32" || valueType != "u32")) { ::warning(ErrorType::WARN_INVALID, "Invalid key type (%1%) or value type (%2%) for table kind %3%, " "replacing with u32", keyType, valueType, kind); + keyType = "u32"; + valueType = "u32"; } if (tableKind == TableLPMTrie) { diff --git a/backends/ebpf/tests/p4testdata/counters.p4 b/backends/ebpf/tests/p4testdata/counters.p4 new file mode 100644 index 00000000000..27c04fd8a31 --- /dev/null +++ b/backends/ebpf/tests/p4testdata/counters.p4 @@ -0,0 +1,138 @@ +/* +Copyright 2022-present Orange +Copyright 2022-present Open Networking Foundation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include +#include +#include "common_headers.p4" + +struct metadata { +} + +struct headers { + ethernet_t ethernet; +} + +parser IngressParserImpl( + packet_in buffer, + out headers parsed_hdr, + inout metadata user_meta, + in psa_ingress_parser_input_metadata_t istd, + in empty_t resubmit_meta, + in empty_t recirculate_meta) +{ + state start { + buffer.extract(parsed_hdr.ethernet); + transition accept; + } +} + + +control ingress(inout headers hdr, + inout metadata user_meta, + in psa_ingress_input_metadata_t istd, + inout psa_ingress_output_metadata_t ostd) +{ + Counter, bit<32>>(1024, PSA_CounterType_t.BYTES) test1_cnt; + Counter, bit<32>>(1024, PSA_CounterType_t.PACKETS) test2_cnt; + Counter, bit<32>>(1024, PSA_CounterType_t.PACKETS_AND_BYTES) test3_cnt; + Counter, bit<32>>(1024, PSA_CounterType_t.PACKETS_AND_BYTES) action_cnt; + + action do_forward(PortId_t egress_port) { + action_cnt.count((bit<32>)egress_port); + send_to_port(ostd, egress_port); + } + + table tbl_fwd { + key = { + istd.ingress_port : exact; + } + actions = { do_forward; NoAction; } + default_action = do_forward((PortId_t) 5); + size = 100; + } + + apply { + tbl_fwd.apply(); + + test1_cnt.count(hdr.ethernet.srcAddr[31:0]); + test2_cnt.count(hdr.ethernet.srcAddr[31:0]); + test3_cnt.count(hdr.ethernet.srcAddr[31:0]); + } +} + +parser EgressParserImpl( + packet_in buffer, + out headers parsed_hdr, + inout metadata user_meta, + in psa_egress_parser_input_metadata_t istd, + in metadata normal_meta, + in empty_t clone_i2e_meta, + in empty_t clone_e2e_meta) +{ + state start { + buffer.extract(parsed_hdr.ethernet); + transition accept; + } +} + +control egress(inout headers hdr, + inout metadata user_meta, + in psa_egress_input_metadata_t istd, + inout psa_egress_output_metadata_t ostd) +{ + apply { + ostd.drop = false; + } +} + +control IngressDeparserImpl( + packet_out packet, + out empty_t clone_i2e_meta, + out empty_t resubmit_meta, + out metadata normal_meta, + inout headers hdr, + in metadata meta, + in psa_ingress_output_metadata_t istd) +{ + apply { + packet.emit(hdr.ethernet); + } +} + +control EgressDeparserImpl( + packet_out packet, + out empty_t clone_e2e_meta, + out empty_t recirculate_meta, + inout headers hdr, + in metadata meta, + in psa_egress_output_metadata_t istd, + in psa_egress_deparser_input_metadata_t edstd) +{ + apply { + packet.emit(hdr.ethernet); + } +} + +IngressPipeline(IngressParserImpl(), + ingress(), + IngressDeparserImpl()) ip; + +EgressPipeline(EgressParserImpl(), + egress(), + EgressDeparserImpl()) ep; + +PSA_Switch(ip, PacketReplicationEngine(), ep, BufferingQueueingEngine()) main; diff --git a/backends/ebpf/tests/ptf/common.py b/backends/ebpf/tests/ptf/common.py index 819e6b11cf2..24e0959650a 100644 --- a/backends/ebpf/tests/ptf/common.py +++ b/backends/ebpf/tests/ptf/common.py @@ -225,3 +225,39 @@ def table_delete(self, table, keys=None): for k in keys: cmd = cmd + "{} ".format(k) self.exec_ns_cmd(cmd, "Table delete failed") + + def counter_get(self, name, keys=None): + key_str = "" + if keys: + key_str = key_str + "key" + for k in keys: + key_str = key_str + " {}".format(k) + cmd = "psabpf-ctl counter get pipe {} {} {}".format(TEST_PIPELINE_ID, name, key_str) + _, stdout, _ = self.exec_ns_cmd(cmd, "Counter get failed") + return json.loads(stdout)['Counter'][name] + + def counter_verify(self, name, keys, bytes=None, packets=None): + counter = self.counter_get(name, keys=keys) + expected_type = "" + if packets: + expected_type = "PACKETS" + if bytes: + if packets: + expected_type = expected_type + "_AND_" + expected_type = expected_type + "BYTES" + counter_type = counter["type"] + if expected_type != counter_type: + self.fail("Invalid counter type, expected: \"{}\", got \"{}\"".format(expected_type, counter_type)) + + entries = counter["entries"] + if len(entries) != 1: + self.fail("expected one Counter entry") + entry = entries[0] + if bytes: + counter_bytes = int(entry["value"]["bytes"], 0) + if counter_bytes != bytes: + self.fail("Invalid counter bytes, expected {}, got {}".format(bytes, counter_bytes)) + if packets: + counter_packets = int(entry["value"]["packets"], 0) + if counter_packets != packets: + self.fail("Invalid counter packets, expected {}, got {}".format(packets, counter_packets)) diff --git a/backends/ebpf/tests/ptf/test.py b/backends/ebpf/tests/ptf/test.py index 68dfc5b696a..25c6695511c 100644 --- a/backends/ebpf/tests/ptf/test.py +++ b/backends/ebpf/tests/ptf/test.py @@ -344,3 +344,30 @@ def runTest(self): # check if MPLS is skipped in parser testutils.send_packet(self, PORT0, pkt) testutils.verify_packet(self, exp_pkt, PORT1) + + +class CountersPSATest(P4EbpfTest): + p4_file_path = "p4testdata/counters.p4" + + def runTest(self): + pkt = testutils.simple_ip_packet(eth_dst='00:11:22:33:44:55', + eth_src='00:AA:00:00:00:01', + pktlen=100) + testutils.send_packet(self, PORT0, pkt) + testutils.verify_packet_any_port(self, pkt, ALL_PORTS) + + self.counter_verify(name="ingress_test1_cnt", keys=[1], bytes=100) + self.counter_verify(name="ingress_test2_cnt", keys=[1], packets=1) + self.counter_verify(name="ingress_test3_cnt", keys=[1], bytes=100, packets=1) + self.counter_verify(name="ingress_action_cnt", keys=[5], bytes=100, packets=1) + + pkt = testutils.simple_ip_packet(eth_dst='00:11:22:33:44:55', + eth_src='00:AA:00:00:01:FE', + pktlen=199) + testutils.send_packet(self, PORT0, pkt) + testutils.verify_packet_any_port(self, pkt, ALL_PORTS) + + self.counter_verify(name="ingress_test1_cnt", keys=[0x1fe], bytes=199) + self.counter_verify(name="ingress_test2_cnt", keys=[0x1fe], packets=1) + self.counter_verify(name="ingress_test3_cnt", keys=[0x1fe], bytes=199, packets=1) + self.counter_verify(name="ingress_action_cnt", keys=[5], bytes=299, packets=2)