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