Linux Audio

Check our new training course

Loading...
   1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2023 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>

/* Access Control List (ACL) structure:
 *
 * There are multiple groups of registers involved in ACL configuration:
 *
 * - Matching Rules: These registers define the criteria for matching incoming
 *   packets based on their header information (Layer 2 MAC, Layer 3 IP, or
 *   Layer 4 TCP/UDP). Different register settings are used depending on the
 *   matching rule mode (MD) and the Enable (ENB) settings.
 *
 * - Action Rules: These registers define how the ACL should modify the packet's
 *   priority, VLAN tag priority, and forwarding map once a matching rule has
 *   been triggered. The settings vary depending on whether the matching rule is
 *   in Count Mode (MD = 01 and ENB = 00) or not.
 *
 * - Processing Rules: These registers control the overall behavior of the ACL,
 *   such as selecting which matching rule to apply first, enabling/disabling
 *   specific rules, or specifying actions for matched packets.
 *
 * ACL Structure:
 *                             +----------------------+
 * +----------------------+    |    (optional)        |
 * |    Matching Rules    |    |    Matching Rules    |
 * |    (Layer 2, 3, 4)   |    |    (Layer 2, 3, 4)   |
 * +----------------------+    +----------------------+
 *             |                            |
 *             \___________________________/
 *                          v
 *               +----------------------+
 *               |   Processing Rules   |
 *               | (action idx,         |
 *               | matching rule set)   |
 *               +----------------------+
 *                          |
 *                          v
 *               +----------------------+
 *               |    Action Rules      |
 *               | (Modify Priority,    |
 *               |  Forwarding Map,     |
 *               |  VLAN tag, etc)      |
 *               +----------------------+
 */

#include <linux/bitops.h>

#include "ksz9477.h"
#include "ksz9477_reg.h"
#include "ksz_common.h"

#define KSZ9477_PORT_ACL_0		0x600

enum ksz9477_acl_port_access {
	KSZ9477_ACL_PORT_ACCESS_0  = 0x00,
	KSZ9477_ACL_PORT_ACCESS_1  = 0x01,
	KSZ9477_ACL_PORT_ACCESS_2  = 0x02,
	KSZ9477_ACL_PORT_ACCESS_3  = 0x03,
	KSZ9477_ACL_PORT_ACCESS_4  = 0x04,
	KSZ9477_ACL_PORT_ACCESS_5  = 0x05,
	KSZ9477_ACL_PORT_ACCESS_6  = 0x06,
	KSZ9477_ACL_PORT_ACCESS_7  = 0x07,
	KSZ9477_ACL_PORT_ACCESS_8  = 0x08,
	KSZ9477_ACL_PORT_ACCESS_9  = 0x09,
	KSZ9477_ACL_PORT_ACCESS_A  = 0x0A,
	KSZ9477_ACL_PORT_ACCESS_B  = 0x0B,
	KSZ9477_ACL_PORT_ACCESS_C  = 0x0C,
	KSZ9477_ACL_PORT_ACCESS_D  = 0x0D,
	KSZ9477_ACL_PORT_ACCESS_E  = 0x0E,
	KSZ9477_ACL_PORT_ACCESS_F  = 0x0F,
	KSZ9477_ACL_PORT_ACCESS_10 = 0x10,
	KSZ9477_ACL_PORT_ACCESS_11 = 0x11
};

#define KSZ9477_ACL_MD_MASK			GENMASK(5, 4)
#define KSZ9477_ACL_MD_DISABLE			0
#define KSZ9477_ACL_MD_L2_MAC			1
#define KSZ9477_ACL_MD_L3_IP			2
#define KSZ9477_ACL_MD_L4_TCP_UDP		3

#define KSZ9477_ACL_ENB_MASK			GENMASK(3, 2)
#define KSZ9477_ACL_ENB_L2_COUNTER		0
#define KSZ9477_ACL_ENB_L2_TYPE			1
#define KSZ9477_ACL_ENB_L2_MAC			2
#define KSZ9477_ACL_ENB_L2_MAC_TYPE		3

/* only IPv4 src or dst can be used with mask */
#define KSZ9477_ACL_ENB_L3_IPV4_ADDR_MASK	1
/* only IPv4 src and dst can be used without mask */
#define KSZ9477_ACL_ENB_L3_IPV4_ADDR_SRC_DST	2

#define KSZ9477_ACL_ENB_L4_IP_PROTO	        0
#define KSZ9477_ACL_ENB_L4_TCP_SRC_DST_PORT	1
#define KSZ9477_ACL_ENB_L4_UDP_SRC_DST_PORT	2
#define KSZ9477_ACL_ENB_L4_TCP_SEQ_NUMBER	3

#define KSZ9477_ACL_SD_SRC			BIT(1)
#define KSZ9477_ACL_SD_DST			0
#define KSZ9477_ACL_EQ_EQUAL			BIT(0)
#define KSZ9477_ACL_EQ_NOT_EQUAL		0

#define KSZ9477_ACL_PM_M			GENMASK(7, 6)
#define KSZ9477_ACL_PM_DISABLE			0
#define KSZ9477_ACL_PM_HIGHER			1
#define KSZ9477_ACL_PM_LOWER			2
#define KSZ9477_ACL_PM_REPLACE			3
#define KSZ9477_ACL_P_M				GENMASK(5, 3)

#define KSZ9477_PORT_ACL_CTRL_0			0x0612

#define KSZ9477_ACL_WRITE_DONE			BIT(6)
#define KSZ9477_ACL_READ_DONE			BIT(5)
#define KSZ9477_ACL_WRITE			BIT(4)
#define KSZ9477_ACL_INDEX_M			GENMASK(3, 0)

/**
 * ksz9477_dump_acl_index - Print the ACL entry at the specified index
 *
 * @dev: Pointer to the ksz9477 device structure.
 * @acle: Pointer to the ACL entry array.
 * @index: The index of the ACL entry to print.
 *
 * This function prints the details of an ACL entry, located at a particular
 * index within the ksz9477 device's ACL table. It omits printing entries that
 * are empty.
 *
 * Return: 1 if the entry is non-empty and printed, 0 otherwise.
 */
static int ksz9477_dump_acl_index(struct ksz_device *dev,
				  struct ksz9477_acl_entry *acle, int index)
{
	bool empty = true;
	char buf[64];
	u8 *entry;
	int i;

	entry = &acle[index].entry[0];
	for (i = 0; i <= KSZ9477_ACL_PORT_ACCESS_11; i++) {
		if (entry[i])
			empty = false;

		sprintf(buf + (i * 3), "%02x ", entry[i]);
	}

	/* no need to print empty entries */
	if (empty)
		return 0;

	dev_err(dev->dev, " Entry %02d, prio: %02d : %s", index,
		acle[index].prio, buf);

