Update: Using a custom ChartElementFunction
to represent each data point with a disk:
ClearAll[ceF]
ceF[nc_: 5, sz_: 5, shft_: 5][ clr_: "Pastel"] :=
Module[{nrows, disks, bb = Charting`ChartStyleInformation["BoundingBox"][[2]],
ncols = Range[#[[1, 1]], #[[1, 2]], (#[[1, 2]] - #[[1, 1]])/(nc - 1)],
cols = If[Head[clr] === String,
ColorData[clr] /@ Range[0, 1, 1/(Length[#3[[1]]] - 1)], clr]},
nrows = Range[## & @@ (shft+bb), (bb[[2]] - bb[[1]])/Quotient[bb[[2]] - bb[[1]], nc]];
disks = Disk[#, Offset[sz]] & /@ (Reverse /@ Tuples[{nrows, ncols}][[;; #2]]);
Thread[{Join @@ ConstantArray @@@ Thread[{cols, #3[[1]]}], disks}]] &;
Examples:
Use the total of each data group as main data and pass its breakdown as metadata to be processed by the ChartElementFunction
to render bars filled with appropriate number of colored disks:
data = {{10, 8, 2}, {12, 15, 4}, {5, 7, 7}};
bcdata = Thread[Total /@ data -> data];
Labeled[BarChart[bcdata, ChartElementFunction -> ceF[][],
BarSpacing -> {1, 0}, ChartLabels -> {Style[#, 16] & /@ {"A", "B", "<=C"}},
Axes -> {True, False},
ChartLegends -> SwatchLegend["Pastel", Style[#, 16] & /@ {"A", "B", "<=C"},
LegendLayout -> "ReversedColumn",
LegendLabel -> Style["Exam 2 Grades", 16, "Panel"]]],
{Rotate[Style["Counts", 16, "Panel"], 90 Degree],
Style["Exam 1", 16, "Panel"]}, {Left, Bottom}]

Use ChartElementFunction -> ceF[][{Red, Green, Blue}]
with appropriate changes in the legend to get

Use ChartElementFunction -> ceF[3, 7][{Red, Green, Blue}]
to get larger disks arranged in three columns:

etc..
Original answer:
data = {{10, 8, 2}, {12, 15, 4}, {5, 7, 7}};
Using a standard BarChart
you can do
Labeled[BarChart[data, ChartLayout -> "Stacked",
ChartLabels -> {Style[#, 16] & /@ {"A", "B", "<=C"}, None},
ChartLegends -> SwatchLegend[Style[#, 16] & /@ {"A", "B", "<=C"},
LegendLayout -> "ReversedColumn",
LegendLabel -> Style["Exam 2 \nGrades", 16, "Panel"]]],
{Rotate[Style["Count", 16, "Panel"], 90 Degree], Style["Exam 1", 16, "Panel"]},
{Left, Bottom}]

Use labeleddata
labeleddata = Thread[Labeled[#, StringJoin @@@ Thread[{{"A : ", "B : ", "<=C : "},
ToString /@ #}], Center]] & /@ data;
as the first argument of BarChart
above to get
