openMSX
CartridgeSlotManager.cc
Go to the documentation of this file.
2 #include "HardwareConfig.hh"
3 #include "CommandException.hh"
4 #include "FileContext.hh"
5 #include "TclObject.hh"
6 #include "MSXException.hh"
7 #include "CliComm.hh"
8 #include "unreachable.hh"
9 #include "one_of.hh"
10 #include "outer.hh"
11 #include "ranges.hh"
12 #include "StringOp.hh"
13 #include "xrange.hh"
14 #include <cassert>
15 #include <memory>
16 
17 using std::string;
18 
19 namespace openmsx {
20 
21 // CartridgeSlotManager::Slot
22 CartridgeSlotManager::Slot::~Slot()
23 {
24  assert(!config);
25  assert(useCount == 0);
26 }
27 
29 {
30  return cartCommand.has_value();
31 }
32 
33 bool CartridgeSlotManager::Slot::used(const HardwareConfig* allowed) const
34 {
35  assert((useCount == 0) == (config == nullptr));
36  return config && (config != allowed);
37 }
38 
39 
40 // CartridgeSlotManager
42  : motherBoard(motherBoard_)
43  , cartCmd(*this, motherBoard, "cart")
44  , extSlotInfo(motherBoard.getMachineInfoCommand())
45 {
46 }
47 
49 {
50  for (auto slot : xrange(MAX_SLOTS)) {
51  (void)slot;
52  assert(!slots[slot].exists());
53  assert(!slots[slot].used());
54  }
55 }
56 
57 int CartridgeSlotManager::getSlotNum(std::string_view slot)
58 {
59  if (slot.size() == 1) {
60  if (('0' <= slot[0]) && (slot[0] <= '3')) {
61  return slot[0] - '0';
62  } else if (('a' <= slot[0]) && (slot[0] <= 'p')) {
63  return -(1 + slot[0] - 'a');
64  } else if (slot[0] == 'X') {
65  return -256;
66  }
67  } else if (slot.size() == 2) {
68  if ((slot[0] == '?') && ('0' <= slot[1]) && (slot[1] <= '3')) {
69  return slot[1] - '0' - 128;
70  }
71  } else if (slot == "any") {
72  return -256;
73  }
74  throw MSXException("Invalid slot specification: ", slot);
75 }
76 
78 {
79  createExternalSlot(ps, -1);
80 }
81 
83 {
84  if (isExternalSlot(ps, ss, false)) {
85  throw MSXException("Slot is already an external slot.");
86  }
87  for (auto slot : xrange(MAX_SLOTS)) {
88  if (!slots[slot].exists()) {
89  slots[slot].ps = ps;
90  slots[slot].ss = ss;
91  char slotName[] = "carta";
92  slotName[4] += slot;
93  char extName[] = "exta";
94  extName[3] += slot;
95  motherBoard.getMSXCliComm().update(
96  CliComm::HARDWARE, slotName, "add");
97  slots[slot].cartCommand.emplace(
98  *this, motherBoard, slotName);
99  slots[slot].extCommand.emplace(
100  motherBoard, extName);
101  return;
102  }
103  }
104  UNREACHABLE;
105 }
106 
107 
108 int CartridgeSlotManager::getSlot(int ps, int ss) const
109 {
110  for (auto slot : xrange(MAX_SLOTS)) {
111  if (slots[slot].exists() &&
112  (slots[slot].ps == ps) && (slots[slot].ss == ss)) {
113  return slot;
114  }
115  }
116  UNREACHABLE; // was not an external slot
117  return 0; // avoid warning
118 }
119 
121  int ps, const HardwareConfig& allowed) const
122 {
123  testRemoveExternalSlot(ps, -1, allowed);
124 }
125 
127  int ps, int ss, const HardwareConfig& allowed) const
128 {
129  int slot = getSlot(ps, ss);
130  if (slots[slot].used(&allowed)) {
131  throw MSXException("Slot still in use.");
132  }
133 }
134 
136 {
137  removeExternalSlot(ps, -1);
138 }
139 
141 {
142  int slot = getSlot(ps, ss);
143  assert(!slots[slot].used());
144  const auto& slotName = slots[slot].cartCommand->getName();
145  motherBoard.getMSXCliComm().update(
146  CliComm::HARDWARE, slotName, "remove");
147  slots[slot].cartCommand.reset();
148  slots[slot].extCommand.reset();
149 }
150 
151 void CartridgeSlotManager::getSpecificSlot(unsigned slot, int& ps, int& ss) const
152 {
153  assert(slot < MAX_SLOTS);
154  if (!slots[slot].exists()) {
155  throw MSXException("slot-", char('a' + slot), " not defined.");
156  }
157  if (slots[slot].used()) {
158  throw MSXException("slot-", char('a' + slot), " already in use.");
159  }
160  ps = slots[slot].ps;
161  ss = slots[slot].ss;
162 }
163 
165 {
166  assert(slot < MAX_SLOTS);
167  if (!slots[slot].exists()) {
168  throw MSXException("slot-", char('a' + slot), " not defined.");
169  }
170  if (slots[slot].used()) {
171  throw MSXException("slot-", char('a' + slot), " already in use.");
172  }
173  if (slots[slot].ss != -1) {
174  throw MSXException("slot-", char('a' + slot), " is not a primary slot.");
175  }
176  assert(slots[slot].useCount == 0);
177  slots[slot].config = &hwConfig;
178  slots[slot].useCount = 1;
179  return slots[slot].ps;
180 }
181 
182 void CartridgeSlotManager::getAnyFreeSlot(int& ps, int& ss) const
183 {
184  // search for the lowest free slot
185  ps = 4; // mark no free slot
186  for (auto slot : xrange(MAX_SLOTS)) {
187  if (slots[slot].exists() && !slots[slot].used()) {
188  int p = slots[slot].ps;
189  int s = slots[slot].ss;
190  if ((p < ps) || ((p == ps) && (s < ss))) {
191  ps = p;
192  ss = s;
193  }
194  }
195  }
196  if (ps == 4) {
197  throw MSXException("Not enough free cartridge slots");
198  }
199 }
200 
202 {
203  for (auto slot : xrange(MAX_SLOTS)) {
204  if (slots[slot].exists() && (slots[slot].ss == -1) &&
205  !slots[slot].used()) {
206  assert(slots[slot].useCount == 0);
207  slots[slot].config = &hwConfig;
208  slots[slot].useCount = 1;
209  return slots[slot].ps;
210  }
211  }
212  throw MSXException("No free primary slot");
213 }
214 
216  int ps, const HardwareConfig& hwConfig)
217 {
218  int slot = getSlot(ps, -1);
219  assert(slots[slot].config == &hwConfig); (void)hwConfig;
220  assert(slots[slot].useCount == 1);
221  slots[slot].config = nullptr;
222  slots[slot].useCount = 0;
223 }
224 
226  int ps, int ss, const HardwareConfig& hwConfig)
227 {
228  for (auto slot : xrange(MAX_SLOTS)) {
229  if (!slots[slot].exists()) continue;
230  if ((slots[slot].ps == ps) && (slots[slot].ss == ss)) {
231  if (slots[slot].useCount == 0) {
232  slots[slot].config = &hwConfig;
233  } else {
234  if (slots[slot].config != &hwConfig) {
235  throw MSXException(
236  "Slot ", ps, '-', ss,
237  " already in use by ",
238  slots[slot].config->getName());
239  }
240  }
241  ++slots[slot].useCount;
242  }
243  }
244  // Slot not found, was not an external slot. No problem.
245 }
246 
248  int ps, int ss, const HardwareConfig& hwConfig)
249 {
250  for (auto slot : xrange(MAX_SLOTS)) {
251  if (!slots[slot].exists()) continue;
252  if ((slots[slot].ps == ps) && (slots[slot].ss == ss)) {
253  assert(slots[slot].config == &hwConfig); (void)hwConfig;
254  assert(slots[slot].useCount > 0);
255  --slots[slot].useCount;
256  if (slots[slot].useCount == 0) {
257  slots[slot].config = nullptr;
258  }
259  return;
260  }
261  }
262  // Slot not found, was not an external slot. No problem.
263 }
264 
265 bool CartridgeSlotManager::isExternalSlot(int ps, int ss, bool convert) const
266 {
267  return ranges::any_of(xrange(MAX_SLOTS), [&](auto slot) {
268  int tmp = (convert && (slots[slot].ss == -1)) ? 0 : slots[slot].ss;
269  return slots[slot].exists() && (slots[slot].ps == ps) && (tmp == ss);
270  });
271 }
272 
273 
274 // CartCmd
275 CartridgeSlotManager::CartCmd::CartCmd(
276  CartridgeSlotManager& manager_, MSXMotherBoard& motherBoard_,
277  std::string_view commandName)
278  : RecordedCommand(motherBoard_.getCommandController(),
279  motherBoard_.getStateChangeDistributor(),
280  motherBoard_.getScheduler(),
281  commandName)
282  , manager(manager_)
283  , cliComm(motherBoard_.getMSXCliComm())
284 {
285 }
286 
287 const HardwareConfig* CartridgeSlotManager::CartCmd::getExtensionConfig(
288  std::string_view cartname)
289 {
290  if (cartname.size() != 5) {
291  throw SyntaxError();
292  }
293  int slot = cartname[4] - 'a';
294  return manager.slots[slot].config;
295 }
296 
297 void CartridgeSlotManager::CartCmd::execute(
298  span<const TclObject> tokens, TclObject& result, EmuTime::param /*time*/)
299 {
300  std::string_view cartname = tokens[0].getString();
301 
302  // strip namespace qualification
303  // TODO investigate whether it's a good idea to strip namespace at a
304  // higher level for all commands. How does that interact with
305  // the event recording feature?
306  if (auto pos = cartname.rfind("::"); pos != std::string_view::npos) {
307  cartname = cartname.substr(pos + 2);
308  }
309  if (tokens.size() == 1) {
310  // query name of cartridge
311  const auto* extConf = getExtensionConfig(cartname);
312  result.addListElement(tmpStrCat(cartname, ':'),
313  extConf ? extConf->getName() : string{});
314  if (!extConf) {
315  TclObject options = makeTclList("empty");
316  result.addListElement(options);
317  }
318  } else if (tokens[1] == one_of("eject", "-eject")) {
319  // remove cartridge (or extension)
320  if (tokens[1] == "-eject") {
321  result =
322  "Warning: use of '-eject' is deprecated, "
323  "instead use the 'eject' subcommand";
324  }
325  if (auto* extConf = getExtensionConfig(cartname)) {
326  try {
327  manager.motherBoard.removeExtension(*extConf);
328  cliComm.update(CliComm::MEDIA, cartname, {});
329  } catch (MSXException& e) {
330  throw CommandException("Can't remove cartridge: ",
331  e.getMessage());
332  }
333  }
334  } else {
335  // insert cartridge
336  auto slotname = (cartname.size() == 5)
337  ? cartname.substr(4, 1)
338  : "any";
339  size_t extensionNameToken = 1;
340  if (tokens[1] == "insert") {
341  if (tokens.size() > 2) {
342  extensionNameToken = 2;
343  } else {
344  throw CommandException("Missing argument to insert subcommand");
345  }
346  }
347  auto options = tokens.subspan(extensionNameToken + 1);
348  try {
349  std::string_view romname = tokens[extensionNameToken].getString();
350  auto extension = HardwareConfig::createRomConfig(
351  manager.motherBoard, string(romname), string(slotname), options);
352  if (slotname != "any") {
353  if (auto* extConf = getExtensionConfig(cartname)) {
354  // still a cartridge inserted, (try to) remove it now
355  manager.motherBoard.removeExtension(*extConf);
356  }
357  }
358  result = manager.motherBoard.insertExtension(
359  "ROM", std::move(extension));
360  cliComm.update(CliComm::MEDIA, cartname, romname);
361  } catch (MSXException& e) {
362  throw CommandException(std::move(e).getMessage());
363  }
364  }
365 }
366 
367 string CartridgeSlotManager::CartCmd::help(span<const TclObject> tokens) const
368 {
369  auto cart = tokens[0].getString();
370  return strCat(
371  cart, " eject : remove the ROM cartridge from this slot\n",
372  cart, " insert <filename> : insert ROM cartridge with <filename>\n",
373  cart, " <filename> : insert ROM cartridge with <filename>\n",
374  cart, " : show which ROM cartridge is in this slot\n",
375  "The following options are supported when inserting a cartridge:\n"
376  "-ips <filename> : apply the given IPS patch to the ROM image\n"
377  "-romtype <romtype> : specify the ROM mapper type\n");
378 }
379 
380 void CartridgeSlotManager::CartCmd::tabCompletion(std::vector<string>& tokens) const
381 {
382  using namespace std::literals;
383  static constexpr std::array extra = {"eject"sv, "insert"sv};
384  completeFileName(tokens, userFileContext(),
385  (tokens.size() < 3) ? extra : span<const std::string_view>{});
386 
387 }
388 
389 bool CartridgeSlotManager::CartCmd::needRecord(span<const TclObject> tokens) const
390 {
391  return tokens.size() > 1;
392 }
393 
394 
395 // class CartridgeSlotInfo
396 
397 CartridgeSlotManager::CartridgeSlotInfo::CartridgeSlotInfo(
398  InfoCommand& machineInfoCommand)
399  : InfoTopic(machineInfoCommand, "external_slot")
400 {
401 }
402 
403 void CartridgeSlotManager::CartridgeSlotInfo::execute(
404  span<const TclObject> tokens, TclObject& result) const
405 {
406  checkNumArgs(tokens, Between{2, 3}, Prefix{2}, "?slot?");
407  auto& manager = OUTER(CartridgeSlotManager, extSlotInfo);
408  switch (tokens.size()) {
409  case 2: {
410  // return list of slots
411  string slot = "slotX";
412  for (auto i : xrange(CartridgeSlotManager::MAX_SLOTS)) {
413  if (!manager.slots[i].exists()) continue;
414  slot[4] = char('a' + i);
415  result.addListElement(slot);
416  }
417  break;
418  }
419  case 3: {
420  // return info on a particular slot
421  const auto& slotName = tokens[2].getString();
422  if ((slotName.size() != 5) || (!StringOp::startsWith(slotName, "slot"))) {
423  throw CommandException("Invalid slot name: ", slotName);
424  }
425  unsigned num = slotName[4] - 'a';
426  if (num >= CartridgeSlotManager::MAX_SLOTS) {
427  throw CommandException("Invalid slot name: ", slotName);
428  }
429  auto& slot = manager.slots[num];
430  if (!slot.exists()) {
431  throw CommandException("Slot '", slotName, "' doesn't currently exist in this msx machine.");
432  }
433  result.addListElement(slot.ps);
434  if (slot.ss == -1) {
435  result.addListElement("X");
436  } else {
437  result.addListElement(slot.ss);
438  }
439  if (slot.config) {
440  result.addListElement(slot.config->getName());
441  } else {
442  result.addListElement(std::string_view{});
443  }
444  break;
445  }
446  }
447 }
448 
449 string CartridgeSlotManager::CartridgeSlotInfo::help(
450  span<const TclObject> /*tokens*/) const
451 {
452  return "Without argument: show list of available external slots.\n"
453  "With argument: show primary and secondary slot number for "
454  "given external slot.\n";
455 }
456 
457 } // namespace openmsx
Definition: one_of.hh:7
CartridgeSlotManager(MSXMotherBoard &motherBoard)
void getAnyFreeSlot(int &ps, int &ss) const
static int getSlotNum(std::string_view slot)
void testRemoveExternalSlot(int ps, const HardwareConfig &allowed) const
void getSpecificSlot(unsigned slot, int &ps, int &ss) const
bool isExternalSlot(int ps, int ss, bool convert) const
void freeSlot(int ps, int ss, const HardwareConfig &hwConfig)
int allocateAnyPrimarySlot(const HardwareConfig &hwConfig)
int allocateSpecificPrimarySlot(unsigned slot, const HardwareConfig &hwConfig)
void allocateSlot(int ps, int ss, const HardwareConfig &hwConfig)
void freePrimarySlot(int ps, const HardwareConfig &hwConfig)
virtual void update(UpdateType type, std::string_view name, std::string_view value)=0
static std::unique_ptr< HardwareConfig > createRomConfig(MSXMotherBoard &motherBoard, std::string romfile, std::string slotname, span< const TclObject > options)
Commands that directly influence the MSX state should send and events so that they can be recorded by...
Definition: span.hh:126
constexpr subspan_return_t< Offset, Count > subspan() const
Definition: span.hh:266
constexpr index_type size() const noexcept
Definition: span.hh:296
bool startsWith(string_view total, string_view part)
Definition: StringOp.cc:29
bool exists(zstring_view filename)
Does this file (directory) exists?
This file implemented 3 utility functions:
Definition: Autofire.cc:9
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:173
TclObject makeTclList(Args &&... args)
Definition: TclObject.hh:290
bool any_of(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:155
#define OUTER(type, member)
Definition: outer.hh:41
TemporaryString tmpStrCat(Ts &&... ts)
Definition: strCat.hh:659
std::string strCat(Ts &&...ts)
Definition: strCat.hh:591
#define UNREACHABLE
Definition: unreachable.hh:38
constexpr auto xrange(T e)
Definition: xrange.hh:155