	return 1;
}

/**
 * ksz9477_dump_acl - Print ACL entries
 *
 * @dev: Pointer to the device structure.
 * @acle: Pointer to the ACL entry array.
 */
static void ksz9477_dump_acl(struct ksz_device *dev,
			     struct ksz9477_acl_entry *acle)
{
	int count = 0;
	int i;

	for (i = 0; i < KSZ9477_ACL_MAX_ENTRIES; i++)
		count += ksz9477_dump_acl_index(dev, acle, i);

	if (count != KSZ9477_ACL_MAX_ENTRIES - 1)
		dev_err(dev->dev, " Empty ACL entries were skipped\n");
}

/**
 * ksz9477_acl_is_valid_matching_rule - Check if an ACL entry contains a valid
 *					matching rule.
 *
 * @entry: Pointer to ACL entry buffer
 *
 * This function checks if the given ACL entry buffer contains a valid
 * matching rule by inspecting the Mode (MD) and Enable (ENB) fields.
 *
 * Returns: True if it's a valid matching rule, false otherwise.
 */
static bool ksz9477_acl_is_valid_matching_rule(u8 *entry)
{
	u8 val1, md, enb;

	val1 = entry[KSZ9477_ACL_PORT_ACCESS_1];

	md = FIELD_GET(KSZ9477_ACL_MD_MASK, val1);
	if (md == KSZ9477_ACL_MD_DISABLE)
		return false;

	if (md == KSZ9477_ACL_MD_L2_MAC) {
		/* L2 counter is not support, so it is not valid rule for now */
		enb = FIELD_GET(KSZ9477_ACL_ENB_MASK, val1);
		if (enb == KSZ9477_ACL_ENB_L2_COUNTER)
			return false;
	}

	return true;
}

/**
 * ksz9477_acl_get_cont_entr - Get count of contiguous ACL entries and validate
 *                             the matching rules.
 * @dev: Pointer to the KSZ9477 device structure.
 * @port: Port number.
 * @index: Index of the starting ACL entry.
 *
 * Based on the KSZ9477 switch's Access Control List (ACL) system, the RuleSet
 * in an ACL entry indicates which entries contain Matching rules linked to it.
 * This RuleSet is represented by two registers: KSZ9477_ACL_PORT_ACCESS_E and
 * KSZ9477_ACL_PORT_ACCESS_F. Each bit set in these registers corresponds to
 * an entry containing a Matching rule for this RuleSet.
 *
 * For a single Matching rule linked, only one bit is set. However, when an
 * entry links multiple Matching rules, forming what's termed a 'complex rule',
 * multiple bits are set in these registers.
 *
 * This function checks that, for complex rules, the entries containing the
 * linked Matching rules are contiguous in terms of their indices. It calculates
 * and returns the number of these contiguous entries.
 *
 * Returns:
 *    - 0 if the entry is empty and can be safely overwritten
 *    - 1 if the entry represents a simple rule
 *    - The number of contiguous entries if it is the root entry of a complex
 *      rule
 *    - -ENOTEMPTY if the entry is part of a complex rule but not the root
 *      entry
 *    - -EINVAL if the validation fails
 */
static int ksz9477_acl_get_cont_entr(struct ksz_device *dev, int port,
				     int index)
{
	struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
	struct ksz9477_acl_entries *acles = &acl->acles;
	int start_idx, end_idx, contiguous_count;
	unsigned long val;
	u8 vale, valf;
	u8 *entry;
	int i;

	entry = &acles->entries[index].entry[0];
	vale = entry[KSZ9477_ACL_PORT_ACCESS_E];
	valf = entry[KSZ9477_ACL_PORT_ACCESS_F];

	val = (vale << 8) | valf;

	/* If no bits are set, return an appropriate value or error */
	if (!val) {
		if (ksz9477_acl_is_valid_matching_rule(entry)) {
			/* Looks like we are about to corrupt some complex rule.
			 * Do not print an error here, as this is a normal case
			 * when we are trying to find a free or starting entry.
			 */
			dev_dbg(dev->dev, "ACL: entry %d starting with a valid matching rule, but no bits set in RuleSet\n",
				index);
			return -ENOTEMPTY;
		}

		/* This entry does not contain a valid matching rule */
		return 0;
	}

	start_idx = find_first_bit((unsigned long *)&val, 16);
	end_idx = find_last_bit((unsigned long *)&val, 16);

	/* Calculate the contiguous count */
	contiguous_count = end_idx - start_idx + 1;

	/* Check if the number of bits set in val matches our calculated count */
	if (contiguous_count != hweight16(val)) {
		/* Probably we have a fragmented complex rule, which is not
		 * supported by this driver.
		 */
		dev_err(dev->dev, "ACL: number of bits set in RuleSet does not match calculated count\n");
		return -EINVAL;
	}

	/* loop over the contiguous entries and check for valid matching rules */
	for (i = start_idx; i <= end_idx; i++) {
		u8 *current_entry = &acles->entries[i].entry[0];

		if (!ksz9477_acl_is_valid_matching_rule(current_entry)) {
			/* we have something linked without a valid matching
			 * rule. ACL table?
			 */
			dev_err(dev->dev, "ACL: entry %d does not contain a valid matching rule\n",
				i);
			return -EINVAL;
		}

		if (i > start_idx) {
			vale = current_entry[KSZ9477_ACL_PORT_ACCESS_E];
			valf = current_entry[KSZ9477_ACL_PORT_ACCESS_F];
			/* Following entry should have empty linkage list */
			if (vale || valf) {
				dev_err(dev->dev, "ACL: entry %d has non-empty RuleSet linkage\n",
					i);
				return -EINVAL;
			}
		}
	}

	return contiguous_count;
}

/**
 * ksz9477_acl_update_linkage - Update the RuleSet linkage for an ACL entry
 *                              after a move operation.
 *
 * @dev: Pointer to the ksz_device.
 * @entry:   Pointer to the ACL entry array.
 * @old_idx: The original index of the ACL entry before moving.
 * @new_idx: The new index of the ACL entry after moving.
 *
 * This function updates the RuleSet linkage bits for an ACL entry when
 * it's moved from one position to another in the ACL table. The RuleSet
 * linkage is represented by two 8-bit registers, which are combined
 * into a 16-bit value for easier manipulation. The linkage bits are shifted
 * based on the difference between the old and new index. If any bits are lost
 * during the shift operation, an error is returned.
 *
 * Note: Fragmentation within a RuleSet is not supported. Hence, entries must
 * be moved as complete blocks, maintaining the integrity of the RuleSet.
 *
 * Returns: 0 on success, or -EINVAL if any RuleSet linkage bits are lost
 * during the move.
 */
static int ksz9477_acl_update_linkage(struct ksz_device *dev, u8 *entry,
				      u16 old_idx, u16 new_idx)
{
	unsigned int original_bit_count;
	unsigned long rule_linkage;
	u8 vale, valf, val0;
	int shift;

	val0 = entry[KSZ9477_ACL_PORT_ACCESS_0];
	vale = entry[KSZ9477_ACL_PORT_ACCESS_E];
	valf = entry[KSZ9477_ACL_PORT_ACCESS_F];

	/* Combine the two u8 values into one u16 for easier manipulation */
	rule_linkage = (vale << 8) | valf;
	original_bit_count = hweight16(rule_linkage);

	/* Even if HW is able to handle fragmented RuleSet, we don't support it.
	 * RuleSet is filled only for the first entry of the set.
	 */
	if (!rule_linkage)
		return 0;

	if (val0 != old_idx) {
		dev_err(dev->dev, "ACL: entry %d has unexpected ActionRule linkage: %d\n",
			old_idx, val0);
		return -EINVAL;
	}

	val0 = new_idx;

	/* Calculate the number of positions to shift */
	shift = new_idx - old_idx;

	/* Shift the RuleSet */
	if (shift > 0)
		rule_linkage <<= shift;
	else
		rule_linkage >>= -shift;

	/* Check that no bits were lost in the process */
	if (original_bit_count != hweight16(rule_linkage)) {
		dev_err(dev->dev, "ACL RuleSet linkage bits lost during move\n");
		return -EINVAL;
	}

	entry[KSZ9477_ACL_PORT_ACCESS_0] = val0;

	/* Update the RuleSet bitfields in the entry */
	entry[KSZ9477_ACL_PORT_ACCESS_E] = (rule_linkage >> 8) & 0xFF;
	entry[KSZ9477_ACL_PORT_ACCESS_F] = rule_linkage & 0xFF;

	return 0;
}

/**
 * ksz9477_validate_and_get_src_count - Validate source and destination indices
 *					and determine the source entry count.
 * @dev: Pointer to the KSZ device structure.
 * @port: Port number on the KSZ device where the ACL entries reside.
 * @src_idx: Index of the starting ACL entry that needs to be validated.
 * @dst_idx: Index of the destination where the source entries are intended to
 *	     be moved.
 * @src_count: Pointer to the variable that will hold the number of contiguous
 *	     source entries if the validation passes.
 * @dst_count: Pointer to the variable that will hold the number of contiguous
 *	     destination entries if the validation passes.
 *
 * This function performs validation on the source and destination indices
 * provided for ACL entries. It checks if the indices are within the valid
 * range, and if the source entries are contiguous. Additionally, the function
 * ensures that there's adequate space at the destination for the source entries
 * and that the destination index isn't in the middle of a RuleSet. If all
 * validations pass, the function returns the number of contiguous source and
 * destination entries.
 *
 * Return: 0 on success, otherwise returns a negative error code if any
 * validation check fails.
 */
static int ksz9477_validate_and_get_src_count(struct ksz_device *dev, int port,
					      int src_idx, int dst_idx,
					      int *src_count, int *dst_count)
{
	int ret;

	if (src_idx >= KSZ9477_ACL_MAX_ENTRIES ||
	    dst_idx >= KSZ9477_ACL_MAX_ENTRIES) {
		dev_err(dev->dev, "ACL: invalid entry index\n");
		return -EINVAL;
	}

	/* Validate if the source entries are contiguous */
	ret = ksz9477_acl_get_cont_entr(dev, port, src_idx);
	if (ret < 0)
		return ret;
	*src_count = ret;

	if (!*src_count) {
		dev_err(dev->dev, "ACL: source entry is empty\n");
		return -EINVAL;
	}

	if (dst_idx + *src_count >= KSZ9477_ACL_MAX_ENTRIES) {
		dev_err(dev->dev, "ACL: Not enough space at the destination. Move operation will fail.\n");
		return -EINVAL;
	}

	/* Validate if the destination entry is empty or not in the middle of
	 * a RuleSet.
	 */
	ret = ksz9477_acl_get_cont_entr(dev, port, dst_idx);
	if (ret < 0)
		return ret;
	*dst_count = ret;

	return 0;
}

/**
 * ksz9477_move_entries_downwards - Move a range of ACL entries downwards in
 *				    the list.
 * @dev: Pointer to the KSZ device structure.
 * @acles: Pointer to the structure encapsulating all the ACL entries.
 * @start_idx: Starting index of the entries to be relocated.
 * @num_entries_to_move: Number of consecutive entries to be relocated.
 * @end_idx: Destination index where the first entry should be situated post
 *           relocation.
 *
 * This function is responsible for rearranging a specific block of ACL entries
 * by shifting them downwards in the list based on the supplied source and
 * destination indices. It ensures that the linkage between the ACL entries is
 * maintained accurately after the relocation.
 *
 * Return: 0 on successful relocation of entries, otherwise returns a negative
 * error code.
 */
static int ksz9477_move_entries_downwards(struct ksz_device *dev,
					  struct ksz9477_acl_entries *acles,
					  u16 start_idx,
					  u16 num_entries_to_move,
					  u16 end_idx)
{
	struct ksz9477_acl_entry *e;
	int ret, i;

	for (i = start_idx; i < end_idx; i++) {
		e = &acles->entries[i];
		*e = acles->entries[i + num_entries_to_move];

		ret = ksz9477_acl_update_linkage(dev, &e->entry[0],
						 i + num_entries_to_move, i);
		if (ret < 0)
			return ret;
	}

	return 0;
}

/**
 * ksz9477_move_entries_upwards - Move a range of ACL entries upwards in the
 *				  list.
 * @dev: Pointer to the KSZ device structure.
 * @acles: Pointer to the structure holding all the ACL entries.
 * @start_idx: The starting index of the entries to be moved.
 * @num_entries_to_move: Number of contiguous entries to be moved.
 * @target_idx: The destination index where the first entry should be placed
 *		after moving.
 *
 * This function rearranges a chunk of ACL entries by moving them upwards
 * in the list based on the given source and destination indices. The reordering
 * process preserves the linkage between entries by updating it accordingly.
 *
 * Return: 0 if the entries were successfully moved, otherwise a negative error
 * code.
 */
static int ksz9477_move_entries_upwards(struct ksz_device *dev,
					struct ksz9477_acl_entries *acles,
					u16 start_idx, u16 num_entries_to_move,
					u16 target_idx)
{
	struct ksz9477_acl_entry *e;
	int ret, i, b;

	for (i = start_idx; i > target_idx; i--) {
		b = i + num_entries_to_move - 1;

		e = &acles->entries[b];
		*e = acles->entries[i - 1];

		ret = ksz9477_acl_update_linkage(dev, &e->entry[0], i - 1, b);
		if (ret < 0)
			return ret;
	}

	return 0;
}

/**
 * ksz9477_acl_move_entries - Move a block of contiguous ACL entries from a
 *			      source to a destination index.
 * @dev: Pointer to the KSZ9477 device structure.
 * @port: Port number.
 * @src_idx: Index of the starting source ACL entry.
 * @dst_idx: Index of the starting destination ACL entry.
 *
 * This function aims to move a block of contiguous ACL entries from the source
 * index to the destination index while ensuring the integrity and validity of
 * the ACL table.
 *
 * In case of any errors during the adjustments or copying, the function will
 * restore the ACL entries to their original state from the backup.
 *
 * Return: 0 if the move operation is successful. Returns -EINVAL for validation
 * errors or other error codes based on specific failure conditions.
 */
static int ksz9477_acl_move_entries(struct ksz_device *dev, int port,
				    u16 src_idx, u16 dst_idx)
{
	struct ksz9477_acl_entry buffer[KSZ9477_ACL_MAX_ENTRIES];
	struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
	struct ksz9477_acl_entries *acles = &acl->acles;
	int src_count, ret, dst_count;

	/* Nothing to do */
	if (src_idx == dst_idx)
		return 0;

	ret = ksz9477_validate_and_get_src_count(dev, port, src_idx, dst_idx,
						 &src_count, &dst_count);
	if (ret)
		return ret;

	/* In case dst_index is greater than src_index, we need to adjust the
	 * destination index to account for the entries that will be moved
	 * downwards and the size of the entry located at dst_idx.
	 */
	if (dst_idx > src_idx)
		dst_idx = dst_idx + dst_count - src_count;

	/* Copy source block to buffer and update its linkage */
	for (int i = 0; i < src_count; i++) {
		buffer[i] = acles->entries[src_idx + i];
		ret = ksz9477_acl_update_linkage(dev, &buffer[i].entry[0],
						 src_idx + i, dst_idx + i);
		if (ret < 0)
			return ret;
	}

	/* Adjust other entries and their linkage based on destination */
	if (dst_idx > src_idx) {
		ret = ksz9477_move_entries_downwards(dev, acles, src_idx,
						     src_count, dst_idx);
	} else {
		ret = ksz9477_move_entries_upwards(dev, acles, src_idx,
						   src_count, dst_idx);
	}
	if (ret < 0)
		return ret;

	/* Copy buffer to destination block */
	for (int i = 0; i < src_count; i++)
		acles->entries[dst_idx + i] = buffer[i];

	return 0;
}

/**
 * ksz9477_get_next_block_start - Identify the starting index of the next ACL
 *				  block.
 * @dev: Pointer to the device structure.
 * @port: The port number on which the ACL entries are being checked.
 * @start: The starting index from which the search begins.
 *
 * This function looks for the next valid ACL block starting from the provided
 * 'start' index and returns the beginning index of that block. If the block is
 * invalid or if it reaches the end of the ACL entries without finding another
 * block, it returns the maximum ACL entries count.
 *
 * Returns:
 *  - The starting index of the next valid ACL block.
 *  - KSZ9477_ACL_MAX_ENTRIES if no other valid blocks are found after 'start'.
 *  - A negative error code if an error occurs while checking.
 */
static int ksz9477_get_next_block_start(struct ksz_device *dev, int port,
					int start)
{
	int block_size;

	for (int i = start; i < KSZ9477_ACL_MAX_ENTRIES;) {
		block_size = ksz9477_acl_get_cont_entr(dev, port, i);
		if (block_size < 0 && block_size != -ENOTEMPTY)
			return block_size;

		if (block_size > 0)
			return i;

		i++;
	}
	return KSZ9477_ACL_MAX_ENTRIES;
}

/**
 * ksz9477_swap_acl_blocks - Swap two ACL blocks
 * @dev: Pointer to the device structure.
 * @port: The port number on which the ACL blocks are to be swapped.
 * @i: The starting index of the first ACL block.
 * @j: The starting index of the second ACL block.
 *
 * This function is used to swap two ACL blocks present at given indices. The
 * main purpose is to aid in the sorting and reordering of ACL blocks based on
 * certain criteria, e.g., priority. It checks the validity of the block at
 * index 'i', ensuring it's not an empty block, and then proceeds to swap it
 * with the block at index 'j'.
 *
 * Returns:
 *  - 0 on successful swapping of blocks.
 *  - -EINVAL if the block at index 'i' is empty.
 *  - A negative error code if any other error occurs during the swap.
 */
static int ksz9477_swap_acl_blocks(struct ksz_device *dev, int port, int i,
				   int j)
{
	int ret, current_block_size;

	current_block_size = ksz9477_acl_get_cont_entr(dev, port, i);
	if (current_block_size < 0)
		return current_block_size;

	if (!current_block_size) {
		dev_err(dev->dev, "ACL: swapping empty entry %d\n", i);
		return -EINVAL;
	}

	ret = ksz9477_acl_move_entries(dev, port, i, j);
	if (ret)
		return ret;

	ret = ksz9477_acl_move_entries(dev, port, j - current_block_size, i);
	if (ret)
		return ret;

	return 0;
}

/**
 * ksz9477_sort_acl_entr_no_back - Sort ACL entries for a given port based on
 *			           priority without backing up entries.
 * @dev: Pointer to the device structure.
 * @port: The port number whose ACL entries need to be sorted.
 *
 * This function sorts ACL entries of the specified port using a variant of the
 * bubble sort algorithm. It operates on blocks of ACL entries rather than
 * individual entries. Each block's starting point is identified and then
 * compared with subsequent blocks based on their priority. If the current
 * block has a lower priority than the subsequent block, the two blocks are
 * swapped.
 *
 * This is done in order to maintain an organized order of ACL entries based on
 * priority, ensuring efficient and predictable ACL rule application.
 *
 * Returns:
 *  - 0 on successful sorting of entries.
 *  - A negative error code if any issue arises during sorting, e.g.,
 *    if the function is unable to get the next block start.
 */
static int ksz9477_sort_acl_entr_no_back(struct ksz_device *dev, int port)
{
	struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
	struct ksz9477_acl_entries *acles = &acl->acles;
	struct ksz9477_acl_entry *curr, *next;
	int i, j, ret;

	/* Bubble sort */
	for (i = 0; i < KSZ9477_ACL_MAX_ENTRIES;) {
		curr = &acles->entries[i];

		j = ksz9477_get_next_block_start(dev, port, i + 1);
		if (j < 0)
			return j;

		while (j < KSZ9477_ACL_MAX_ENTRIES) {
			next = &acles->entries[j];

			if (curr->prio > next->prio) {
				ret = ksz9477_swap_acl_blocks(dev, port, i, j);
				if (ret)
					return ret;
			}

			j = ksz9477_get_next_block_start(dev, port, j + 1);
			if (j < 0)
				return j;
		}

		i = ksz9477_get_next_block_start(dev, port, i + 1);
		if (i < 0)
			return i;
	}

	return 0;
}

/**
 * ksz9477_sort_acl_entries - Sort the ACL entries for a given port.
 * @dev: Pointer to the KSZ device.
 * @port: Port number.
 *
 * This function sorts the Access Control List (ACL) entries for a specified
 * port. Before sorting, a backup of the original entries is created. If the
 * sorting process fails, the function will log error messages displaying both
 * the original and attempted sorted entries, and then restore the original
 * entries from the backup.
 *
 * Return: 0 if the sorting succeeds, otherwise a negative error code.
 */
int ksz9477_sort_acl_entries(struct ksz_device *dev, int port)
{
	struct ksz9477_acl_entry backup[KSZ9477_ACL_MAX_ENTRIES];
	struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
	struct ksz9477_acl_entries *acles = &acl->acles;
	int ret;

	/* create a backup of the ACL entries, if something goes wrong
	 * we can restore the ACL entries.
	 */
	memcpy(backup, acles->entries, sizeof(backup));

	ret = ksz9477_sort_acl_entr_no_back(dev, port);
	if (ret) {
		dev_err(dev->dev, "ACL: failed to sort entries for port %d\n",
			port);
		dev_err(dev->dev, "ACL dump before sorting:\n");
		ksz9477_dump_acl(dev, backup);
		dev_err(dev->dev, "ACL dump after sorting:\n");
		ksz9477_dump_acl(dev, acles->entries);
		/* Restore the original entries */
		memcpy(acles->entries, backup, sizeof(backup));
	}

	return ret;
}

/**
 * ksz9477_acl_wait_ready - Waits for the ACL operation to complete on a given
 *			    port.
 * @dev: The ksz_device instance.
 * @port: The port number to wait for.
 *
 * This function checks if the ACL write or read operation is completed by
 * polling the specified register.
 *
 * Returns: 0 if the operation is successful, or a negative error code if an
 * error occurs.
 */
static int ksz9477_acl_wait_ready(struct ksz_device *dev, int port)
{
	unsigned int wr_mask = KSZ9477_ACL_WRITE_DONE | KSZ9477_ACL_READ_DONE;
	unsigned int val, reg;
	int ret;

	reg = dev->dev_ops->get_port_addr(port, KSZ9477_PORT_ACL_CTRL_0);

	ret = regmap_read_poll_timeout(dev->regmap[0], reg, val,
				       (val & wr_mask) == wr_mask, 1000, 10000);
	if (ret)
		dev_err(dev->dev, "Failed to read/write ACL table\n");

	return ret;
}

/**
 * ksz9477_acl_entry_write - Writes an ACL entry to a given port at the
 *			     specified index.
 * @dev: The ksz_device instance.
 * @port: The port number to write the ACL entry to.
 * @entry: A pointer to the ACL entry data.
 * @idx: The index at which to write the ACL entry.
 *
 * This function writes the provided ACL entry to the specified port at the
 * given index.
 *
 * Returns: 0 if the operation is successful, or a negative error code if an
 * error occurs.
 */
static int ksz9477_acl_entry_write(struct ksz_device *dev, int port, u8 *entry,
				   int idx)
{
	int ret, i;
	u8 val;

	for (i = 0; i < KSZ9477_ACL_ENTRY_SIZE; i++) {
		ret = ksz_pwrite8(dev, port, KSZ9477_PORT_ACL_0 + i, entry[i]);
		if (ret) {
			dev_err(dev->dev, "Failed to write ACL entry %d\n", i);
			return ret;
		}
	}

	/* write everything down */
	val = FIELD_PREP(KSZ9477_ACL_INDEX_M, idx) | KSZ9477_ACL_WRITE;
	ret = ksz_pwrite8(dev, port, KSZ9477_PORT_ACL_CTRL_0, val);
	if (ret)
		return ret;

	/* wait until everything is written  */
	return ksz9477_acl_wait_ready(dev, port);
}

/**
 * ksz9477_acl_port_enable - Enables ACL functionality on a given port.
 * @dev: The ksz_device instance.
 * @port: The port number on which to enable ACL functionality.
 *
 * This function enables ACL functionality on the specified port by configuring
 * the appropriate control registers. It returns 0 if the operation is
 * successful, or a negative error code if an error occurs.
 *
 * 0xn801 - KSZ9477S 5.2.8.2 Port Priority Control Register
 *        Bit 7 - Highest Priority
 *        Bit 6 - OR'ed Priority
 *        Bit 4 - MAC Address Priority Classification
 *        Bit 3 - VLAN Priority Classification
 *        Bit 2 - 802.1p Priority Classification
 *        Bit 1 - Diffserv Priority Classification
 *        Bit 0 - ACL Priority Classification
 *
 * Current driver implementation sets 802.1p priority classification by default.
 * In this function we add ACL priority classification with OR'ed priority.
 * According to testing, priority set by ACL will supersede the 802.1p priority.
 *
 * 0xn803 - KSZ9477S 5.2.8.4 Port Authentication Control Register
 *        Bit 2 - Access Control List (ACL) Enable
 *        Bits 1:0 - Authentication Mode
 *                00 = Reserved
 *                01 = Block Mode. Authentication is enabled. When ACL is
 *                     enabled, all traffic that misses the ACL rules is
 *                     blocked; otherwise ACL actions apply.
 *                10 = Pass Mode. Authentication is disabled. When ACL is
 *                     enabled, all traffic that misses the ACL rules is
 *                     forwarded; otherwise ACL actions apply.
 *                11 = Trap Mode. Authentication is enabled. All traffic is
 *                     forwarded to the host port. When ACL is enabled, all
 *                     traffic that misses the ACL rules is blocked; otherwise
 *                     ACL actions apply.
 *
 * We are using Pass Mode int this function.
 *
 * Returns: 0 if the operation is successful, or a negative error code if an
 * error occurs.
 */
static int ksz9477_acl_port_enable(struct ksz_device *dev, int port)
{
	int ret;

	ret = ksz_prmw8(dev, port, P_PRIO_CTRL, 0, PORT_ACL_PRIO_ENABLE |
			PORT_OR_PRIO);
	if (ret)
		return ret;

	return ksz_pwrite8(dev, port, REG_PORT_MRI_AUTHEN_CTRL,
			   PORT_ACL_ENABLE |
			   FIELD_PREP(PORT_AUTHEN_MODE, PORT_AUTHEN_PASS));
}

/**
 * ksz9477_acl_port_disable - Disables ACL functionality on a given port.
 * @dev: The ksz_device instance.
 * @port: The port number on which to disable ACL functionality.
 *
 * This function disables ACL functionality on the specified port by writing a
 * value of 0 to the REG_PORT_MRI_AUTHEN_CTRL control register and remove
 * PORT_ACL_PRIO_ENABLE bit from P_PRIO_CTRL register.
 *
 * Returns: 0 if the operation is successful, or a negative error code if an
 * error occurs.
 */
static int ksz9477_acl_port_disable(struct ksz_device *dev, int port)
{
	int ret;

	ret = ksz_prmw8(dev, port, P_PRIO_CTRL, PORT_ACL_PRIO_ENABLE, 0);
	if (ret)
		return ret;

	return ksz_pwrite8(dev, port, REG_PORT_MRI_AUTHEN_CTRL, 0);
}

/**
 * ksz9477_acl_write_list - Write a list of ACL entries to a given port.
 * @dev: The ksz_device instance.
 * @port: The port number on which to write ACL entries.
 *
 * This function enables ACL functionality on the specified port, writes a list
 * of ACL entries to the port, and disables ACL functionality if there are no
 * entries.
 *
 * Returns: 0 if the operation is successful, or a negative error code if an
 * error occurs.
 */
int ksz9477_acl_write_list(struct ksz_device *dev, int port)
{
	struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
	struct ksz9477_acl_entries *acles = &acl->acles;
	int ret, i;

	/* ACL should be enabled before writing entries */
	ret = ksz9477_acl_port_enable(dev, port);
	if (ret)
		return ret;

	/* write all entries */
	for (i = 0; i < ARRAY_SIZE(acles->entries); i++) {
		u8 *entry = acles->entries[i].entry;

		/* Check if entry was removed and should be zeroed.
		 * If last fields of the entry are not zero, it means
		 * it is removed locally but currently not synced with the HW.
		 * So, we will write it down to the HW to remove it.
		 */
		if (i >= acles->entries_count &&
		    entry[KSZ9477_ACL_PORT_ACCESS_10] == 0 &&
		    entry[KSZ9477_ACL_PORT_ACCESS_11] == 0)
			continue;

		ret = ksz9477_acl_entry_write(dev, port, entry, i);
		if (ret)
			return ret;

		/* now removed entry is clean on HW side, so it can
		 * in the cache too
		 */
		if (i >= acles->entries_count &&
		    entry[KSZ9477_ACL_PORT_ACCESS_10] != 0 &&
		    entry[KSZ9477_ACL_PORT_ACCESS_11] != 0) {
			entry[KSZ9477_ACL_PORT_ACCESS_10] = 0;
			entry[KSZ9477_ACL_PORT_ACCESS_11] = 0;
		}
	}

	if (!acles->entries_count)
		return ksz9477_acl_port_disable(dev, port);

	return 0;
}

/**
 * ksz9477_acl_remove_entries - Remove ACL entries with a given cookie from a
 *                              specified ksz9477_acl_entries structure.
 * @dev: The ksz_device instance.
 * @port: The port number on which to remove ACL entries.
 * @acles: The ksz9477_acl_entries instance.
 * @cookie: The cookie value to match for entry removal.
 *
 * This function iterates through the entries array, removing any entries with
 * a matching cookie value. The remaining entries are then shifted down to fill
 * the gap.
 */
void ksz9477_acl_remove_entries(struct ksz_device *dev, int port,
				struct ksz9477_acl_entries *acles,
				unsigned long cookie)
{
	int entries_count = acles->entries_count;
	int ret, i, src_count;
	int src_idx = -1;

	if (!entries_count)
		return;

	/* Search for the first position with the cookie */
	for (i = 0; i < entries_count; i++) {
		if (acles->entries[i].cookie == cookie) {
			src_idx = i;
			break;
		}
	}

	/* No entries with the matching cookie found */
	if (src_idx == -1)
		return;

	/* Get the size of the cookie entry. We may have complex entries. */
	src_count = ksz9477_acl_get_cont_entr(dev, port, src_idx);
	if (src_count <= 0)
		return;

	/* Move all entries down to overwrite removed entry with the cookie */
	ret = ksz9477_move_entries_downwards(dev, acles, src_idx,
					     src_count,
					     entries_count - src_count);
	if (ret) {
		dev_err(dev->dev, "Failed to move ACL entries down\n");
		return;
	}

	/* Overwrite new empty places at the end of the list with zeros to make
	 * sure not unexpected things will happen or no unexplored quirks will
	 * come out.
	 */
	for (i = entries_count - src_count; i < entries_count; i++) {
		struct ksz9477_acl_entry *entry = &acles->entries[i];

		memset(entry, 0, sizeof(*entry));

		/* Set all access bits to be able to write zeroed entry to HW */
		entry->entry[KSZ9477_ACL_PORT_ACCESS_10] = 0xff;
		entry->entry[KSZ9477_ACL_PORT_ACCESS_11] = 0xff;
	}

	/* Adjust the total entries count */
	acles->entries_count -= src_count;
}

/**
 * ksz9477_port_acl_init - Initialize the ACL for a specified port on a ksz
 *			   device.
 * @dev: The ksz_device instance.
 * @port: The port number to initialize the ACL for.
 *
 * This function allocates memory for an acl structure, associates it with the
 * specified port, and initializes the ACL entries to a default state. The
 * entries are then written using the ksz9477_acl_write_list function, ensuring
 * the ACL has a predictable initial hardware state.
 *
 * Returns: 0 on success, or an error code on failure.
 */
int ksz9477_port_acl_init(struct ksz_device *dev, int port)
{
	struct ksz9477_acl_entries *acles;
	struct ksz9477_acl_priv *acl;
	int ret, i;

	acl = kzalloc(sizeof(*acl), GFP_KERNEL);
	if (!acl)
		return -ENOMEM;

	dev->ports[port].acl_priv = acl;

	acles = &acl->acles;
	/* write all entries */
	for (i = 0; i < ARRAY_SIZE(acles->entries); i++) {
		u8 *entry = acles->entries[i].entry;

		/* Set all access bits to be able to write zeroed
		 * entry
		 */
		entry[KSZ9477_ACL_PORT_ACCESS_10] = 0xff;
		entry[KSZ9477_ACL_PORT_ACCESS_11] = 0xff;
	}

	ret = ksz9477_acl_write_list(dev, port);
	if (ret)
		goto free_acl;

	return 0;

free_acl:
	kfree(dev->ports[port].acl_priv);
	dev->ports[port].acl_priv = NULL;

	return ret;
}

/**
 * ksz9477_port_acl_free - Free the ACL resources for a specified port on a ksz
 *			   device.
 * @dev: The ksz_device instance.
 * @port: The port number to initialize the ACL for.
 *
 * This disables the ACL for the specified port and frees the associated memory,
 */
void ksz9477_port_acl_free(struct ksz_device *dev, int port)
{
	if (!dev->ports[port].acl_priv)
		return;

	ksz9477_acl_port_disable(dev, port);

	kfree(dev->ports[port].acl_priv);
	dev->ports[port].acl_priv = NULL;
}

/**
 * ksz9477_acl_set_reg - Set entry[16] and entry[17] depending on the updated
 *			   entry[]
 * @entry: An array containing the entries
 * @reg: The register of the entry that needs to be updated
 * @value: The value to be assigned to the updated entry
 *
 * This function updates the entry[] array based on the provided register and
 * value. It also sets entry[0x10] and entry[0x11] according to the ACL byte
 * enable rules.
 *
 * 0x10 - Byte Enable [15:8]
 *
 * Each bit enables accessing one of the ACL bytes when a read or write is
 * initiated by writing to the Port ACL Byte Enable LSB Register.
 * Bit 0 applies to the Port ACL Access 7 Register
 * Bit 1 applies to the Port ACL Access 6 Register, etc.
 * Bit 7 applies to the Port ACL Access 0 Register
 * 1 = Byte is selected for read/write
 * 0 = Byte is not selected
 *
 * 0x11 - Byte Enable [7:0]
 *
 * Each bit enables accessing one of the ACL bytes when a read or write is
 * initiated by writing to the Port ACL Byte Enable LSB Register.
 * Bit 0 applies to the Port ACL Access F Register
 * Bit 1 applies to the Port ACL Access E Register, etc.
 * Bit 7 applies to the Port ACL Access 8 Register
 * 1 = Byte is selected for read/write
 * 0 = Byte is not selected
 */
static void ksz9477_acl_set_reg(u8 *entry, enum ksz9477_acl_port_access reg,
				u8 value)
{
	if (reg >= KSZ9477_ACL_PORT_ACCESS_0 &&
	    reg <= KSZ9477_ACL_PORT_ACCESS_7) {
		entry[KSZ9477_ACL_PORT_ACCESS_10] |=
				BIT(KSZ9477_ACL_PORT_ACCESS_7 - reg);
	} else if (reg >= KSZ9477_ACL_PORT_ACCESS_8 &&
		   reg <= KSZ9477_ACL_PORT_ACCESS_F) {
		entry[KSZ9477_ACL_PORT_ACCESS_11] |=
			BIT(KSZ9477_ACL_PORT_ACCESS_F - reg);
	} else {
		WARN_ON(1);
		return;
	}

	entry[reg] = value;
}

/**
 * ksz9477_acl_matching_rule_cfg_l2 - Configure an ACL filtering entry to match
 *				      L2 types of Ethernet frames
 * @entry: Pointer to ACL entry buffer
 * @ethertype: Ethertype value
 * @eth_addr: Pointer to Ethernet address
 * @is_src: If true, match the source MAC address; if false, match the
 *	    destination MAC address
 *
 * This function configures an Access Control List (ACL) filtering
 * entry to match Layer 2 types of Ethernet frames based on the provided
 * ethertype and Ethernet address. Additionally, it can match either the source
 * or destination MAC address depending on the value of the is_src parameter.
 *
 * Register Descriptions for MD = 01 and ENB != 00 (Layer 2 MAC header
 * filtering)
 *
 * 0x01 - Mode and Enable
 *        Bits 5:4 - MD (Mode)
 *                01 = Layer 2 MAC header or counter filtering
 *        Bits 3:2 - ENB (Enable)
 *                01 = Comparison is performed only on the TYPE value
 *                10 = Comparison is performed only on the MAC Address value
 *                11 = Both the MAC Address and TYPE are tested
 *        Bit  1   - S/D (Source / Destination)
 *                0 = Destination address
 *                1 = Source address
 *        Bit  0   - EQ (Equal / Not Equal)
 *                0 = Not Equal produces true result
 *                1 = Equal produces true result
 *
 * 0x02-0x07 - MAC Address
 *        0x02 - MAC Address [47:40]
 *        0x03 - MAC Address [39:32]
 *        0x04 - MAC Address [31:24]
 *        0x05 - MAC Address [23:16]
 *        0x06 - MAC Address [15:8]
 *        0x07 - MAC Address [7:0]
 *
 * 0x08-0x09 - EtherType
 *        0x08 - EtherType [15:8]
 *        0x09 - EtherType [7:0]
 */
static void ksz9477_acl_matching_rule_cfg_l2(u8 *entry, u16 ethertype,
					     u8 *eth_addr, bool is_src)
{
	u8 enb = 0;
	u8 val;

	if (ethertype)
		enb |= KSZ9477_ACL_ENB_L2_TYPE;
	if (eth_addr)
		enb |= KSZ9477_ACL_ENB_L2_MAC;

	val = FIELD_PREP(KSZ9477_ACL_MD_MASK, KSZ9477_ACL_MD_L2_MAC) |
	      FIELD_PREP(KSZ9477_ACL_ENB_MASK, enb) |
	      FIELD_PREP(KSZ9477_ACL_SD_SRC, is_src) | KSZ9477_ACL_EQ_EQUAL;
	ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_1, val);

	if (eth_addr) {
		int i;

		for (i = 0; i < ETH_ALEN; i++) {
			ksz9477_acl_set_reg(entry,
					    KSZ9477_ACL_PORT_ACCESS_2 + i,
					    eth_addr[i]);
		}
	}

	ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_8, ethertype >> 8);
	ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_9, ethertype & 0xff);
}

/**
 * ksz9477_acl_action_rule_cfg - Set action for an ACL entry
 * @entry: Pointer to the ACL entry
 * @force_prio: If true, force the priority value
 * @prio_val: Priority value
 *
 * This function sets the action for the specified ACL entry. It prepares
 * the priority mode and traffic class values and updates the entry's
 * action registers accordingly. Currently, there is no port or VLAN PCP
 * remapping.
 *
 * ACL Action Rule Parameters for Non-Count Modes (MD ≠ 01 or ENB ≠ 00)
 *
 * 0x0A - PM, P, RPE, RP[2:1]
 *        Bits 7:6 - PM[1:0] - Priority Mode
 *		00 = ACL does not specify the packet priority. Priority is
 *		     determined by standard QoS functions.
 *		01 = Change packet priority to P[2:0] if it is greater than QoS
 *		     result.
 *		10 = Change packet priority to P[2:0] if it is smaller than the
 *		     QoS result.
 *		11 = Always change packet priority to P[2:0].
 *        Bits 5:3 - P[2:0] - Priority value
 *        Bit  2   - RPE - Remark Priority Enable
 *        Bits 1:0 - RP[2:1] - Remarked Priority value (bits 2:1)
 *		0 = Disable priority remarking
 *		1 = Enable priority remarking. VLAN tag priority (PCP) bits are
 *		    replaced by RP[2:0].
 *
 * 0x0B - RP[0], MM
 *        Bit  7   - RP[0] - Remarked Priority value (bit 0)
 *        Bits 6:5 - MM[1:0] - Map Mode
 *		00 = No forwarding remapping
 *		01 = The forwarding map in FORWARD is OR'ed with the forwarding
 *		     map from the Address Lookup Table.
 *		10 = The forwarding map in FORWARD is AND'ed with the forwarding
 *		     map from the Address Lookup Table.
 *		11 = The forwarding map in FORWARD replaces the forwarding map
 *		     from the Address Lookup Table.
 * 0x0D - FORWARD[n:0]
 *       Bits 7:0 - FORWARD[n:0] - Forwarding map. Bit 0 = port 1,
 *		    bit 1 = port 2, etc.
 *		1 = enable forwarding to this port
 *		0 = do not forward to this port
 */
void ksz9477_acl_action_rule_cfg(u8 *entry, bool force_prio, u8 prio_val)
{
	u8 prio_mode, val;

	if (force_prio)
		prio_mode = KSZ9477_ACL_PM_REPLACE;
	else
		prio_mode = KSZ9477_ACL_PM_DISABLE;

	val = FIELD_PREP(KSZ9477_ACL_PM_M, prio_mode) |
	      FIELD_PREP(KSZ9477_ACL_P_M, prio_val);
	ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_A, val);

	/* no port or VLAN PCP remapping for now */
	ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_B, 0);
	ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_D, 0);
}

/**
 * ksz9477_acl_processing_rule_set_action - Set the action for the processing
 *					    rule set.
 * @entry: Pointer to the ACL entry
 * @action_idx: Index of the action to be applied
 *
 * This function sets the action for the processing rule set by updating the
 * appropriate register in the entry. There can be only one action per
 * processing rule.
 *
 * Access Control List (ACL) Processing Rule Registers:
 *
 * 0x00 - First Rule Number (FRN)
 *        Bits 3:0 - First Rule Number. Pointer to an Action rule entry.
 */
void ksz9477_acl_processing_rule_set_action(u8 *entry, u8 action_idx)
{
	ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_0, action_idx);
}

/**
 * ksz9477_acl_processing_rule_add_match - Add a matching rule to the rule set
 * @entry: Pointer to the ACL entry
 * @match_idx: Index of the matching rule to be added
 *
 * This function adds a matching rule to the rule set by updating the
 * appropriate bits in the entry's rule set registers.
 *
 * Access Control List (ACL) Processing Rule Registers:
 *
 * 0x0E - RuleSet [15:8]
 *        Bits 7:0 - RuleSet [15:8] Specifies a set of one or more Matching rule
 *        entries. RuleSet has one bit for each of the 16 Matching rule entries.
 *        If multiple Matching rules are selected, then all conditions will be
 *	  AND'ed to produce a final match result.
 *		0 = Matching rule not selected
 *		1 = Matching rule selected
 *
 * 0x0F - RuleSet [7:0]
 *        Bits 7:0 - RuleSet [7:0]
 */
static void ksz9477_acl_processing_rule_add_match(u8 *entry, u8 match_idx)
{
	u8 vale = entry[KSZ9477_ACL_PORT_ACCESS_E];
	u8 valf = entry[KSZ9477_ACL_PORT_ACCESS_F];

	if (match_idx < 8)
		valf |= BIT(match_idx);
	else
		vale |= BIT(match_idx - 8);

	ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_E, vale);
	ksz9477_acl_set_reg(entry, KSZ9477_ACL_PORT_ACCESS_F, valf);
}

/**
 * ksz9477_acl_get_init_entry - Get a new uninitialized entry for a specified
 *				port on a ksz_device.
 * @dev: The ksz_device instance.
 * @port: The port number to get the uninitialized entry for.
 * @cookie: The cookie to associate with the entry.
 * @prio: The priority to associate with the entry.
 *
 * This function retrieves the next available ACL entry for the specified port,
 * clears all access flags, and associates it with the current cookie.
 *
 * Returns: A pointer to the new uninitialized ACL entry.
 */
static struct ksz9477_acl_entry *
ksz9477_acl_get_init_entry(struct ksz_device *dev, int port,
			   unsigned long cookie, u32 prio)
{
	struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
	struct ksz9477_acl_entries *acles = &acl->acles;
	struct ksz9477_acl_entry *entry;

	entry = &acles->entries[acles->entries_count];
	entry->cookie = cookie;
	entry->prio = prio;

	/* clear all access flags */
	entry->entry[KSZ9477_ACL_PORT_ACCESS_10] = 0;
	entry->entry[KSZ9477_ACL_PORT_ACCESS_11] = 0;

	return entry;
}

/**
 * ksz9477_acl_match_process_l2 - Configure Layer 2 ACL matching rules and
 *                                processing rules.
 * @dev: Pointer to the ksz_device.
 * @port: Port number.
 * @ethtype: Ethernet type.
 * @src_mac: Source MAC address.
 * @dst_mac: Destination MAC address.
 * @cookie: The cookie to associate with the entry.
 * @prio: The priority of the entry.
 *
 * This function sets up matching and processing rules for Layer 2 ACLs.
 * It takes into account that only one MAC per entry is supported.
 */
void ksz9477_acl_match_process_l2(struct ksz_device *dev, int port,
				  u16 ethtype, u8 *src_mac, u8 *dst_mac,
				  unsigned long cookie, u32 prio)
{
	struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
	struct ksz9477_acl_entries *acles = &acl->acles;
	struct ksz9477_acl_entry *entry;

	entry = ksz9477_acl_get_init_entry(dev, port, cookie, prio);

	/* ACL supports only one MAC per entry */
	if (src_mac && dst_mac) {
		ksz9477_acl_matching_rule_cfg_l2(entry->entry, ethtype, src_mac,
						 true);

		/* Add both match entries to first processing rule */
		ksz9477_acl_processing_rule_add_match(entry->entry,
						      acles->entries_count);
		acles->entries_count++;
		ksz9477_acl_processing_rule_add_match(entry->entry,
						      acles->entries_count);

		entry = ksz9477_acl_get_init_entry(dev, port, cookie, prio);
		ksz9477_acl_matching_rule_cfg_l2(entry->entry, 0, dst_mac,
						 false);
		acles->entries_count++;
	} else {
		u8 *mac = src_mac ? src_mac : dst_mac;
		bool is_src = src_mac ? true : false;

		ksz9477_acl_matching_rule_cfg_l2(entry->entry, ethtype, mac,
						 is_src);
		ksz9477_acl_processing_rule_add_match(entry->entry,
						      acles->entries_count);
		acles->entries_count++;
	}
}